LoreKeeper
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
    end

Die 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.

On this page