mirror of
https://github.com/sortedcord/omnia.git
synced 2026-07-22 03:52:48 +05:30
FEAT!(voice): Implement intent hydration, dehydration system fixes: #29
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
"dependencies": {
|
||||
"@omnia/core": "workspace:*",
|
||||
"@omnia/llm": "workspace:*",
|
||||
"@omnia/voice": "workspace:*",
|
||||
"zod": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { WorldState, resolveAlias } from "@omnia/core";
|
||||
import { ILLMProvider } from "@omnia/llm";
|
||||
import { dehydrate } from "@omnia/voice";
|
||||
import { Intent, IntentSequence, LLMIntentSequenceSchema } from "./intent.js";
|
||||
|
||||
export class IntentDecoder {
|
||||
@@ -51,8 +52,10 @@ export class IntentDecoder {
|
||||
return `(Alias="${alias}", ID="${tid}")`;
|
||||
})
|
||||
.join(", ");
|
||||
// In the historical context, prior.content is already dehydrated, so we hydrate it for the actor's view
|
||||
// Wait, we can keep it simple or just use the content. We'll show the content.
|
||||
historicalLines.push(
|
||||
` - Content: "${prior.originalText}", Type: ${prior.type}, Target Entities: ${targetsStr || "None"}`,
|
||||
` - Content: "${prior.content}", Type: ${prior.type}, Target Entities: ${targetsStr || "None"}`,
|
||||
);
|
||||
}
|
||||
const historicalContext =
|
||||
@@ -67,15 +70,11 @@ Your job is to take a block of narrative prose written by an actor agent and dec
|
||||
For each intent you must:
|
||||
1. Classify its type:
|
||||
- "dialogue": if actor speaking, talking, whispering, murmuring, etc
|
||||
- "action": Any physical or logical action performed in the world (e.g., moving, opening, looking).
|
||||
- "monologue" (or "thought"): An inner thought, reflection, or internal monologue/self narration.
|
||||
2. Extract the original text fragment from the prose that corresponds to this intent.
|
||||
3. Populate "description" and "selfDescription":
|
||||
- "description": No subject or name — a bare third-person verb phrase only (e.g. "clears their throat", "shakes their head slowly")
|
||||
- "selfDescription": The same event from the actor's own perspective, second person, complete sentence starting with "You" (e.g. "You clear your throat.", "You shake your head slowly."). This is shown directly in the actor's own memory — it must never say "the actor" or refer to them in the third person.
|
||||
- In case of a dialogue, the description and self Description only stores the exact words said by the entity. (e.g. "I will do that later", "Are you serious right now?")
|
||||
4. Identify targetIds — the entity IDs of the receiving parties. Use the "Other entities in context" list to resolve any subjective names, aliases, or descriptions used in the prose to their correct entity IDs. If no specific target, use an empty array.
|
||||
5. Identify modifiers — a list of strings representing additional qualities or modifiers extracted from the narrative prose. This includes emotions, tone of voice, speed, manner of action, or statement type (e.g., "question", "anxious", "whispering", "slowly", "quietly", "forcefully"). If no modifiers are present, use an empty array.
|
||||
- "action": Any physical action performed in the world (e.g., moving, opening, looking). DO NOT CLASSIFY SPEAKING MODIFIERS AS ACTIONS
|
||||
- "monologue" (or "thought"): An inner thought, reflection, or monologue/self narration.
|
||||
2. Extract the original narrative text fragment from the prose that corresponds to this intent and populate it as "content". Do not paraphrase, do not convert to third person, and do not convert to second person. Keep the original text fragment exactly as written in the prose (first-person voice).
|
||||
3. Identify targetIds — the entity IDs of the receiving parties. Use the "Other entities in context" list to resolve any subjective names, aliases, or descriptions used in the prose to their correct entity IDs. If no specific target, use an empty array.
|
||||
4. Identify modifiers — a list of strings representing additional qualities or modifiers extracted from the narrative prose. This includes emotions, tone of voice, speed, manner of action, or statement type (e.g., "question", "anxious", "whispering", "slowly", "quietly", "forcefully"). If no modifiers are present, use an empty array.
|
||||
`.trim();
|
||||
|
||||
const userContext = `
|
||||
@@ -101,10 +100,26 @@ ${narrativeProse}
|
||||
);
|
||||
}
|
||||
|
||||
const fullIntents = response.data.intents.map((intent) => ({
|
||||
...intent,
|
||||
actorId,
|
||||
}));
|
||||
const aliasMap: Record<string, string> = {};
|
||||
if (actor) {
|
||||
for (const [targetId, alias] of actor.aliases.entries()) {
|
||||
aliasMap[alias] = targetId;
|
||||
}
|
||||
}
|
||||
|
||||
const fullIntents = response.data.intents.map((intent) => {
|
||||
const dehydrated = dehydrate(
|
||||
intent.content,
|
||||
actorId,
|
||||
intent.targetIds,
|
||||
aliasMap,
|
||||
);
|
||||
return {
|
||||
...intent,
|
||||
content: dehydrated,
|
||||
actorId,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
intents: fullIntents,
|
||||
|
||||
@@ -24,14 +24,8 @@ export const LLMIntentSchema = z.object({
|
||||
/** The type of intent. */
|
||||
type: IntentTypeSchema,
|
||||
|
||||
/** The original narrative text fragment this intent was extracted from. */
|
||||
originalText: z.string(),
|
||||
|
||||
/** A concise, structured description of the intent's action or dialogue. */
|
||||
description: z.string(),
|
||||
|
||||
/** The same event from the actor's own perspective (second person, "You"). */
|
||||
selfDescription: z.string(),
|
||||
/** The dehydrated canonical content of the intent. */
|
||||
content: z.string(),
|
||||
|
||||
/**
|
||||
* Entity IDs of the receiving parties (e.g., who is being spoken to,
|
||||
|
||||
@@ -9,13 +9,11 @@ describe("IntentDecoder Unit Tests (Tier 1)", () => {
|
||||
const alice = new Entity("alice");
|
||||
world.addEntity(alice);
|
||||
|
||||
const mockResponse: IntentSequence = {
|
||||
const mockResponse = {
|
||||
intents: [
|
||||
{
|
||||
type: "action",
|
||||
originalText: "Alice opened the chest.",
|
||||
description: "Open the wooden chest.",
|
||||
selfDescription: "You open the wooden chest.",
|
||||
content: "I open the wooden chest.",
|
||||
targetIds: [],
|
||||
modifiers: [],
|
||||
},
|
||||
@@ -34,6 +32,7 @@ describe("IntentDecoder Unit Tests (Tier 1)", () => {
|
||||
expect(result.intents).toHaveLength(1);
|
||||
expect(result.intents[0].type).toBe("action");
|
||||
expect(result.intents[0].actorId).toBe("alice");
|
||||
expect(result.intents[0].content).toContain("entity@alice[I]");
|
||||
expect(result.intents[0].targetIds).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -44,13 +43,11 @@ describe("IntentDecoder Unit Tests (Tier 1)", () => {
|
||||
world.addEntity(alice);
|
||||
world.addEntity(bob);
|
||||
|
||||
const mockResponse: IntentSequence = {
|
||||
const mockResponse = {
|
||||
intents: [
|
||||
{
|
||||
type: "dialogue",
|
||||
originalText: '"Do you have the key?" Alice asked Bob.',
|
||||
description: "Alice asks Bob if he has the key.",
|
||||
selfDescription: "You ask Bob if he has the key.",
|
||||
content: '"Do you have the key?" I asked Bob.',
|
||||
targetIds: ["bob"],
|
||||
modifiers: [],
|
||||
},
|
||||
@@ -68,6 +65,7 @@ describe("IntentDecoder Unit Tests (Tier 1)", () => {
|
||||
|
||||
expect(result.intents).toHaveLength(1);
|
||||
expect(result.intents[0].type).toBe("dialogue");
|
||||
expect(result.intents[0].content).toContain("entity@alice[I]");
|
||||
expect(result.intents[0].targetIds).toEqual(["bob"]);
|
||||
});
|
||||
|
||||
@@ -78,21 +76,17 @@ describe("IntentDecoder Unit Tests (Tier 1)", () => {
|
||||
world.addEntity(alice);
|
||||
world.addEntity(bob);
|
||||
|
||||
const mockResponse: IntentSequence = {
|
||||
const mockResponse = {
|
||||
intents: [
|
||||
{
|
||||
type: "dialogue",
|
||||
originalText: '"Cover me," Alice whispered to Bob.',
|
||||
description: "Alice whispers to Bob requesting cover.",
|
||||
selfDescription: "You whisper to Bob requesting cover.",
|
||||
content: '"Cover me," I whispered to Bob.',
|
||||
targetIds: ["bob"],
|
||||
modifiers: [],
|
||||
},
|
||||
{
|
||||
type: "action",
|
||||
originalText: "She crept towards the door and pulled the handle.",
|
||||
description: "Creep towards the door and pull the handle.",
|
||||
selfDescription: "You creep towards the door and pull the handle.",
|
||||
content: "I crept towards the door and pulled the handle.",
|
||||
targetIds: [],
|
||||
modifiers: [],
|
||||
},
|
||||
@@ -113,6 +107,7 @@ describe("IntentDecoder Unit Tests (Tier 1)", () => {
|
||||
expect(result.intents[0].targetIds).toEqual(["bob"]);
|
||||
expect(result.intents[1].type).toBe("action");
|
||||
expect(result.intents[1].actorId).toBe("alice");
|
||||
expect(result.intents[1].content).toContain("entity@alice[I]");
|
||||
});
|
||||
|
||||
test("throws on LLM failure", async () => {
|
||||
|
||||
@@ -5,5 +5,9 @@
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "../core" }, { "path": "../llm" }]
|
||||
"references": [
|
||||
{ "path": "../core" },
|
||||
{ "path": "../llm" },
|
||||
{ "path": "../voice" }
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user