Technische Architektur
Agentic Loop & Tool Execution
Technische Implementierung des ReAct Tool-Calling Loops und der Directus Tool-Ausführung
Agentic Loop & Tool Execution
Der autonome Agenten-Loop wird im AgenticChatService abgebildet. Er implementiert das ReAct-Pattern (Reasoning + Acting) über OpenAI-kompatibles Function Calling.
Ablauf des Iterations-Zyklus
sequenceDiagram
autonumber
participant App as Fastify Route
participant Service as AgenticChatService
participant LLM as LiteLLM (Gemini)
participant Directus as Directus SDK
App->>Service: chat({ bookId, messages, maxIterations: 6 })
Service->>Directus: readItem('books', bookId)
Service->>Service: Inject Universe System-Prompt & SeriesId
loop Max Iterations (1..6)
Service->>LLM: chat.completions.create(messages, tools)
alt Modell erzeugt tool_calls
LLM-->>Service: assistantMessage with tool_calls: [fn1, fn2]
loop For each toolCall
Service->>Directus: executeTool(name, args, seriesId)
Directus-->>Service: Result / Record
Service->>Service: Append message { role: 'tool', content: json }
end
else Modell liefert finalen Text (no tool_calls)
LLM-->>Service: assistantMessage with content
Service->>App: Return { reply, toolCallsExecuted, thoughtProcess }
end
endDie registrierten Werkzeuge (tools)
Der Agent erhält in jeder Iteration 6 vordefinierte JSON-Schema-Werkzeuge:
// Auszug aus AgenticChatService.getToolDefinitions()
[
{
type: 'function',
function: {
name: 'search_lore',
description: 'Search for canon lore entities in the universe of the current book.',
parameters: {
type: 'object',
properties: {
query: { type: 'string' },
type: { type: 'string', enum: ['character', 'location', 'faction', 'object', 'rule'] }
},
required: ['query']
}
}
},
{
type: 'function',
function: {
name: 'get_entity_state',
description: 'Get static traits and latest active bitemporal state patches for an entity.',
parameters: {
type: 'object',
properties: { entityId: { type: 'string' } },
required: ['entityId']
}
}
},
{
type: 'function',
function: {
name: 'record_lore_state',
description: 'Record a new bitemporal state patch for an entity.',
parameters: {
type: 'object',
properties: {
entityId: { type: 'string' },
statePatch: { type: 'object' },
chapterId: { type: 'string' },
sourceSceneId: { type: 'string' }
},
required: ['entityId', 'statePatch']
}
}
},
{
type: 'function',
function: {
name: 'create_lore_entity',
description: 'Create a new canon entity in the book series universe.',
parameters: {
type: 'object',
properties: {
type: { type: 'string', enum: ['character', 'location', 'faction', 'object', 'rule'] },
name: { type: 'string' },
staticTraits: { type: 'object' }
},
required: ['type', 'name', 'staticTraits']
}
}
},
{
type: 'function',
function: {
name: 'fetch_chapter_scenes',
description: 'Read previous scenes in a chapter to maintain continuity.',
parameters: {
type: 'object',
properties: { chapterId: { type: 'string' } },
required: ['chapterId']
}
}
},
{
type: 'function',
function: {
name: 'persist_scene',
description: 'Save or update a generated narrative scene in Directus.',
parameters: {
type: 'object',
properties: {
chapterId: { type: 'string' },
sceneNumber: { type: 'integer' },
title: { type: 'string' },
rawText: { type: 'string' }
},
required: ['chapterId', 'sceneNumber', 'title', 'rawText']
}
}
}
]Universums-Scoping: Bei allen Abfragen (search_lore, create_lore_entity) filtert der Service serverseitig strikt auf series_id = book.series_id. Dadurch ist es unmöglich, dass Entitäten aus einem anderen Universum in die Konversation gelangen.