mirror of
https://github.com/sortedcord/omnia.git
synced 2026-07-22 03:52:48 +05:30
refactor(intent): Improve intent decoder userContext structure
This commit is contained in:
@@ -85,7 +85,7 @@
|
||||
},
|
||||
{
|
||||
"id": "zz3f29d2-as11-9811-9a99-b13c126d123e",
|
||||
"timestamp": "2026-07-01T09:58:00.000Z",
|
||||
"timestamp": "2026-07-09T07:58:00.000Z",
|
||||
"locationId": "white-room",
|
||||
"intent": {
|
||||
"type": "action",
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"@langchain/openai": "^0.3.17",
|
||||
"@langchain/openrouter": "^0.4.3",
|
||||
"@types/node": "^20.19.43",
|
||||
"compromise": "^14.16.0",
|
||||
"dotenv": "^17.4.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,13 +76,13 @@ Your output is a short block of narrative prose describing what your character d
|
||||
|
||||
Guidelines:
|
||||
- Always write in the first person
|
||||
- Only describe your character's own actions, spoken words, and internal reactions. Do NOT narrate or describe the environment or your surroundings, or other characters' actions.
|
||||
- Refer to other entities by the subjective names/aliases that you refer to them as.
|
||||
- Keep your prose vivid but concise. Write it in natural narrative order.
|
||||
- Not every response requires an outward action. It is perfectly valid to only think (a monologue) and do nothing perceivable.
|
||||
- Never speak or act on another entity's behalf. You only control your own character.
|
||||
- Stay strictly within what your character knows. Do not invent knowledge that doesn't exist or act on it.
|
||||
- You are limited by just your memory. If your memory is limited, then that's all you can remember. If you do make stuff up then that's lying. Which is allowed, but remember that you're lying.
|
||||
- You are limited by just your memory. If your memory is limited, then that's all you can remember. If you do make stuff up then that's lying. Which is allowed, but remember that you're lying
|
||||
- Only describe your character's own actions, spoken words, and internal reactions. Do NOT narrate the environment or your surroundings, or other characters' actions.
|
||||
".
|
||||
`.trim();
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ export class ActorAgent {
|
||||
|
||||
constructor(
|
||||
llmProvider: ILLMProvider | { actor: ILLMProvider; decoder: ILLMProvider },
|
||||
bufferRepo?: BufferRepository,
|
||||
private bufferRepo?: BufferRepository,
|
||||
ledgerRepo?: LedgerRepository,
|
||||
memoryLimit?: number,
|
||||
generator?: IActorProseGenerator,
|
||||
@@ -127,10 +127,19 @@ export class ActorAgent {
|
||||
userContext,
|
||||
);
|
||||
|
||||
const recentEntries = this.bufferRepo
|
||||
? this.bufferRepo.listForOwner(entity.id)
|
||||
: [];
|
||||
const recentIntents = recentEntries
|
||||
.filter((e) => e.intent.actorId !== entity.id)
|
||||
.slice(-3)
|
||||
.map((e) => e.intent);
|
||||
|
||||
const intents = await this.decoder.decode(
|
||||
worldState,
|
||||
entity.id,
|
||||
narrativeProse,
|
||||
recentIntents,
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { WorldState } from "@omnia/core";
|
||||
import { WorldState, resolveAlias } from "@omnia/core";
|
||||
import { ILLMProvider } from "@omnia/llm";
|
||||
import { IntentSequence, LLMIntentSequenceSchema } from "./intent.js";
|
||||
import { Intent, IntentSequence, LLMIntentSequenceSchema } from "./intent.js";
|
||||
|
||||
export class IntentDecoder {
|
||||
constructor(private llmProvider: ILLMProvider) {}
|
||||
@@ -18,20 +18,47 @@ export class IntentDecoder {
|
||||
worldState: WorldState,
|
||||
actorId: string,
|
||||
narrativeProse: string,
|
||||
recentIntents: Intent[] = [],
|
||||
): Promise<IntentSequence> {
|
||||
const entityIds = Array.from(worldState.entities.keys());
|
||||
const actor = worldState.getEntity(actorId);
|
||||
|
||||
const aliasEntries = actor ? Array.from(actor.aliases.entries()) : [];
|
||||
const aliasContext =
|
||||
aliasEntries.length > 0
|
||||
? aliasEntries
|
||||
.map(
|
||||
([targetId, alias]) =>
|
||||
`- "${alias}" refers to entity ID: "${targetId}"`,
|
||||
)
|
||||
.join("\n")
|
||||
: "(No known aliases)";
|
||||
// 1. Get other entities co-located in the same context
|
||||
const otherEntitiesLines: string[] = [];
|
||||
for (const otherEntity of worldState.entities.values()) {
|
||||
if (
|
||||
otherEntity.id !== actorId &&
|
||||
otherEntity.locationId === actor?.locationId
|
||||
) {
|
||||
const alias = actor
|
||||
? resolveAlias(actor, otherEntity.id)
|
||||
: otherEntity.id;
|
||||
otherEntitiesLines.push(` - Alias="${alias}" ID=${otherEntity.id}`);
|
||||
}
|
||||
}
|
||||
const otherEntitiesContext =
|
||||
otherEntitiesLines.length > 0
|
||||
? otherEntitiesLines.join("\n")
|
||||
: " (No other entities in context)";
|
||||
|
||||
// 2. Format historical context (2-3 recent intents received by the actor)
|
||||
const historicalLines: string[] = [];
|
||||
for (const prior of recentIntents) {
|
||||
const targetIds =
|
||||
prior.actorId !== actorId ? [prior.actorId] : prior.targetIds;
|
||||
const targetsStr = targetIds
|
||||
.map((tid) => {
|
||||
const alias = actor ? resolveAlias(actor, tid) : tid;
|
||||
return `(Alias="${alias}", ID="${tid}")`;
|
||||
})
|
||||
.join(", ");
|
||||
historicalLines.push(
|
||||
` - Content: "${prior.originalText}", Type: ${prior.type}, Target Entities: ${targetsStr || "None"}`,
|
||||
);
|
||||
}
|
||||
const historicalContext =
|
||||
historicalLines.length > 0
|
||||
? historicalLines.join("\n")
|
||||
: " (No prior intents in context)";
|
||||
|
||||
const systemPrompt = `
|
||||
You are the Intent Decoder for a narrative simulation engine.
|
||||
@@ -47,23 +74,16 @@ For each intent you must:
|
||||
- "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 "KNOWN ENTITY IDS" mapping to resolve any subjective names,or aliases used in the prose to their correct system entity IDs. If no specific target, use an empty array.
|
||||
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.
|
||||
`.trim();
|
||||
|
||||
const userContext = `
|
||||
=== KNOWN ENTITY IDS ===
|
||||
${entityIds.length > 0 ? entityIds.join(", ") : "(No entities)"}
|
||||
|
||||
=== ACTOR ALIASES ===
|
||||
The actor refers to other entities using these subjective names/aliases:
|
||||
${aliasContext}
|
||||
|
||||
=== WORLD STATE ===
|
||||
${serializeSimplifiedWorldState(worldState)}
|
||||
|
||||
=== ACTOR ===
|
||||
Actor ID: ${actorId}
|
||||
Intent Source: ${actorId}
|
||||
Other entities in context:
|
||||
${otherEntitiesContext}
|
||||
Historical Context:
|
||||
${historicalContext}
|
||||
|
||||
=== NARRATIVE PROSE ===
|
||||
${narrativeProse}
|
||||
@@ -91,32 +111,3 @@ ${narrativeProse}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function serializeSimplifiedWorldState(worldState: WorldState): string {
|
||||
const lines: string[] = [];
|
||||
|
||||
lines.push("Locations:");
|
||||
if (worldState.locations.size > 0) {
|
||||
for (const loc of worldState.locations.values()) {
|
||||
const parentId = (loc as { parentId?: string | null }).parentId;
|
||||
const parentStr = parentId ? ` (Parent: ${parentId})` : "";
|
||||
lines.push(` - Location [ID: ${loc.id}]${parentStr}`);
|
||||
}
|
||||
} else {
|
||||
lines.push(" (No locations)");
|
||||
}
|
||||
|
||||
lines.push("Entities:");
|
||||
if (worldState.entities.size > 0) {
|
||||
for (const entity of worldState.entities.values()) {
|
||||
const locStr = entity.locationId
|
||||
? ` (Location: ${entity.locationId})`
|
||||
: "";
|
||||
lines.push(` - Entity [ID: ${entity.id}]${locStr}`);
|
||||
}
|
||||
} else {
|
||||
lines.push(" (No entities)");
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
42
pnpm-lock.yaml
generated
42
pnpm-lock.yaml
generated
@@ -284,6 +284,9 @@ importers:
|
||||
"@types/node":
|
||||
specifier: ^20.19.43
|
||||
version: 20.19.43
|
||||
compromise:
|
||||
specifier: ^14.16.0
|
||||
version: 14.16.0
|
||||
dotenv:
|
||||
specifier: ^17.4.2
|
||||
version: 17.4.2
|
||||
@@ -4716,6 +4719,13 @@ packages:
|
||||
}
|
||||
engines: { node: ">= 18" }
|
||||
|
||||
compromise@14.16.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-4DFYl/Hl7sW4XWUDfx9S5vxqyYKpZDwwqrpXsQv5acdbVP+joKceIcIaLb0lhVWUpDBV0OnExk/o/dnYUwXnhQ==,
|
||||
}
|
||||
engines: { node: ">=12.0.0" }
|
||||
|
||||
conf@10.2.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -5388,6 +5398,13 @@ packages:
|
||||
integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==,
|
||||
}
|
||||
|
||||
efrt@2.7.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-/RInbCy1d4P6Zdfa+TMVsf/ufZVotat5hCw3QXmWtjU+3pFEOvOQ7ibo3aIxyCJw2leIeAMjmPj+1SLJiCpdrQ==,
|
||||
}
|
||||
engines: { node: ">=12.0.0" }
|
||||
|
||||
electron-to-chromium@1.5.389:
|
||||
resolution:
|
||||
{
|
||||
@@ -6114,6 +6131,13 @@ packages:
|
||||
integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==,
|
||||
}
|
||||
|
||||
grad-school@0.0.5:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-rXunEHF9M9EkMydTBux7+IryYXEZinRk6g8OBOGDBzo/qWJjhTxy86i5q7lQYpCLHN8Sqv1XX3OIOc7ka2gtvQ==,
|
||||
}
|
||||
engines: { node: ">=8.0.0" }
|
||||
|
||||
groq-sdk@1.3.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -8914,6 +8938,12 @@ packages:
|
||||
integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==,
|
||||
}
|
||||
|
||||
suffix-thumb@5.0.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-I5PWXAFKx3FYnI9a+dQMWNqTxoRt6vdBdb0O+BJ1sxXCWtSoQCusc13E58f+9p4MYx/qCnEMkD5jac6K2j3dgA==,
|
||||
}
|
||||
|
||||
supports-color@10.2.2:
|
||||
resolution:
|
||||
{
|
||||
@@ -12520,6 +12550,12 @@ snapshots:
|
||||
|
||||
common-ancestor-path@2.0.0: {}
|
||||
|
||||
compromise@14.16.0:
|
||||
dependencies:
|
||||
efrt: 2.7.0
|
||||
grad-school: 0.0.5
|
||||
suffix-thumb: 5.0.2
|
||||
|
||||
conf@10.2.0:
|
||||
dependencies:
|
||||
ajv: 8.20.0
|
||||
@@ -12898,6 +12934,8 @@ snapshots:
|
||||
|
||||
ee-first@1.1.1: {}
|
||||
|
||||
efrt@2.7.0: {}
|
||||
|
||||
electron-to-chromium@1.5.389: {}
|
||||
|
||||
emoji-regex@10.6.0: {}
|
||||
@@ -13379,6 +13417,8 @@ snapshots:
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
grad-school@0.0.5: {}
|
||||
|
||||
groq-sdk@1.3.0: {}
|
||||
|
||||
h3@1.15.11:
|
||||
@@ -15488,6 +15528,8 @@ snapshots:
|
||||
|
||||
stylis@4.4.0: {}
|
||||
|
||||
suffix-thumb@5.0.2: {}
|
||||
|
||||
supports-color@10.2.2: {}
|
||||
|
||||
svgo@4.0.1:
|
||||
|
||||
Reference in New Issue
Block a user