36 KiB
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 (gateway/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, startetrunAgentLoop(agentLoop.py). - Loop:
runAgentLoop—ConversationManager+ Systemprompt; pro Runde: optional RAG viabuildRagContextFn, Budget-Check, Progressive Summarization bei Bedarf (ConversationManager, Modell-OperationDATA_ANALYSE), dannAiCallRequestmitOperationTypeEnum.AGENT,messages,tools. - Tool Registry:
registerCoreToolsincoreTools/registerCore.pydelegiert an domänenspezifische Module (_workspaceTools,_connectionTools,_dataSourceTools,_documentTools,_mediaTools,_featureSubAgentTools,_crossWorkflowTools);ActionToolAdapterregistriert alle Workflow-Actions mitdynamicMode=Trueals zusätzliche Tools (method_action→ internexecuteAction). - Parallele Ausführung: In
_executeToolCallswerden alsreadOnly=Truemarkierte 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 StatusbudgetExceededund abschliessender Fortschritts-Text (FINAL). - Rundenlimit:
AgentConfig.maxRounds(Default 25); bei Erreichen währendrunning→ StatusmaxRoundsReachedund 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 alsCHUNK-Events; Abschluss u. a.AGENT_SUMMARYmit Kosten- und Round-Metriken; Trace kann über Knowledge-Entities persistiert werden (_persistTrace).
Konfiguration (datamodelAgent.AgentConfig, Ist)
| Feld | Bedeutung |
|---|---|
maxRounds |
Max. Schleifenrunden (Default 25, 1–100) |
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:
- Browser:
Intl.DateTimeFormat().resolvedOptions().timeZone(z. B."Europe/Zurich"). - Frontend:
frontend_nyla/src/api.tsAxios-Interceptor sendet den IANA-Namen alsX-User-Timezone-Header. - Gateway:
_requestContextMiddleware(app.py) liest den Header und schreibt ihn via_setRequestTimezonein eineContextVar. - Konsumenten:
getRequestNow()/getRequestTimezone()ausmodules/shared/timeUtils.pyliefern die Werte für den Prompt-Block (gleiche Pattern wie_setLanguage/_getLanguage). - 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 viaworkflowTools.getWorkflowToolDefinitionsregistriert
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.pydietoolDefinitionsfuer 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 (gateway/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.BankAccountspezialisiertAccount, gebunden anTrusteeDataAccountmitaccountNumber LIKE '102%'). Sub-Entitäten mitparentEntitymachen Sub-Group-Filter explizit.relations: FK-Beziehungen mitCardinality(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)
gateway/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 desselbentoolNamein späteren Runden (nachgewiesener Repair-Versuch).successAfterRepair: Repair-Attempts, die einenvalidationFailureCode=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):
ActionToolAdapterleitet 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-Testtest_staticNodesHaveNoDriftAgainstLiveMethodsblockiert (_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 registerCoreTools → coreTools/; 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 |
Externe Quelle durchsuchen |
searchDataSource |
Suche in Datenquelle |
downloadFromDataSource |
Download → FileItem (inkl. Vererbung neutralize auf Datei, siehe Invarianten) |
uploadToExternal |
Upload zu externer Quelle |
sendMail |
E-Mail senden |
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 gateway/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):
- RoundMemory
file_ref(„Known Files“) - Instance/personal/mandate/global —
semanticSearchmit Query-Embedding (Limit/Score wie im Code) - RoundMemory semantisch (
semanticSearchRoundMemory) - Workflow-Entities (
getWorkflowEntities) - Mandate-Scope — geteilte Mandats-Dokumente („Shared Knowledge“)
- 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.
RAG Consent & Control (ab 2026-05)
Volle Architektur für transparente, datenzentrierte Steuerung der RAG-Indexierung durch den Benutzer.
Prinzipien
- Datenquelle ist die Single Source of Truth —
DataSource.ragIndexEnabledsteuert, ob ein Baum-Element indexiert wird. Vererbung entlang der Baumstruktur (wiescopeundneutralize). - Kein Wizard-Seiteneffekt ohne UI-Revoke — Jede Einstellung die im AddConnectionWizard gesetzt wird, ist in der UDB (Unified Data Bar) sicht- und revertbar.
- Konsent-Entzug = sofortige Purge — Deaktivierung von
ragIndexEnabledoderknowledgeIngestionEnabledlö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]undprogressCb: JobProgressCallback - Prüft
progressCb.isCancelled()periodisch für graceful Abort - Enthält
dataSourceIdin der Provenance jedesIngestionJob - Nutzt per-DataSource
neutralize-Policy (aussubPolicyResolver)
Job-Cancellation
mainBackgroundJobService.py bietet:
cancelJob(jobId)— setzt Status aufCANCELLEDcancelJobsByConnection(connectionId)— bulk-cancel aller laufenden JobsJobProgressCallback.isCancelled()— kooperativer Check mit 3s-Cache
Zombie-Job-Recovery & Walker-Timeouts (ab 2026-05-14)
Drei-stufige Absicherung gegen hängende Bootstraps:
- Boot-Recovery:
recoverInterruptedJobs()markiert beim Worker-Start RUNNING-Jobs als ERROR (kein Auto-Requeue, sonst Endlosschleife). - Live-Killer: Cron
background_jobs.zombie_killerruft alle 5 minkillZombieJobs(maxAgeSeconds=1800)auf — RUNNING-Jobs ohne Progress-Update >30 min werden als ERROR markiert (nutztstartedAtaus dem DB-Record). - Walker-Timeouts (
subWalkerHelpers.py): jeder Walker wickelt seine drei Hot-Spots inasyncio.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
ragIndexEnabledvererbt an Kind-Pfade - Gleiches Pattern wie
neutralizeundscope
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:
- Zentraler Default —
modules/serviceCenter/services/serviceKnowledge/_ragLimits.py(FILES_LIMITS_DEFAULT,CLICKUP_LIMITS_DEFAULT). Die altenMAX_*_DEFAULT-Konstanten in den Walkern sind dünne Aliase und bleiben für Rückwärtskompatibilität bestehen. - DataSource-Override —
DataSource.settings.ragLimits.<key>(oderFeatureDataSource.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-suppliedlimits=-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 USD-Schätzung für einen Voll-Sync mit den aktuellen Limits. Antwort: {estimatedTokens, estimatedUsd, basis: {kind, limits, assumptions, notes}, sourceId}. Default-Heuristik: text-embedding-3-small @ $0.02 / 1M Token, 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:
- Connection —
knowledgeIngestionEnabledMaster-Toggle (=patchKnowledgeConsent-Pfad). - DataSource RAG-Limits — Editierbare Felder; Bytes-Limits in MB im UI, in Bytes am Backend.
- 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 gateway/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.submitDirectorPrompt → service.submitDirectorPrompt → asyncio.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 |
|---|---|
gateway/modules/serviceCenter/registry.py |
Registrierung agent, knowledge, Dependencies, objectKey |
gateway/modules/serviceCenter/services/serviceAgent/mainServiceAgent.py |
AgentService, runAgent, Prompt-Enrichment, Registry-Orchestrierung |
gateway/modules/serviceCenter/services/serviceAgent/coreTools/registerCore.py |
Orchestrator: delegiert Tool-Registrierung an Domänen-Module |
gateway/modules/serviceCenter/services/serviceAgent/coreTools/_workspaceTools.py |
Dateien, Ordner, Web, Übersetzung |
gateway/modules/serviceCenter/services/serviceAgent/coreTools/_connectionTools.py |
Externe Connections, Upload, Mail |
gateway/modules/serviceCenter/services/serviceAgent/coreTools/_dataSourceTools.py |
DataSource Browse/Search/Download |
gateway/modules/serviceCenter/services/serviceAgent/coreTools/_documentTools.py |
Container, Content-Objects, Vision |
gateway/modules/serviceCenter/services/serviceAgent/coreTools/_mediaTools.py |
Rendering, TTS, STT, Bildgenerierung, Charts, Neutralize, Code |
gateway/modules/serviceCenter/services/serviceAgent/coreTools/_featureSubAgentTools.py |
Feature Data Sub-Agent (queryFeatureInstance) |
gateway/modules/serviceCenter/services/serviceAgent/coreTools/_crossWorkflowTools.py |
Workflow-Historie, Messages, _CORE_ONLY_TOOLS-Tagging |
gateway/modules/serviceCenter/services/serviceAgent/agentLoop.py |
ReAct-Loop, Budget, RAG-Injektion, Tool-Dispatch, Summaries |
gateway/modules/serviceCenter/services/serviceAgent/toolRegistry.py |
Registrierung, Dispatch, readOnly, Function-Calling-Format |
gateway/modules/serviceCenter/services/serviceAgent/conversationManager.py |
Kontextfenster, Summarization, Systemprompt |
gateway/modules/serviceCenter/services/serviceAgent/datamodelAgent.py |
AgentConfig, Events, Trace-Modelle |
gateway/modules/serviceCenter/services/serviceAgent/actionToolAdapter.py |
Workflow-Actions → Agent-Tools (Schema-Generierung; Param-Validierung erfolgt zentral im ActionExecutor) |
gateway/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) |
gateway/modules/connectors/connectorDbPostgre.py |
DB-Connector; Read-Methoden raisen DatabaseQueryError bei echten Query-Fehlern (Postgres-Adapt, UndefinedTable/Column, OperationalError, …); Empty Result Sets bleiben []/None |
gateway/modules/serviceCenter/services/serviceAgent/toolboxRegistry.py |
Toolbox-Definitionen, requestToolbox Meta-Tool-Schema |
gateway/modules/serviceCenter/services/serviceAgent/workflowTools.py |
Workflow-Editing-Tools (readWorkflowGraph, addNode, ...) |
gateway/modules/serviceCenter/services/serviceAgent/sandboxExecutor.py |
executeCode-Sandbox |
gateway/modules/serviceCenter/services/serviceKnowledge/mainServiceKnowledge.py |
Index, buildAgentContext, Workflow-/Round-Memory-Helfer |
gateway/modules/interfaces/interfaceDbKnowledge.py |
DB-Zugriff Knowledge / RAG |
gateway/modules/datamodels/datamodelKnowledge.py |
FileContentIndex, ContentChunk, RoundMemory, WorkflowMemory |
gateway/modules/serviceCenter/services/serviceAi/mainServiceAi.py |
Zentrales Neutralisierungs-Gate vor LLM, Billing, Provider-Aufruf |
gateway/modules/aicore/aicoreModelRegistry.py |
Provider-/Modell-Registry |
gateway/modules/aicore/aicoreModelSelector.py |
Modellauswahl |
gateway/modules/aicore/aicorePlugin*.py |
Provider-Implementierungen |
gateway/modules/datamodels/datamodelAi.py |
OperationTypeEnum, AiCallRequest, Optionen |
Regeln / Invarianten
- Neutralisierung vor dem Modell: Alle an ein LLM gehenden Inhalte (Prompt, Kontext, Messages) werden ausschliesslich in
serviceAientsprechend Policy neutralisiert bzw. blockiert — nicht in den Agent-Tools. - Tools liefern Rohdaten: Kein zweites Neutralisieren in Tools; Ausnahme ist bewusste Data-at-rest-Neutralisierung beim Indexieren (
indexFilebeiFileItem.neutralize=True). - DataSource → FileItem:
_downloadFromDataSourcevererbt dasneutralize-Flag der DataSource auf erzeugte Dateien;queryFeatureInstancekann Sub-Agent-Calls mitrequireNeutralization=Trueauslösen, wenn die Feature-Datenquelle das vorsieht. - Kein Tool-RBAC: Autorisierung über Datenbank-, Connection- und Service-Schicht.
- Parallele Tools nur bei
readOnly=True: Schreibende Tools nicht parallel zueinander aus demselben Batch. - RAG optional: Embedding- oder DB-Fehler im Kontextpfad führen nicht zwingend zum Abbruch des Agent-Runs (RAG wird übersprungen/entfällt).
- Billing: AI-Calls und eingebettete Pfade (Embeddings, Summaries) laufen über bestehende Billing-/Subscription-Checks in
serviceAibzw. 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.