wiki/b-reference/platform-core/ai-agent.md

40 KiB
Raw Blame History

AI Agent & Knowledge Store

Überblick

Der AI-Agent ist der Service serviceAgent (AgentService) im ServiceCenter. Er orchestriert einen ReAct-Loop mit nativer Function Calling-Unterstützung: pro Runde ruft das Modell serviceAi auf; bei Tool-Calls werden Handler über die Tool Registry ausgeführt und Ergebnisse wieder in den Konversationskontext gegeben. Er ist die zentrale Schicht für den Workspace (Chat, Streaming, Tools, Dateien, externe Quellen).

Darunter liegt serviceAi als Low-Level-Gateway (Billing-Preflight, Provider-/Modellwahl, Neutralisierung vor Modellaufruf). serviceKnowledge liefert RAG: Indexierung extrahierter Inhalte, semantische Suche (pgvector) und buildAgentContext für kontextuelle Injektion vor jeder Agent-Runde.

Der AI-Core (platform-core/modules/aicore/) kapselt Provider-Plugins und die Modellwahl; der Agent nutzt ihn indirekt über serviceAi (u. a. OperationTypeEnum.AGENT, Embeddings für den Knowledge Store).


Agent Core

ServiceCenter-Integration

Registry-Key Klasse objectKey Dependencies (laut registry.py)
agent AgentService service.agent ai, chat, utils, extraction, billing, streaming, knowledge
knowledge KnowledgeService service.knowledge ai

Pro Request propagiert der ServiceCenterContext u. a. userId, mandateId, featureInstanceId und optional requireNeutralization (siehe Invarianten).

Ablauf (Ist-Code)

  • Einstieg: AgentService.runAgent (mainServiceAgent.py) — baut die Tool Registry, optional Anreicherung des Prompts mit Datei-Metadaten/FileContentIndex, startet runAgentLoop (agentLoop.py).
  • Loop: runAgentLoopConversationManager + Systemprompt; pro Runde: optional RAG via buildRagContextFn, Budget-Check, Progressive Summarization bei Bedarf (ConversationManager, Modell-Operation DATA_ANALYSE), dann AiCallRequest mit OperationTypeEnum.AGENT, messages, tools.
  • Tool Registry: registerCoreTools in coreTools/registerCore.py delegiert an domänenspezifische Module (_workspaceTools, _connectionTools, _dataSourceTools, _documentTools, _mediaTools, _featureSubAgentTools, _crossWorkflowTools); ActionToolAdapter registriert alle Workflow-Actions mit dynamicMode=True als zusätzliche Tools (method_action → intern executeAction).
  • Parallele Ausführung: In _executeToolCalls werden als readOnly=True markierte Tool-Calls parallel (asyncio.gather), schreibende/übrige sequentiell — vermeidet Race Conditions bei zustandsändernden Tools.
  • Budget: AgentConfig.maxCostCHF — vor jeder Runde Abgleich mit Workflow-Kosten; bei Überschreitung Status budgetExceeded und abschliessender Fortschritts-Text (FINAL).
  • Rundenlimit: AgentConfig.maxRounds (Default 25); bei Erreichen während running → Status maxRoundsReached und Fortschritts-Zusammenfassung.
  • Degradation: Schlägt RAG-Injektion fehl, wird nur geloggt (non-blocking); der Agent läuft ohne diesen Kontext weiter. Fehlgeschlagene RoundMemory-Persistenz ebenfalls non-blocking.
  • Streaming: Optional aiCallStreamFn — Chunks als CHUNK-Events; Abschluss u. a. AGENT_SUMMARY mit Kosten- und Round-Metriken; Trace kann über Knowledge-Entities persistiert werden (_persistTrace).

Konfiguration (datamodelAgent.AgentConfig, Ist)

Feld Bedeutung
maxRounds Max. Schleifenrunden (Default 25, 1100)
maxCostCHF Optionales Workflow-Budget in CHF
toolSet Aktives Tool-Set (Default "core")
temperature Optional für den Agent-AI-Call

System-Prompt: temporaler Kontext (Ist)

buildSystemPrompt (conversationManager.py) injiziert beim Bauen des System-Prompts einen Datums-/Zeitblock (_buildTemporalContext). Der Sub-Agent-Prompt in featureDataAgent._buildSchemaContext macht dasselbe. Ohne diesen Block halluzinieren LLMs das aktuelle Datum aus ihrem Training-Cutoff (typisch: »Juni 2025«), was bei relativen Zeit-Filtern (»letzten Monat«, »Q1«) sofort zu falschen SQL-Filtern und falschen Antworten führt.

Datenfluss der Zeitzone:

  1. Browser: Intl.DateTimeFormat().resolvedOptions().timeZone (z. B. "Europe/Zurich").
  2. Frontend: ui-nyla/src/api.ts Axios-Interceptor sendet den IANA-Namen als X-User-Timezone-Header.
  3. Gateway: _requestContextMiddleware (app.py) liest den Header und schreibt ihn via _setRequestTimezone in eine ContextVar.
  4. Konsumenten: getRequestNow() / getRequestTimezone() aus modules/shared/timeUtils.py liefern die Werte für den Prompt-Block (gleiche Pattern wie _setLanguage / _getLanguage).
  5. Fallback: Header fehlt oder ist ungültig → UTC. Niemals server-seitige Hardcoded-TZ.

Storage bleibt UTC (getUtcTimestamp, getIsoTimestamp). Nur user-sichtbare Now-Werte (Agent-Prompt, formatierte Display-Strings) gehen über getRequestNow().

Toolbox Registry

Datei: serviceCenter/services/serviceAgent/toolboxRegistry.py

Statt alle Tools flach zu exponieren, gruppiert die Toolbox Registry Tools in thematische Toolboxes. Der Agent startet schlank und kann Spezial-Toolboxes zur Laufzeit nachfordern.

Toolbox Anzahl Tools Default Requires Connection
core 20 Ja --
ai 9 Ja --
datasources 8 Ja --
email 5 Nein microsoft
sharepoint 3 Nein microsoft
clickup 3 Nein clickup
jira 3 Nein jira
workflow 9 Nein -- (featureCode: graphicalEditor)
trustee 1 Nein -- (featureCode: trustee)

Aktivierung (_activateToolboxes in mainServiceAgent.py):

  • Default-Toolboxes sind immer aktiv
  • Connection-abhaengige Toolboxes werden aktiviert, wenn der User eine passende Connection hat
  • Tools aus inaktiven Toolboxes werden aus der Registry entfernt
  • workflow-Tools werden explizit via workflowTools.getWorkflowToolDefinitions registriert

requestToolbox Meta-Tool:

  • Erlaubt dem Agent, zur Laufzeit inaktive Toolboxes anzufordern
  • Schema: { toolboxId: enum[inactive IDs], reason: string }
  • Handler in mainServiceAgent._registerRequestToolbox()
  • Nach erfolgreichem Aufruf refresht agentLoop.py die toolDefinitions fuer die naechste Runde
  • Nur Toolboxes, die aktuell nicht aktiv sind, erscheinen als Optionen

RBAC (Architekturentscheid)

Keine separate Tool-Level-RBAC: Zugriff wird über Datenbank-RBAC (z. B. File-Queries), OAuth/Connections bei externen Quellen und serviceAi (Billing, Provider-/Modell-RBAC) sowie can_access_service für service.agent durchgesetzt.


FeatureDataAgent: Query-Repair-Loop + Ontologie (ab 2026-05)

Der Feature Data Sub-Agent (platform-core/modules/serviceCenter/services/serviceAgent/featureDataAgent.py) ist die Spezialisten-Schicht hinter dem queryFeatureInstance-Tool. Er hat seinen eigenen ReAct-Loop und drei Tools (browseTable, queryTable, aggregateTable) gegen jede Feature-Datentabelle. Ab Mai 2026 verhindern zwei Schichten Halluzinationen deterministisch:

1. Pre-execute Validator (queryValidator.py)

QueryValidator validiert die Argumente jedes Tool-Calls vor der DB. Bei Fehlschlag liefert der Tool-Handler ToolResult(success=False, error=<short>, errorDetails={code, field, suggestion, hint}) -- der LLM erhält strukturierte Reparatur-Hinweise im nächsten ReAct-Turn und kann gezielt umlenken.

code Triggert bei LLM-Reaktion
FIELD_NOT_FOUND Feldname existiert nicht im Pydantic-Modell browseTable aufrufen, dann suggestion nehmen
OPERATOR_INCOMPATIBLE LIKE auf int-Feld, > auf string Op wechseln oder typkorrekten Wert filtern
INVALID_AGGREGATE_TARGET SUM/AVG auf *Balance/*Total (Convention oder Ontologie) queryTable mit Periode-Filter benutzen
ORDER_BY_INVALID orderBy zeigt auf unbekanntes Feld Feldname korrigieren
TYPE_MISMATCH SUM auf nicht-numerischem Feld Anderes Feld wählen

Verkabelung: runFeatureDataAgent ruft _buildValidatorForFeature(featureCode), das den Validator automatisch mit der OntologyDescriptor des Features verbindet (wenn vorhanden). Die Ontologie ersetzt die Convention-Defaults: NEVER_AGGREGATE-Constraints aus der Ontologie haben Vorrang vor dem *Balance/*Total-Suffix-Check.

2. Ontologie-Layer (Phase 2, Trustee-Pilot)

Statt freier _AGENT_DOMAIN_HINTS-Strings exportieren Features einen strukturierten OntologyDescriptor über getAgentOntology() -> OntologyDescriptor in ihrer mainXxx.py. Der Descriptor enthält:

  • entities: Semantische Konzepte (z. B. BankAccount spezialisiert Account, gebunden an TrusteeDataAccount mit accountNumber LIKE '102%'). Sub-Entitäten mit parentEntity machen Sub-Group-Filter explizit.
  • relations: FK-Beziehungen mit Cardinality (z. B. JournalLine -> JournalEntry (MANY_TO_ONE via journalEntryId)).
  • constraints: Maschinenlesbare Regeln (NEVER_AGGREGATE, REQUIRES_FILTER_ON, PREFERRED_TABLE_FOR_INTENT), die gleichzeitig vom Validator und vom Prompt-Compiler konsumiert werden -- Single Source of Truth.
  • canonicalPatterns: Tool-Call-Skelette für häufige Intents (BANK_BALANCE_AT_DATE, JOURNAL_SUM_AT_ACCOUNT, ...). Werden vom Compiler als worked examples in den Prompt geschrieben.

featureDataAgent._buildSchemaContext prüft bei jedem Sub-Agent-Run, ob das Feature getAgentOntology() exponiert (_loadFeatureOntologyBlock). Wenn ja, wird der Block via ontologyToPromptCompiler.compileOntologyToPrompt() deterministisch gerendert und an den Schema-Prompt angehängt. Sonst Fallback auf den Legacy-Pfad getAgentDomainHints(). Eval-only Override: POWERON_DISABLE_FEATURE_ONTOLOGY=1 erzwingt den Legacy-Pfad (siehe Eval-Harness).

3. Eval-Harness (Phase 1.5)

platform-core/tests/eval/runTrusteeBenchmark.py ist ein standalone Runner (kein pytest), der jede Frage des Goldstandards (tests/fixtures/trusteeBenchmark/questions.yaml, derzeit 19) gegen einen FakeFeatureDataProvider mit synthetischen aber realistischen Trustee-Daten fährt. Drei Modi werden parallel gemessen:

Mode Validator Ontologie Prompt-Block
baseline aus aus getAgentDomainHints() (Legacy)
phase1 an aus getAgentDomainHints() (Legacy) + Validator
phase2 an an compileOntologyToPrompt(...) + Validator

Pro Frage werden gemessen: patternOk (richtige Tool-Wahl + Filter), forbidOk (vermiedene Anti-Pattern), numericOk (Antwort enthält die erwarteten Zahlen), accuracyOk (alle drei). Aggregiert: repairConversionRate (erfolgreiche Repairs / Validator-Rejects), totalCostCHF, totalRounds. Output: Markdown-Bericht + JSON in local/notes/trustee-benchmark-<timestamp>.{md,json}.

Letzte Messung (2026-05-15, 19 Fragen × 3 Modi = 57 echte LLM-Runs):

Mode Accuracy Pattern compliance Validator rejects Rounds Cost (CHF)
baseline 89.5 % 89.5 % 0 40 0.0841
phase1 94.7 % 100 % 2 41 0.0851
phase2 100 % 100 % 0 39 0.0975

4. Repair-Telemetrie (AgentTrace)

AgentTrace (datamodelAgent.py) aggregiert pro Sub-Agent-Run drei neue Counter (gefüllt von agentLoop._computeRepairCounters am Ende des Loops):

  • validationFailures: Tool-Calls, die der Validator vor der DB abgelehnt hat.
  • repairAttempts: Wiederholungen desselben toolName in späteren Runden (nachgewiesener Repair-Versuch).
  • successAfterRepair: Repair-Attempts, die einen validationFailureCode=None-Result lieferten.

Jeder ToolCallLog trägt den validationFailureCode (z. B. INVALID_AGGREGATE_TARGET) für Pro-Call-Analyse. Der AGENT_SUMMARY-Event am Loop-Ende exposed die aggregierten Counter ans Frontend / Persistierung.

Agent Tools

Prinzip

Tools sind registrierte Handler mit JSON-Schema für Argumente, readOnly-Flag (Parallelisierung) und optional toolSet. Tool-Ausgaben sind Rohdaten für das Modell; Neutralisierung vor dem LLM erfolgt zentral in serviceAi, nicht in den Tools.

Zusätzlich zu den unten genannten Kern-Tools existieren dynamische Tools aus dem Workflow-System: ActionToolAdapter liest methodDiscovery.methods und registriert jede Action mit dynamicMode=True unter einem zusammengesetzten Namen ({methodShort}_{actionName}); im Adapter sind diese derzeit alle als nicht-readOnly registriert.

Tool-Generierung aus dem Catalog (Typed Action Architecture, 2026-04): ActionToolAdapter leitet das JSON-Schema fuer jeden Tool-Aufruf direkt aus der typisierten Action-Signatur ab (Pflicht-Felder, Typen, Default-Werte, FeatureInstanceRef-Discriminator). Es gibt keine handgepflegte Heuristik mehr -- Editor, AI-Agent und Adapter konsumieren denselben Catalog (/api/automation2/catalog). Aenderungen an einer Action-Signatur schlagen automatisch auf das Tool-Schema durch; veraltete oder gedriftete Felder werden vom Snapshot-Test test_staticNodesHaveNoDriftAgainstLiveMethods blockiert (_KNOWN_ADAPTER_DRIFTS = frozenset()). Details: wiki/c-work/3-validate/2026-04-typed-action-architecture.md.

Toolbox-Zuordnung: Kern-Tools sind den Toolboxes core, ai und datasources zugeordnet (siehe Toolbox Registry oben). Connection-abhaengige Tools (email, sharepoint, clickup, jira) werden nur aktiviert, wenn der User eine passende Connection hat. Workflow-Editing-Tools (workflow Toolbox) werden separat via workflowTools.py registriert.

Kern-Tools (registriert via registerCoreToolscoreTools/; Stand 2026-04 inkl. UDM-Helfer)

Workspace / Dateien

Tool Kurzbeschreibung
readFile Dateiinhalt lesen (optional zeilenweise)
listFiles Dateien filtern (Ordner, Tags, Suche)
searchInFileContent Suche im Dateiinhalt
writeFile Datei anlegen / anhängen / überschreiben
deleteFile Datei löschen
renameFile Umbenennen
moveFile Verschieben (Ordner)
tagFile Tags setzen
copyFile Unabhängige Kopie
replaceInFile Ersetzen im Text

Ordner

Tool Kurzbeschreibung
listFolders Ordner auflisten
createFolder Ordner anlegen
deleteFolder Ordner löschen (optional rekursiv)
renameFolder Ordner umbenennen
moveFolder Ordner verschieben

Web & Sprache

Tool Kurzbeschreibung
webSearch Websuche
readUrl Inhalt einer bekannten URL laden
translateText Übersetzung (Voice/Translation-Pipeline)
textToSpeech TTS
speechToText Transkription Audio-Datei (Gateway: VoiceObjects.speechToText; optionale Connector-Parameter model/lightweight/audioFormat — Agent-Tool nutzt Defaults)
detectLanguage Spracherkennung für Text

Externe Datenquellen / Mail

Tool Kurzbeschreibung
listConnections User-Connections auflisten
browseDataSource Verzeichnis/Ordner einer Quelle auflisten (oder neueste Items eines Mail-/Kalender-Ordners)
searchDataSource Primäres Tool für gezielte Abfragen — Query läuft server-seitig in der Quelle
downloadFromDataSource Download → FileItem (inkl. Vererbung neutralize auf Datei, siehe Invarianten)
uploadToExternal Upload zu externer Quelle
sendMail E-Mail senden

Search-first-Strategie & Adapter-Effizienz (ab 2026-06)

Externe Quellen können gigabyte- bis terabytegross sein. Damit der Agent sie nicht wie ein lokales Dateisystem behandelt (alles browsen + alles herunterladen), gelten drei Schichten:

  1. Agent-Steuerung (Prompt + Tool-Beschreibungen): buildDataSourceContext (routeFeatureWorkspace.py) injiziert eine Search-first-Regel. Die Tool-Beschreibungen in _dataSourceTools.py deklarieren searchDataSource als primär (inkl. Per-Service-Query-Syntax) und browseDataSource als reines Directory-Listing. So lädt der Agent gezielt statt massenhaft.

  2. Server-seitige Suche + Scoping/Pagination pro Adapter: Jeder ServiceAdapter.search nutzt die native Such-API der Quelle und scopt auf den angebundenen Ordner/Label; grosse Ergebnismengen werden paginiert.

    Service Query-Syntax / Mechanik Scoping Pagination
    Outlook Mail Graph $search (KQL: from:, subject:); browse mit Datumsbereich → $filter receivedDateTime Mail-Ordner $top
    Gmail q= (from:, after:/before:); Metadaten parallel aufgelöst (kein sequenzielles N+1) labelIds maxResults
    SharePoint / OneDrive Graph drive/root:/<folder>:/search(q=) (Name + Inhalt) angebundener Ordner @odata.nextLink
    Google Drive fullText contains (Name + Inhalt) '<folderId>' in parents pageToken
    MSFT / Google Calendar Datumsbereich → calendarView bzw. timeMin/timeMax Kalender $top / maxResults
    Infomaniak Calendar Datumsbereich-Filter (auf Vendor-Limit <3 Monate geclampt) statt fixem 90-Tage-Fenster Kalender --
    ClickUp searchTeamTasks Team API-Page
  3. Strukturierte Metadaten inline (_dataSourceTools.py-Formatter): Damit der Agent ohne Download entscheiden/antworten kann, rendern die Tool-Outputs pro Service-Typ die relevanten Felder inline:

    Typ Inline-Felder Hinweis im Output
    Mail (✉️) Datum, Absender "nur Betreff — Download für Volltext"
    Kalender (📅) Start, Ende, Ort "keine Einzel-Events downloaden"
    Kontakte (👤) E-Mail, Telefon, Firma "vCard nur bei Bedarf downloaden"
    ClickUp-Tasks (☑️) Status, Assignee, Fälligkeit "Task-JSON nur für Detail downloaden"

    Die Felder kommen aus ExternalEntry.metadata; der Formatter normalisiert die je Provider unterschiedlichen Keys (z. B. Kontakt-E-Mail: MSFT emailAddresses, Google emails, Infomaniak email).

Dokumente / Content-Objekte

Tool Kurzbeschreibung
browseContainer Struktur-Index (Seiten, Abschnitte, …)
readContentObjects Gezielt Content-Objekte lesen
extractContainerItem Element aus Container extrahieren
summarizeContent KI-Zusammenfassung
getUdmStructure UDM-JSON: Überblick (Knoten, Struktur, Block-Zahlen); udmJson als stringifiziertes Objekt
walkUdmBlocks UDM traversieren: alle ContentBlock-Knoten mit Pfad und Kurz-Preview
filterUdmByType UDM: alle Blöcke mit gegebenem contentType (z.B. table, image)
describeImage Vision-Analyse
renderDocument Dokument rendern
generateImage Bildgenerierung
createChart Chart erzeugen

Feature / Workflow

Tool Kurzbeschreibung
queryFeatureInstance Abfrage anderer Feature-Instanz (setzt bei Bedarf requireNeutralization für Sub-Calls). Sub-Agent hat browseTable, queryTable und aggregateTable (SUM/COUNT/AVG/MIN/MAX mit GROUP BY und filters). DB-Connection-Pooling und Result-Caching (5 Min TTL). System-Prompt wird aus drei Schichten gebaut: (1) generischer Header mit Tabellen + Pydantic-Schema (_buildSchemaContext), (2) generische Regeln inkl. kein SUM/AVG auf bereits aggregierten Saldo-/Total-Feldern (closingBalance, openingBalance, debitTotal, creditTotal, …), (3) Domain-Block: bevorzugt aus getAgentOntology() -> OntologyDescriptor via ontologyToPromptCompiler.compileOntologyToPrompt() (deterministisch, single source of truth für Prompt + Validator); Fallback auf getAgentDomainHints() -> str für Features ohne Ontologie. Round-/Cost-Budget wird vom Parent-Agent geerbt (AgentConfig.maxRounds → Tool-Context → runFeatureDataAgent). Pre-execute Validator (QueryValidator, mit Ontologie verkabelt via _buildValidatorForFeature) fängt vier Halluzinations-Klassen deterministisch ab und gibt strukturierte Repair-Hints zurück: siehe Abschnitt »FeatureDataAgent: Query-Repair-Loop + Ontologie« unten.
listWorkflowHistory Workflow-Historie
readWorkflowMessages Nachrichten eines Workflows lesen

Sicherheit / Ausführung

Tool Kurzbeschreibung
neutralizeData Anonymisierter Text (non-destructive)
executeCode Sandboxed Code-Ausführung (siehe sandboxExecutor.py)

AI-Core (Provider-Abstraction)

Komponente Datei / Rolle
Model Registry aicoreModelRegistry.py — registriert Provider-Plugins und Modelle
Model Selector aicoreModelSelector.py — Auswahl nach Operationstyp, Promptgrösse, Restriktionen, Ranking

Provider-Plugins (Dateien unter platform-core/modules/aicore/)

Plugin-Modul Typische Rolle
aicorePluginAnthropic.py Claude-Modelle
aicorePluginOpenai.py GPT, Embeddings, Bild — modellabhaengiges Payload-Tuning: GPT-5.x und o-Serie (o1/o3/o4) sind Reasoning-Modelle und akzeptieren weder max_tokens (-> immer max_completion_tokens) noch ein custom temperature (-> Feld bei diesen Modellen weggelassen, OpenAI erzwingt sonst HTTP 400 unsupported_value)
aicorePluginMistral.py Mistral Chat / Embed
aicorePluginPerplexity.py Sonar / Recherche
aicorePluginTavily.py Web-Suche
aicorePluginPrivateLlm.py Private LLM
aicorePluginInternal.py Interne Extraktion/Generierung/Rendering

Operation Types (datamodelAi.OperationTypeEnum, Auszug)

u. a. plan, dataAnalyse, dataGenerate, dataExtract, imageAnalyse, imageGenerate, neutralizationText, neutralizationImage, webSearch, webCrawl, agent, embedding, speechTeams.

Der Agent-Loop verwendet AGENT für Hauptrunden und DATA_ANALYSE für Summarization-Calls; Embeddings laufen über die Knowledge-Service-Pipeline (callEmbedding).


Knowledge Store (RAG)

Datenmodelle (datamodelKnowledge.py)

Modell Zweck
FileContentIndex Strukturindex pro Datei (ohne LLM): structure, objectSummary, status, Spiegelung von Mandat/Instanz über scope (personal, featureInstance, mandate, global), Neutralisierungs-Felder isNeutralized, neutralizationStatus
ContentChunk Persistente Chunks mit Embedding (vector(1536)), data, contextRef, contentType
RoundMemory Pro Runde: file_ref, Tool-Ergebnisse, Entscheidungen — mit Embedding für semantische Wiederverwendung trotz ConversationManager-Kürzung
WorkflowMemory Workflow-scoped Key-Value-Cache (Entities, Fakten, inkl. optional Embedding)

Zugriff über interfaceDbKnowledge (FileContentIndex, ContentChunk, RoundMemory, WorkflowMemory).

Indexierung

KnowledgeService.indexFile — nach Extraktion (Content-Objekte): übernimmt Scope aus FileItem als Single Source of Truth; bei FileItem.neutralize=True werden Inhalte vor dem Speichern neutralisiert; Chunking (u. a. DEFAULT_CHUNK_TOKENS / Zeichen-Heuristik), Embedding via AI-Service, Persistenz von Index + Chunks; optional Billing-Reconciliation für Mandats-Speicher.

Semantische Suche & Kontext

buildAgentContext — priorisierte Schichten (Ist-Code, vereinfacht):

  1. RoundMemory file_ref („Known Files“)
  2. Instance/personal/mandate/globalsemanticSearch mit Query-Embedding (Limit/Score wie im Code)
  3. RoundMemory semantisch (semanticSearchRoundMemory)
  4. Workflow-Entities (getWorkflowEntities)
  5. Mandate-Scope — geteilte Mandats-Dokumente („Shared Knowledge“)
  6. Optional Cross-Workflow-Hints (workflowHintItems)

Rückgabe: formatierter String für Injektion in den Agent-Systemkontext. Wenn Embedding fehlschlägt, liefert buildAgentContext einen leeren String (Agent arbeitet ohne diesen RAG-Block).

Erweiterte Hilfen (z. B. readSection, Caching) für selektives Lesen sind im selben Service dokumentiert.

Volle Architektur für transparente, datenzentrierte Steuerung der RAG-Indexierung durch den Benutzer.

Prinzipien

  1. Datenquelle ist die Single Source of TruthDataSource.ragIndexEnabled steuert, ob ein Baum-Element indexiert wird. Vererbung entlang der Baumstruktur (wie scope und neutralize).
  2. Kein Wizard-Seiteneffekt ohne UI-Revoke — Jede Einstellung die im AddConnectionWizard gesetzt wird, ist in der UDB (Unified Data Bar) sicht- und revertbar.
  3. Konsent-Entzug = sofortige Purge — Deaktivierung von ragIndexEnabled oder knowledgeIngestionEnabled löscht zugehörige Chunks synchron.

Datenmodell-Erweiterungen

Modell Feld Zweck
DataSource ragIndexEnabled: bool Pro Baum-Element: Auto-Indexierung an/aus
DataSource lastIndexed: Optional[float] Zeitstempel des letzten erfolgreichen Index-Laufs
UserConnection knowledgeIngestionEnabled: bool Globaler Konsent pro Verbindung

Walker-Architektur

Jeder Walker (subConnectorSync*.py) iteriert über ragIndexEnabled=True DataSources:

  • Empfängt dataSources: List[Dict] und progressCb: JobProgressCallback
  • Prüft progressCb.isCancelled() periodisch für graceful Abort
  • Enthält dataSourceId in der Provenance jedes IngestionJob
  • Nutzt per-DataSource neutralize-Policy (aus subPolicyResolver)

Job-Cancellation

mainBackgroundJobService.py bietet:

  • cancelJob(jobId) — setzt Status auf CANCELLED
  • cancelJobsByConnection(connectionId) — bulk-cancel aller laufenden Jobs
  • JobProgressCallback.isCancelled() — kooperativer Check mit 3s-Cache

Zombie-Job-Recovery & Walker-Timeouts (ab 2026-05-14)

Drei-stufige Absicherung gegen hängende Bootstraps:

  1. Boot-Recovery: recoverInterruptedJobs() markiert beim Worker-Start RUNNING-Jobs als ERROR (kein Auto-Requeue, sonst Endlosschleife).
  2. Live-Killer: Cron background_jobs.zombie_killer ruft alle 5 min killZombieJobs(maxAgeSeconds=1800) auf — RUNNING-Jobs ohne Progress-Update >30 min werden als ERROR markiert (nutzt startedAt aus dem DB-Record).
  3. Walker-Timeouts (subWalkerHelpers.py): jeder Walker wickelt seine drei Hot-Spots in asyncio.wait_for:
Helper Timeout Zweck
downloadWithTimeout 60s Adapter-Download (SharePoint/Drive/kDrive/Outlook-Attachment)
extractWithTimeout 90s runExtraction auf Worker-Thread (sync Extraktor blockiert sonst Event-Loop)
ingestWithTimeout 60s KnowledgeService.requestIngestion (Embedding-API)

Vor jedem Item ruft der Walker logItemStart(service, path, sizeBytes, mime) — das letzte solche Log vor einem Hang oder Timeout benennt das verursachende Item exakt (Pfad, Grösse, MIME). Sync-Extraktion läuft via asyncio.to_thread auf einem Worker-Thread; wait_for schützt nur den Awaiter, der Thread selbst kann weiterlaufen, aber der Walker geht zum nächsten Item.

API-Endpunkte

Methode Pfad Zweck
PATCH /api/datasources/{id}/rag-index Toggle ragIndexEnabled (Trigger mini-bootstrap oder Purge)
PATCH /api/connections/{id}/knowledge-consent Globaler Konsent-Toggle
POST /api/connections/{id}/knowledge-stop Alle laufenden Jobs stoppen
GET /api/rag/inventory/me Persönliche RAG-Übersicht
GET /api/rag/inventory/mandate Mandant-Aggregation (Admin)
GET /api/rag/inventory/platform Plattform-Statistik (SysAdmin)
GET /api/rag/inventory/jobs Aktive RAG-Jobs des Users

Frontend-Komponenten

Komponente Ort Funktion
UDB SourcesTab 4. Toggle (🧠) ragIndexEnabled pro Tree-Element
AddConnectionWizard Connector-aware Steps MS Admin Consent + Infomaniak PAT integriert
RagInventoryPage Start > Nutzung > RAG Globale Übersicht & Steuerung
RagRunningBadge MainLayout (fixed) Floating-Badge bei aktiven Jobs

Vererbung (Policy Resolver)

subPolicyResolver.py implementiert Longest-Prefix-Matching:

  • Eltern-DataSource mit explizitem ragIndexEnabled vererbt an Kind-Pfade
  • Gleiches Pattern wie neutralize und scope

Konfigurierbare RAG-Limits (ab 2026-05-17)

Walker-Limits (maxBytes, maxFileSize, maxItems, maxDepth für File-Walker; maxTasks, maxWorkspaces, maxListsPerWorkspace für ClickUp) sind nicht mehr in den Walker-Modulen hartkodiert, sondern aus zwei Quellen zusammengesetzt:

  1. Zentraler Defaultmodules/serviceCenter/services/serviceKnowledge/_ragLimits.py (FILES_LIMITS_DEFAULT, CLICKUP_LIMITS_DEFAULT). Die alten MAX_*_DEFAULT-Konstanten in den Walkern sind dünne Aliase und bleiben für Rückwärtskompatibilität bestehen.
  2. DataSource-OverrideDataSource.settings.ragLimits.<key> (oder FeatureDataSource.settings). JSONB-Spalte, optional, vollständig vom User editierbar.

Semantik (kritisch, weil leicht zu missverstehen):

  • _ragLimits.getStoredOverrides(ds, kind) liefert NUR die explizit gesetzten Overrides → Walker mergen sie auf den caller-supplied limits=-Parameter (Test-Override gewinnt weiterhin).
  • _ragLimits.getRagLimits(ds, kind) mergt Overrides auf die globalen Defaults → API/Cost-Estimate-Pfad.
  • Keine Override-Schicht, kein Resolver, keine Vererbung für ragLimits. Was im Settings-Modal steht, ist exakt das, was der Walker liest.

Settings-API:

Methode Pfad Zweck
PATCH /api/datasources/{id}/settings Partial-Update auf DataSource.settings/FeatureDataSource.settings. Nur Top-Level-Key ragLimits akzeptiert; unknown keys → 400. Audit-Log: AuditCategory.PERMISSION/datasource_settings_changed. Owner-only (Personal); für Mandate-Scope auch Mandate-Admin.
GET /api/datasources/{id}/cost-estimate Indikative CHF-Schätzung für einen Voll-Sync mit den aktuellen Limits. Antwort: {estimatedTokens, estimatedChf, basis: {kind, limits, assumptions, notes}, sourceId}. Default-Heuristik: text-embedding-3-small @ 0.02 CHF / 1M Token (Projekt-Konvention: Anbieter-Listenpreise werden direkt als CHF behandelt, siehe calculatepriceCHF in aicorePluginOpenai.py), BYTES_PER_TOKEN=4, EXTRACTABLE_FRACTION=0.4. Quelle: _costEstimate.py.

UDB Settings-Modal (DataSourceSettingsModal.tsx): einziges UI für DataSource-Settings, geöffnet via ⚙️-Icon pro Tree-Node im SourcesTab. Drei Sektionen:

  1. ConnectionknowledgeIngestionEnabled Master-Toggle (= patchKnowledgeConsent-Pfad).
  2. DataSource RAG-Limits — Editierbare Felder; Bytes-Limits in MB im UI, in Bytes am Backend.
  3. Kostenschätzung — Indikativ, nicht-verbindlich, ändert sich live nach PATCH /settings.

Das gleiche Modal wird auf der RagInventoryPage aus dem Partial-Banner (stoppedAtLimit) via "Limit anpassen"-Button geöffnet → User hat direkten Pfad vom Symptom zur Behebung.


Teamsbot-Integration (Hybrid-Routing, kein eigenes Toolset)

Der Teamsbot ruft den selben AgentService.runAgent über das ServiceCenter auf — es gibt kein Teamsbot-spezifisches Toolset. Aufrufer ist platform-core/modules/features/teamsbot/service.py::_runAgentForMeeting mit AgentConfig(maxRounds=5, maxCostCHF=0.10, toolSet="core", initialToolboxes=["core","web"], excludeActionTools=True).

Trigger Pfad Wer ruft runAgent?
Operator schickt Director Prompt (One-Shot oder Persistent) via Regie-Panel routeFeatureTeamsbot.submitDirectorPromptservice.submitDirectorPromptasyncio.create_task(_processDirectorPrompt)_runAgentForMeeting direkt, umgeht SPEECH_TEAMS
SPEECH_TEAMS setzt needsAgent=true + agentReason (z. B. „recherchier das im Internet") service._analyzeAndRespond erkennt needsAgent, ruft _runAgentForMeeting mit taskBrief = agentReason Eskalation aus dem schnellen Pfad

FINAL-Delivery: Der FINAL-Event-Text wird im Teamsbot-Service über die bestehenden Kanäle (TTS + sendChatMessage) ins Meeting gespielt — der Agent „spricht" nicht selbst, sondern liefert nur den Text. Damit braucht es kein Teamsbot-spezifisches Tool wie sendChat oder readAloud im Agent.

Workflow-ID-Konvention: workflowId = f"teamsbot:{sessionId}" — RoundMemory und RAG akkumulieren pro Meeting, getrennt von anderen Sessions.

Persistente Direktiven: service._buildPersistentDirectorContext rendert aktive persistent-Direktiven als OPERATOR_DIRECTIVES-Block in den SPEECH_TEAMS-Kontext, damit sie auch ohne erneuten Director-Prompt-Aufruf wirken (z. B. „Antworte immer in Englisch").

Siehe b-reference/teams-bot/architecture.md für die vollständige Hybrid-Architektur und Director-Prompt-Lifecycle.


Schlüssel-Dateien

Datei Rolle
platform-core/modules/serviceCenter/registry.py Registrierung agent, knowledge, Dependencies, objectKey
platform-core/modules/serviceCenter/services/serviceAgent/mainServiceAgent.py AgentService, runAgent, Prompt-Enrichment, Registry-Orchestrierung
platform-core/modules/serviceCenter/services/serviceAgent/coreTools/registerCore.py Orchestrator: delegiert Tool-Registrierung an Domänen-Module
platform-core/modules/serviceCenter/services/serviceAgent/coreTools/_workspaceTools.py Dateien, Ordner, Web, Übersetzung
platform-core/modules/serviceCenter/services/serviceAgent/coreTools/_connectionTools.py Externe Connections, Upload, Mail
platform-core/modules/serviceCenter/services/serviceAgent/coreTools/_dataSourceTools.py DataSource Browse/Search/Download
platform-core/modules/serviceCenter/services/serviceAgent/coreTools/_documentTools.py Container, Content-Objects, Vision
platform-core/modules/serviceCenter/services/serviceAgent/coreTools/_mediaTools.py Rendering, TTS, STT, Bildgenerierung, Charts, Neutralize, Code
platform-core/modules/serviceCenter/services/serviceAgent/coreTools/_featureSubAgentTools.py Feature Data Sub-Agent (queryFeatureInstance)
platform-core/modules/serviceCenter/services/serviceAgent/coreTools/_crossWorkflowTools.py Workflow-Historie, Messages, _CORE_ONLY_TOOLS-Tagging
platform-core/modules/serviceCenter/services/serviceAgent/agentLoop.py ReAct-Loop, Budget, RAG-Injektion, Tool-Dispatch, Summaries
platform-core/modules/serviceCenter/services/serviceAgent/toolRegistry.py Registrierung, Dispatch, readOnly, Function-Calling-Format
platform-core/modules/serviceCenter/services/serviceAgent/conversationManager.py Kontextfenster, Summarization, Systemprompt
platform-core/modules/serviceCenter/services/serviceAgent/datamodelAgent.py AgentConfig, Events, Trace-Modelle
platform-core/modules/serviceCenter/services/serviceAgent/actionToolAdapter.py Workflow-Actions → Agent-Tools (Schema-Generierung; Param-Validierung erfolgt zentral im ActionExecutor)
platform-core/modules/workflows/processing/shared/parameterValidation.py Universelle Action-Parameter-Validierung + Coercion (Required-Enforcement, Ref-Schema → id-String, Primitive-Coercion); aufgerufen aus ActionExecutor.executeAction für alle Aufrufpfade (Agent, Workflow-Graph, REST)
platform-core/modules/connectors/connectorDbPostgre.py DB-Connector; Read-Methoden raisen DatabaseQueryError bei echten Query-Fehlern (Postgres-Adapt, UndefinedTable/Column, OperationalError, …); Empty Result Sets bleiben []/None
platform-core/modules/serviceCenter/services/serviceAgent/toolboxRegistry.py Toolbox-Definitionen, requestToolbox Meta-Tool-Schema
platform-core/modules/serviceCenter/services/serviceAgent/workflowTools.py Workflow-Editing-Tools (readWorkflowGraph, addNode, ...)
platform-core/modules/serviceCenter/services/serviceAgent/sandboxExecutor.py executeCode-Sandbox
platform-core/modules/serviceCenter/services/serviceKnowledge/mainServiceKnowledge.py Index, buildAgentContext, Workflow-/Round-Memory-Helfer
platform-core/modules/interfaces/interfaceDbKnowledge.py DB-Zugriff Knowledge / RAG
platform-core/modules/datamodels/datamodelKnowledge.py FileContentIndex, ContentChunk, RoundMemory, WorkflowMemory
platform-core/modules/serviceCenter/services/serviceAi/mainServiceAi.py Zentrales Neutralisierungs-Gate vor LLM, Billing, Provider-Aufruf
platform-core/modules/aicore/aicoreModelRegistry.py Provider-/Modell-Registry
platform-core/modules/aicore/aicoreModelSelector.py Modellauswahl
platform-core/modules/aicore/aicorePlugin*.py Provider-Implementierungen
platform-core/modules/datamodels/datamodelAi.py OperationTypeEnum, AiCallRequest, Optionen

Regeln / Invarianten

  1. Neutralisierung vor dem Modell: Alle an ein LLM gehenden Inhalte (Prompt, Kontext, Messages) werden ausschliesslich in serviceAi entsprechend Policy neutralisiert bzw. blockiert — nicht in den Agent-Tools.
  2. Tools liefern Rohdaten: Kein zweites Neutralisieren in Tools; Ausnahme ist bewusste Data-at-rest-Neutralisierung beim Indexieren (indexFile bei FileItem.neutralize=True).
  3. DataSource → FileItem: _downloadFromDataSource vererbt das neutralize-Flag der DataSource auf erzeugte Dateien; queryFeatureInstance kann Sub-Agent-Calls mit requireNeutralization=True auslösen, wenn die Feature-Datenquelle das vorsieht.
  4. Kein Tool-RBAC: Autorisierung über Datenbank-, Connection- und Service-Schicht.
  5. Parallele Tools nur bei readOnly=True: Schreibende Tools nicht parallel zueinander aus demselben Batch.
  6. RAG optional: Embedding- oder DB-Fehler im Kontextpfad führen nicht zwingend zum Abbruch des Agent-Runs (RAG wird übersprungen/entfällt).
  7. Billing: AI-Calls und eingebettete Pfade (Embeddings, Summaries) laufen über bestehende Billing-/Subscription-Checks in serviceAi bzw. aufrufenden Schichten.

Detaillierte Neutralisierungs-Kette und Compliance: wiki/compliance/Neutralisierung.md.

Architektur-Roadmap (Unified Workspace, ProviderConnector 1:n, Phasenplan): wiki/concepts/AI-Agent-Architecture-Konzept.md — als Zielbild lesen, nicht als Ist-Abbild aller Codepfade.