522 lines
40 KiB
Markdown
522 lines
40 KiB
Markdown
<!-- status: canonical -->
|
||
<!-- lastReviewed: 2026-06-02 -->
|
||
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification); platform-core/modules/features/teamsbot/service.py (Hybrid Agent Escalation 2026-04-24); Typed Action Architecture Phasen 1-5; featureDataAgent domain hints hook 2026-04-27; central parameterValidation + DatabaseQueryError 2026-04-28; OpenAI temperature contract for GPT-5.x / o-series 2026-04-28; Voice STT speechToText params note 2026-05-10; RAG Consent & Control Unification (Phases A-D) 2026-05-12; Zombie-Killer + Walker-Timeouts 2026-05-14; FeatureDataAgent Query-Repair-Loop + Ontology layer 2026-05-15; UDB DataSource Settings + configurable RAG-Limits 2026-05-17; Cost-Estimate Währung USD→CHF 2026-05-22; DataSource search-first + adapter scoping/pagination + inline metadata (Outlook/Gmail/SharePoint/OneDrive/Drive/Calendar/Contacts/ClickUp) 2026-06-02 (connectorMsft.py, connectorGoogle.py, connectorInfomaniak.py, connectorClickup.py, _dataSourceTools.py, routeFeatureWorkspace.py) -->
|
||
|
||
# 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:** `runAgentLoop` — `ConversationManager` + 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, 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:
|
||
|
||
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 `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` | 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 (PDF/DOCX/PPTX/XLSX/HTML); optionaler `style`-Parameter fuer Agent-Overrides; AI-enhanced Styling basierend auf Dokumentkontext; Smart Table Styling via `tableStyle` pro Tabelle. Details: `b-reference/platform-core/document-rendering.md` |
|
||
| `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/global** — `semanticSearch` 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.
|
||
|
||
### RAG Consent & Control (ab 2026-05)
|
||
|
||
Volle Architektur für transparente, datenzentrierte Steuerung der RAG-Indexierung durch den Benutzer.
|
||
|
||
#### Prinzipien
|
||
|
||
1. **Datenquelle ist die Single Source of Truth** — `DataSource.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 Default** — `modules/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-Override** — `DataSource.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. **Connection** — `knowledgeIngestionEnabled` 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.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`](../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.
|