# Feature Data Sub-Agent: Query-Repair-Loop + Ontologie-Layer (Trustee-Pilot) ## Beschreibung und Kontext Der **Feature Data Sub-Agent** (`featureDataAgent.py`, Tool `queryFeatureInstance`) gibt dem Workspace-Agent strukturierten Zugriff auf Feature-Datentabellen (Trustee, RealEstate, ...) via `browseTable`/`queryTable`/`aggregateTable`. Heute steuert er sich aus einem Schema-Prompt (Pydantic-Felder + freie Domain-Hints in `mainXxx.getAgentDomainHints()`). Bekannte Halluzinations-Klassen: - **Falsche Aggregate** auf bereits aggregierten Spalten (z. B. `SUM(closingBalance)` über mehrere Perioden -- 7 Jahre × 13 Perioden ≈ 90× echter Saldo). - **ISO-String vs. Unix-Sekunden**-Datumsfilter trotz expliziter Hint-Zeile. - **Erfundene Feldnamen** (LLM rät Felder, statt `browseTable` zuerst aufzurufen). - **Wrong-Table-Choice** (raw JournalLine statt period-bucketed AccountBalance). Heins Forschung (*Boosting Querying Accuracy with Ontology-Guided LLMs*, ENTER 2025) zeigt: Vanilla Text-to-SQL erreicht ~16 % Genauigkeit; ontologie-gestützter Zugriff fast 3× bessere Werte. Sein zweiter Hebel ist deterministische Post-Validierung mit strukturiertem Re-Prompt -- einfacher als die Ontologie und sofort umsetzbar. **Business-Treiber:** - Trustee ist heute der Hot-Spot für Halluzinationen (Bling, PWG, WW-Sales-Pitch). Jede falsche Saldo-Antwort untergräbt das Vertrauen direkt im wichtigsten Feature. - AI-Agent-Quality ist das einzige Differenzierungsmerkmal gegenüber Copilot -- ohne mess- und steuerbare Genauigkeit kein USP. - Vorbereitung auf 90-Min-Q&A mit Andreas Hein: konkretes Pilot-Setup statt abstrakter Diskussion. **Risiko bei Nicht-Tun:** Halluzinationen bleiben opportunistisch über Domain-Hint-Strings gefixt; bei jedem neuen Modell oder Edge-Case Regressionen, ohne Eval-Suite nicht messbar. Die nächsten Features (Lawyer, RealEstate-Buchhaltung) erben das Problem. ## Fokus und kritische Details - **Eine zentrale Stelle:** `featureDataAgent._buildSchemaContext` und `featureDataProvider` -- jede Doppel-Implementierung pro Feature ist verboten (Doc-Sync `b-reference/platform-core/ai-agent.md`). - **Token-Budget:** Jede Prompt-Zeile zählt für jeden Sub-Agent-Call. Heins Paper zeigt, dass kompakte, strukturierte Constraints mehr bringen als ausgeschriebene Erklärungen. Trustee-Hints sind heute ~80 Zeilen -- hart an der Grenze. - **Repair-Loop verbraucht Runden, nicht extra AI-Calls:** Wenn ein Tool-Call deterministisch invalid ist, geben wir dem LLM einen strukturierten Fehler zurück (im selben Tool-Result). Kein paralleler Mini-Agent, keine versteckten Mehrkosten. - **Ontologie ist optional pro Feature:** Features ohne `getAgentOntology()` laufen weiter über den heutigen 3-Schichten-Prompt. Kein Big-Bang. - **Benchmark-Eval ist der eigentliche Release-Gate**, nicht "Feature gebaut" -- ohne Vorher-/Nachher-Zahlen kein Merge. - **Multi-Tenant-Datenrealismus für Eval:** Goldstandard-Antworten brauchen echte Tenant-Daten (anonymisiert oder synthetic-faithful). Nicht aus dem Bauch. ## Ziel und Nicht-Ziele **Ziel Phase 1 (Repair-Loop, 3.3):** - Deterministischer Pre-Execute-Validator in `featureDataProvider` -- liefert strukturierte Fehler statt SQL-Exceptions. - Strukturierte Repair-Hints im Tool-Result, sodass der ReAct-Loop sofort konkret weiss, was zu fixen ist. - Validierungs-Constraints zentral konfigurierbar pro Tabelle (kein Hardcode in der Provider-Methode). **Ziel Phase 2 (Ontologie-Pilot Trustee, 3.1):** - `OntologyDescriptor`-Datenmodell als typisierter Container für Entitäten, Beziehungen und Constraints. - Trustee-Ontologie als erste vollständige Implementation (50-100 Entitäten/Beziehungen). - `getAgentOntology()`-Hook pro Feature-Modul, analog zum bestehenden `getAgentDomainHints()`. - Auto-Generierung von Validator-Constraints **und** Prompt-Hints aus derselben Ontologie -- Single Source of Truth. - Heins ENTER-2025-Methodik: 19-20 Goldstandard-Fragen pro Feature, automatisierter Vorher-/Nachher-Vergleich. **Explizit NICHT:** - Ontologien für andere Features in dieser Iteration (RealEstate, CommCoach, Workspace folgen nach Trustee-Pilot-Erfolg). - Volle OWL/RDF-Tooling -- wir bleiben bei Pydantic + JSON-LD-light. Kein neuer Dependency-Stack. - Frontend-Editor für Ontologien -- v1 ist Code-as-Truth, UI später. - Re-Implementierung der Tool-Signaturen -- LLM-API bleibt identisch. - Ontologie-getriebene SQL-Generierung über Toolset hinaus -- wir bleiben beim Tool-Calling-Pattern. ## Betroffene Module - **Gateway:** - `platform-core/modules/serviceCenter/services/serviceAgent/datamodelAgent.py` (`ToolResult.errorDetails`, `ToolCallLog.validationFailureCode`, `AgentTrace`-Counter) - `platform-core/modules/serviceCenter/services/serviceAgent/agentLoop.py` (Aggregation der Repair-Counter in `AgentTrace` aus den `ToolCallLog`-Einträgen am Run-Ende) - `platform-core/modules/serviceCenter/services/serviceAgent/featureDataAgent.py` (Schema-Prompt-Build, Ontologie-Hook-Resolver, Pre-Execute-Validator-Call, Tool-Description-Ergänzung um `errorDetails`-Erklärung) - `platform-core/modules/serviceCenter/services/serviceAgent/featureDataProvider.py` (unverändert; Validator läuft davor) - `platform-core/modules/serviceCenter/services/serviceAgent/queryValidator.py` **(neu)** (zentrale Validator-Engine) - `platform-core/modules/serviceCenter/services/serviceAgent/datamodelOntology.py` **(neu)** (`OntologyDescriptor`, `Constraint`, `Entity`, `Relation`, `QueryValidationError`) - `platform-core/modules/features/trustee/mainTrustee.py` (`_AGENT_DOMAIN_HINTS` ersetzt durch `getAgentOntology()` + auto-generated hints; alte Hints als `_AGENT_DOMAIN_HINTS_LEGACY` für 1 Sprint geparkt) - `platform-core/modules/features/trustee/trusteeOntology.py` **(neu)** (Trustee-Buchhaltungs-Ontologie als Code) - **Frontend:** keine Änderungen. - **DB-Migration:** nein. Eval-Goldstandard liegt im Test-Repo, nicht in der Mandanten-DB. - **Andere Komponenten:** - `platform-core/tests/integration/featureDataAgent/` **(neu)** -- Goldstandard-Eval-Harness, läuft optional in CI als langer Job. - `platform-core/tests/fixtures/trusteeBenchmark/` **(neu)** -- 19 Fragen + Goldstandard-Antworten + Python-Fixture-Loader (KEINE `fixture.sql`; bestehende Trustee-Tests bauen Daten konsistent via Pydantic + `recordCreate` auf, z. B. `tests/unit/features/trustee/test_accountingDataSync_balances.py`). Goldstandard-Fragen bleiben als YAML. ## Entscheidungen | Datum | Entscheidung | Begründung | |-------|-------------|------------| | 2026-05-15 | Phase 1 (Repair-Loop) vor Phase 2 (Ontologie) | Niedrigste Komplexität zuerst, baut Eval-Infrastruktur, die Phase 2 ohnehin braucht | | 2026-05-15 | Validator-Engine zentral, nicht in `_browseTable`/`_queryTable`/`_aggregateTable` | Wiederverwendung über alle drei Tools, einheitliches Error-Format, testbar isoliert | | 2026-05-15 | Repair-Hints als Tool-Result, kein zweiter AI-Call | Bleibt im ReAct-Pattern, keine versteckten Kosten, einfacher zu debuggen | | 2026-05-15 | Ontologie als Pydantic-Code, nicht als JSON-Datei | Typsicher, IDE-Refactoring, kein Loader-Code, gleiche Patterns wie restliche Plattform | | 2026-05-15 | Trustee als Pilot-Feature | Höchste Halluzinationsrate, klarste Kunden-Sichtbarkeit, bestehende Domain-Hints liefern bereits 80 % der Ontologie-Inhalte | | 2026-05-15 | Goldstandard mit synthetischen Daten, nicht Kunden-Snapshot | Reproduzierbar in CI, kein Datenschutz-Issue, deckt Edge-Cases bewusst ab | | 2026-05-15 | 19 Fragen analog Hein 2025 | Empirisch validierte Sample-Grösse, vergleichbar mit Paper-Resultaten, Aufwand machbar | --- ## Architektur ### Phase 1: Query-Repair-Loop (3.3) **Heute (Soll-Ist):** ``` LLM → Tool _queryTable → FeatureDataProvider.queryTable → SQL execute → Exception "column does not exist" → str(e) im ToolResult → LLM bekommt rohen SQL-Fehler, rät neue Query ``` **Soll:** ``` LLM → Tool _queryTable → QueryValidator.validate(args, ontology|schema) → strukturierter Fehler ODER ok → falls ok: FeatureDataProvider.queryTable → falls Fehler: ToolResult( success=False, error="FIELD_NOT_FOUND: closingBalance? (you wrote klosingBalance)", errorDetails={"code": "FIELD_NOT_FOUND", "field": "klosingBalance", "suggestion": "closingBalance", "hint": "Use browseTable to inspect schema."}) → LLM bekommt actionable Fehler, korrigiert in nächster Runde ``` **Datenmodell-Erweiterung `ToolResult` (`datamodelAgent.py`):** Heute ist `ToolResult.error: Optional[str]`. Für den strukturierten Repair-Hint kommt **ein** zusätzliches Feld dazu: ```python class ToolResult(BaseModel): ... error: Optional[str] = None # bleibt: kurzer human-readable Text errorDetails: Optional[Dict[str, Any]] = None # NEU: strukturierter Repair-Hint ``` `error` bleibt der kurze String, den heutige Konsumenten (Logs, Tests, Audit) sehen. `errorDetails` ist der maschinenlesbare Block, den der LLM-Tool-Result-Serializer einbettet. Das vermeidet JSON-in-String-Hacks (`error=json.dumps(...)`) und hält die bestehenden Tools rückwärtskompatibel. **Komponenten:** 1. **`datamodelOntology.QueryValidationError`** (Pydantic): - `code: ValidationErrorCode` (Enum: `FIELD_NOT_FOUND`, `INVALID_AGGREGATE_TARGET`, `WRONG_TABLE_FOR_PURPOSE`, `TYPE_MISMATCH`, `OPERATOR_INCOMPATIBLE`, `MISSING_REQUIRED_FILTER`) - `field: Optional[str]` - `suggestion: Optional[str]` (z. B. fuzzy-matched Feldname) - `hint: str` (kurzer korrigierender Tipp, max. 80 Zeichen) 2. **`queryValidator.QueryValidator`** -- isolierte Klasse, eine Methode pro Tool: - `validateBrowseQuery(args, tableSchema) -> Optional[QueryValidationError]` - `validateQueryTable(args, tableSchema, constraints) -> Optional[QueryValidationError]` - `validateAggregateQuery(args, tableSchema, constraints) -> Optional[QueryValidationError]` - Liest `tableSchema` aus dem Pydantic-Modell (`MODEL_REGISTRY`). - Liest `constraints` aus optional verfügbarer `OntologyDescriptor`. 3. **Constraint-Typen (Phase 1, Minimum):** - `FieldExists`: Feldname existiert in Pydantic-Modell. - `OperatorCompatible`: Operator passt zum Feldtyp (`LIKE` nur auf String, `>=` nur auf Comparable). - `AggregateTarget`: Whitelist `numerische, nicht bereits aggregierte Felder`. Blacklist via Konvention (`*Balance`, `*Total`) -- Phase 2 ersetzt das durch ontologische Markierung. - `OrderByValid`: `orderBy` ist gültiger Feldname. 4. **Integration in `_browseTable` / `_queryTable` / `_aggregateTable`:** - Vor `provider.xxx(...)` wird `QueryValidator.validateXxx(...)` aufgerufen. - Bei Fehler: `ToolResult(success=False, error=": ", errorDetails={code, field, suggestion, hint})`. `error` bleibt der Kurz-String für Logs/Audit; `errorDetails` ist der maschinenlesbare Block für den LLM. - LLM-Tool-Schema (`parameters`) bleibt identisch -- nur die Tool-Description bekommt einen Satz dazu, der `errorDetails` erklärt. Repair-Logik bleibt sonst unsichtbar. 5. **Telemetrie:** - **Heimat ist `AgentTrace`/`ToolCallLog` in `datamodelAgent.py`, NICHT `aiAuditLogger`.** Begründung: `aiAuditLogger.logAiCall` schreibt pro AI-Call in `AiAuditLogEntry` -- Repair-Metriken sind aber pro Sub-Agent-Run aggregiert (über alle Runden). - `ToolCallLog` um `validationFailureCode: Optional[str]` erweitern (Wert = der `code` aus `errorDetails`, oder `None` wenn ok). Damit landet die Information bei der bestehenden `_persistTrace`-Pipeline in `agentLoop.py` und wird mit dem Rest des Traces gespeichert. - `AgentTrace` bekommt zusätzlich drei aggregierte Counter (`validationFailures`, `repairAttempts`, `successAfterRepair`), die `agentLoop` am Run-Ende aus den `ToolCallLog`-Einträgen aufsummiert. - Eval-Harness und der separate Vergleichsbericht lesen direkt aus `AgentTrace`; kein Eingriff in den AI-Audit-Log nötig. ### Phase 2: Ontologie-Layer für Trustee (3.1) **`OntologyDescriptor`-Modell:** ```python class Entity(BaseModel): name: str # "Konto", "AccountBalance", "JournalEntry" pythonClass: Optional[str] # FK zum Pydantic-Modell, wenn DB-backed semanticType: SemanticType # ACCOUNT, BALANCE_SNAPSHOT, TRANSACTION, ... description: str # Mensch-lesbar invariants: List[Invariant] # z. B. "closingBalance ist NICHT summable" class Relation(BaseModel): fromEntity: str toEntity: str cardinality: Cardinality # ONE_TO_MANY, MANY_TO_ONE, ... via: Optional[str] # FK-Feldname class Constraint(BaseModel): appliesTo: str # "AccountBalance.closingBalance" rule: ConstraintRule # NEVER_AGGREGATE, REQUIRES_FILTER_ON, ... message: str # Hint für Repair-Loop class CanonicalQueryPattern(BaseModel): intent: str # "Banksaldo per Stichtag" pattern: dict # Tool-Call-Skelett mit Platzhaltern class OntologyDescriptor(BaseModel): featureCode: str entities: List[Entity] relations: List[Relation] constraints: List[Constraint] canonicalPatterns: List[CanonicalQueryPattern] ``` **Trustee-Ontologie (Inhalte):** | Block | Anzahl | Heute in Domain-Hints | Neue Form | |-------|--------|------------------------|-----------| | Konten-Klassen (Aktiven, Passiven, Ertrag, Aufwand) | 9 | Hardcoded Bullet-Liste | `Entity` mit `semanticType` + Account-Number-Range-Invariant | | Konten-Subklassen (Bank, Kasse, Forderungen, ...) | ~15 | Bullet-Liste | `Entity` mit `parentEntity` | | Periode-Konvention | 1 | Prosa | `Entity` mit `Invariant`: `periodMonth=0 → annual` | | Saldo-Aggregations-Verbot | 1 (impliziert) | Anti-Pattern-Section | `Constraint(NEVER_AGGREGATE, on="*Balance, *Total")` | | Datumsformat-Pflicht | 1 | Prosa | `Constraint(TYPE_MISMATCH_GUARD, on="bookingDate")` | | Canonical Patterns | 4 | Code-Blöcke im Prompt | `CanonicalQueryPattern` mit Skelett | **Auto-Generierung des Prompt-Texts:** - `_buildSchemaContext` ruft `getAgentOntology(featureCode)`. - Wenn vorhanden: `OntologyToPromptCompiler.compile(ontology, requestLang)` → kompakter, deterministisch sortierter Prompt-Block (ähnliche Länge wie heute, aber ableitbar). - Wenn nicht vorhanden: Fallback auf altes `getAgentDomainHints()` (Übergangsphase). **Ontologie speist auch den Validator:** - `QueryValidator` lädt Constraints aus der Ontologie. - `INVALID_AGGREGATE_TARGET` für `closingBalance`-SUM kommt nicht mehr aus Hardcoded-Regex, sondern aus `Constraint(rule=NEVER_AGGREGATE, appliesTo="AccountBalance.closingBalance", message="closingBalance is already a per-period balance.")`. **Single Source of Truth:** Constraint einmal in der Ontologie definiert → wirkt im Prompt **und** im Validator. Heutige Doppelpflege (Anti-Pattern in Domain-Hint UND impliziter Trust auf LLM) verschwindet. ### Eval-Harness (querschneidend, Phase 1 + 2) **Goldstandard-Format** (`platform-core/tests/fixtures/trusteeBenchmark/questions.yaml`): ```yaml - id: q01 question: "Was ist der Banksaldo per 31.12.2025?" intent: BANK_BALANCE_AT_DATE expectedToolPattern: tool: queryTable tableName: TrusteeDataAccountBalance requiredFilters: [periodYear=2025, periodMonth=0, accountNumber LIKE 102%] forbiddenTools: [aggregateTable] # SUM closingBalance verboten expectedAnswerContains: ["1020", "1021", "CHF"] expectedAnswerNumeric: "1020.closingBalance": 152400.00 "1021.closingBalance": 28100.00 ``` **Harness** (`platform-core/tests/integration/featureDataAgent/test_trusteeBenchmark.py`): - Lädt das Benchmark-Fixture über einen Python-Loader (`loadTrusteeBenchmarkFixture(db, mandateId, featureInstanceId)`) aus `tests/fixtures/trusteeBenchmark/`. Loader nutzt dieselben Pydantic-Modelle und `recordCreate`-Pfade wie Production (kein SQL-Dump, kein Schema-Drift). - Iteriert über 19 Fragen. - Pro Frage: `runFeatureDataAgent` ausführen, `AgentTrace` (Tool-Call-Log inkl. `validationFailureCode`) + finale Antwort capturen. - Bewertung pro Frage: - **Pattern-Match:** richtige Tools, richtige Filter? (binär) - **Forbidden-Tools:** keine verbotenen Aggregations-Calls? (binär) - **Numerische Korrektheit:** Antwort enthält erwartete Zahlen? (toleranzbasiert) - **Repair-Conversion:** falls Validator-Fehler getriggert -- hat das LLM in der nächsten Runde einen *anderen, validen* Tool-Call abgesetzt? (binär, nur für Fälle mit `repairTriggered=True`) - Aggregierte Metrik pro Run: `accuracy`, `patternCompliance`, `repairTriggeredCount`, `repairSucceededCount`, `repairConversionRate = repairSucceededCount / max(1, repairTriggeredCount)`, `avgValidationFailuresPerRun`. - Lauft als pytest-marker `@pytest.mark.eval`, nicht in normaler Test-Suite -- gezielt aufrufbar, lange Laufzeit (≈ 10 Minuten + AI-Cost). **Vergleichs-Runs:** - **Baseline:** heutiger Code, ohne Repair-Loop, ohne Ontologie. - **Phase 1 done:** mit Repair-Loop, ohne Ontologie. - **Phase 2 done:** mit Repair-Loop und Ontologie. - Resultate als Markdown-Bericht in `local/notes/eval-results-YYYY-MM-DD.md`. ## Umsetzungs-Checkliste ### Phase 1 -- Repair-Loop (Woche 1, ca. 3-4 PT) -- DONE 2026-05-15 - [x] `datamodelOntology.py` mit minimaler `OntologyDescriptor`, `Constraint`, `QueryValidationError` (auch ohne volle Ontologie nutzbar) - [x] `ToolResult.errorDetails: Optional[Dict[str, Any]]` in `datamodelAgent.py` ergänzen - [x] `ToolCallLog.validationFailureCode: Optional[str]` in `datamodelAgent.py` ergänzen - [x] `AgentTrace` um aggregierte Counter (`validationFailures`, `repairAttempts`, `successAfterRepair`) erweitern; `agentLoop._computeRepairCounters` summiert aus `ToolCallLog` am Run-Ende, Counters fliessen ins `AGENT_SUMMARY`-Event und damit in `_persistTrace` - [x] `queryValidator.QueryValidator` mit `FieldExists`, `OperatorCompatible`, `AggregateTarget`, `OrderByValid`-Constraints (+ `TYPE_MISMATCH` für SUM auf nicht-numerischen Feldern) - [x] Integration in `_browseTable`, `_queryTable`, `_aggregateTable` (pre-execute-Validator) -- setzt `errorDetails` und befüllt `error` als Kurz-String, Provider wird nie mit known-bad Query erreicht - [x] Tool-Descriptions in `featureDataAgent._buildSubAgentTools` ergänzen: "On validation failure the tool returns success=False with errorDetails={code, field, suggestion, hint}. Use that to correct the next call." - [x] Unit-Tests `test_queryValidator.py` (24 Fälle: pro Constraint Happy + Sad Path, plus Ontologie-Override-Test als Phase-2-Readiness) - [x] Unit-Test-Erweiterung `test_featureDataAgent_schema.py` für `ToolResult.errorDetails`-Format (3 neue Tests: Short-Circuit auf invalid field, Short-Circuit auf SUM(closingBalance), Sanity dass valide Calls den Provider erreichen) - [x] Unit-Tests `test_agentTrace_repairCounters.py` (8 Fälle: leerer Trace, sauberer Run, Single-Fail-No-Repair, Repair-Success, Repair-Re-Fail, Sibling-Calls-im-selben-Round, Tool-Independence, Multi-Tool-Mix) - [x] DB-Migration: nein - [x] Frontend: nein - [x] Neutralisierung betroffen? Nein - [x] RBAC: nein - [x] Billing-Impact? Nein (Repair-Hints kosten 0 zusätzliche AI-Calls) - [x] Lint-Check: keine Warnings - [x] Regression-Test: alle 62 bestehenden serviceAgent-/featureDataAgent-schema-Tests bleiben grün ### Phase 1.5 -- Eval-Harness (Woche 2, ca. 3-4 PT, parallel zu Phase 2 startbar) - [ ] `platform-core/tests/fixtures/trusteeBenchmark/loadTrusteeBenchmarkFixture.py` (Python-Loader, Pydantic + `recordCreate`; analog zu `tests/unit/features/trustee/test_accountingDataSync_balances.py`) - [ ] `platform-core/tests/fixtures/trusteeBenchmark/questions.yaml` mit 19 Fragen + Goldstandard - [ ] `test_trusteeBenchmark.py` mit pytest-marker `eval`; misst neben `accuracy` und `patternCompliance` auch `repairConversionRate` (Repair-Triggered → korrigiert in nächster Runde) - [ ] Eval-Bericht-Generator (Markdown-Output) - [ ] **Baseline-Run** (vor Phase 2) -- Resultate in `local/notes/eval-results-baseline.md` - [ ] **Phase-1-Run** (nach Repair-Loop, vor Ontologie) -- Resultate in `local/notes/eval-results-phase1.md` ### Phase 2 -- Ontologie-Pilot Trustee (Woche 3-4, ca. 5-7 PT) -- DONE - [x] Ontology-Datenmodell vervollständigt (`Entity`, `Relation`, `Invariant`, `CanonicalQueryPattern`, `SemanticType`-Enum -- bereits in Phase 1 vorbereitet, jetzt aktiv genutzt) - [x] `platform-core/modules/features/trustee/trusteeOntology.py` mit Trustee-Buchhaltungs-Ontologie (6 Entitäten inkl. BankAccount/CashAccount-Spezialisierungen, 3 Relations, 6 Constraints, 8 CanonicalPatterns) - [x] `getAgentOntology()`-Hook in `mainTrustee.py` - [x] `OntologyToPromptCompiler` -- generiert kompakten Prompt-Text deterministisch (`ontologyToPromptCompiler.py`) - [x] `_buildSchemaContext` ruft Ontologie-Compiler bevorzugt (`_loadFeatureOntologyBlock`), fällt auf `getAgentDomainHints()` zurück; mit `POWERON_DISABLE_FEATURE_ONTOLOGY=1` Eval-only Override - [x] `QueryValidator` lädt Constraints aus Ontologie (NEVER_AGGREGATE via `_isAggregateBlacklisted`); `_buildValidatorForFeature` wired Validator+Ontologie automatisch - [x] Migration: alte Trustee-Domain-Hints als `_AGENT_DOMAIN_HINTS_LEGACY` geparkt (Sicherheitsnetz) - [x] Unit-Tests für Ontologie-Loading + Compiler (16 Tests in `test_trusteeOntology.py`); test_featureDataAgent_schema erweitert um Ontology-Pfad - [x] **Phase-2-Run** des Eval-Harness -- Resultate in `local/notes/trustee-benchmark-20260515-161126.md` - [x] Vergleichs-Tabelle Baseline vs. Phase 1 vs. Phase 2 -- gleicher Report - [x] Side-Fix: `aggregateTable` Tool-Definition um `filters`-Parameter erweitert (war Code-Bug, blockierte SUM-on-account-Anfragen) ### Abschluss vor Hein-Q&A - [x] Vergleichs-Tabelle in präsentierbarem Format -- Markdown-Tabelle in `local/notes/trustee-benchmark-20260515-161126.md` (Summary-Sektion) - [x] Übersicht der häufigsten `validationFailures` aus Telemetrie -- 2× `INVALID_AGGREGATE_TARGET` auf `debitTotal` (q13 Phase 1); Phase 2 verhindert das präemptiv durch Ontologie-Block im Prompt ## Akzeptanzkriterien | # | Kriterium (Given-When-Then) | Prio | |---|----------------------------|------| | 1 | Given Sub-Agent ruft `queryTable` mit nicht-existentem Feld, When Validator prüft vor Execute, Then Tool-Result hat `success=False` mit `code=FIELD_NOT_FOUND` und `suggestion`-Feld | must | | 2 | Given Sub-Agent ruft `aggregateTable(SUM, closingBalance)` ohne Periode-Filter, When Validator prüft, Then Tool-Result hat `code=INVALID_AGGREGATE_TARGET` mit Hint zu Periode-Filter | must | | 3 | Given Validator-Fehler wurde im Eval-Run getriggert (Repair-Triggered-Subset), When ReAct nächste Runde startet, Then in ≥ 80 % dieser Fälle setzt das LLM einen *anderen, validen* Tool-Call ab (`repairConversionRate ≥ 0.8`, nicht End-Accuracy) | must | | 4 | Given Trustee-Feature exportiert `getAgentOntology()`, When `_buildSchemaContext` läuft, Then verwendet es die Ontologie und nicht `getAgentDomainHints()` | must | | 5 | Given `getAgentOntology()` fehlt für ein Feature, When Sub-Agent läuft, Then Fallback auf altes Verhalten ohne Fehler | must | | 6 | Given Eval-Harness läuft auf Goldstandard-19, When Baseline und Phase 2 verglichen, Then Phase 2 hat ≥ 25 Prozentpunkte bessere `accuracy` | must | | 7 | Given Phase 2 deployt, When Sub-Agent für irgendein Feature ohne Ontologie läuft, Then Verhalten unverändert (keine Regression) | must | | 8 | Given Validator-Constraints aus Ontologie, When Constraint geändert wird, Then sowohl Prompt als auch Validator nutzen die neue Definition (Single Source of Truth) | must | | 9 | Given Repair-Hint im Tool-Result, When als Tool-Description-Schema beschrieben, Then LLM versteht das Format ohne zusätzlichen Prompt-Text auf jedem Call | should | | 10 | Given `AgentTrace` mit aggregierter Telemetrie, When Sub-Agent-Run analysiert wird, Then `validationFailures` und `successAfterRepair` (aus `ToolCallLog`-Aggregation) zeigen, ob Repair-Loop wirkt | should | ## Testplan | ID | AC | Art | Automatisiert | Repo-Pfad | Status | |----|----|-----|--------------|-----------|--------| | T1 | 1 | unit | ja | platform-core/tests/unit/services/test_queryValidator.py | DONE (24 tests) | | T2 | 2 | unit | ja | platform-core/tests/unit/services/test_queryValidator.py | DONE | | T3 | 3 | eval | standalone runner | platform-core/tests/eval/runTrusteeBenchmark.py | DONE (Phase 1 zeigt repair-deflection in q13: val-fail=2, accuracy 89.5% -> 94.7%) | | T4 | 4 | unit | ja | platform-core/tests/unit/services/test_featureDataAgent_schema.py, test_trusteeOntology.py | DONE | | T5 | 5 | unit | ja | platform-core/tests/unit/services/test_featureDataAgent_schema.py | DONE (test_skipsHintsForFeaturesWithoutHook, test_buildValidatorForFeature_unknownFeature_noOntology) | | T6 | 6 | eval | standalone runner | platform-core/tests/eval/runTrusteeBenchmark.py | DONE -- Baseline 89.5% -> Phase 2 100% = +10.5 Pp (Ziel war ≥ 25 Pp; AC nur teilweise erfüllt -- ehrlich dokumentiert, siehe Diskussion unten) | | T7 | 7 | unit + smoke | ja | platform-core/tests/unit/services/test_featureDataAgent_schema.py | DONE -- Features ohne Ontologie laufen unverändert | | T8 | 8 | unit | ja | platform-core/tests/unit/services/test_trusteeOntology.py | DONE -- 16 tests, einer prüft Validator + Compiler über gleiche Constraint | | T9 | 9 | manuell | nein | platform-core/modules/serviceCenter/services/serviceAgent/featureDataAgent.py:284-310 | DONE -- Tool-Description erklärt errorDetails-Format inline | | T10 | 10 | unit | ja | platform-core/tests/unit/serviceAgent/test_agentTrace_repairCounters.py | DONE (8 tests) | ## Links - Forschungs-Input: `pamocreate/projects/poweron/admin-compliance-security/20-outputs/20260515-andreas-hein-learnings-fuer-porta.md` (Punkte 3.1, 3.3) - Hein-Paper: *Boosting Querying Accuracy with Ontology-Guided LLMs* (ENTER 2025, Springer) - Bestehender Code: `platform-core/modules/serviceCenter/services/serviceAgent/featureDataAgent.py`, `mainTrustee.py:676-754` (`_AGENT_DOMAIN_HINTS`) - Bezug Wiki: `b-reference/platform-core/ai-agent.md` (Abschnitt FeatureSubAgent) ## Abschluss - [x] `b-reference/platform-core/ai-agent.md` aktualisiert (FeatureSubAgent-Abschnitt: `ToolResult.errorDetails`, `QueryValidator`, Ontologie-Hook, Eval-Harness, Repair-Telemetrie auf `AgentTrace`) - [x] `b-reference/platform-core/features/trustee.md` neuer Abschnitt "Agent-Ontologie" - [x] `wiki/d-guides/coding-conventions.md` Migrations-Pattern für `getAgentOntology()` - [x] TOPICS.md geprüft (kein neuer Eintrag erforderlich -- bestehende KI-Agent-Sektion verweist auf die aktualisierte b-reference-Seite) - [x] Eval-Vergleich für Hein-Q&A: `local/notes/trustee-benchmark-20260515-161126.md` (Summary-Tabelle) - [ ] Dieses Dokument → `c-work/4-done/` nach Validierungs-Sign-off ## Ergebnisse (Validation) **Benchmark-Run vom 2026-05-15 16:11 (19 Fragen × 3 Modi, 57 echte LLM-Calls):** | Mode | Accuracy | Pattern compliance | Validator rejects | Rounds | Cost (CHF) | |-------------------------------|----------|--------------------|-------------------|--------|------------| | Baseline (no validator) | 89.5 % | 89.5 % | 0 | 40 | 0.0841 | | Phase 1 (validator on) | 94.7 % | 100 % | 2 | 41 | 0.0851 | | Phase 2 (validator + ontology)| **100 %**| 100 % | 0 | 39 | 0.0975 | **Beobachtungen:** 1. **Phase 1 Repair-Loop wirkt aktiv** (q13 "Summe aller Aufwandskonten"): Validator lehnt `aggregateTable(SUM, debitTotal)` 2× ab, der Agent lenkt eigenständig auf `queryTable` mit Periode-Filter um -- ohne dass derselbe Tool-Call wiederholt wird (Deflection statt klassischer Repair). Das erklärt `repairAttempts=0` trotz Erfolg: Der Agent korrigiert sich präemptiv über die strukturierte `errorDetails`-Antwort. 2. **Phase 2 Ontologie verhindert Halluzinationen präemptiv** (q09 "Beratungsertrag"): Baseline und Phase 1 wählen fälschlicherweise `creditTotal` statt `closingBalance`. Die Ontologie-Beschreibung "debitTotal/creditTotal sind period TURNOVER, nicht Balance" lenkt den Agent korrekt zu `closingBalance` -- kein Validator-Hit nötig. 3. **Phase 2 ist effizienter** (39 vs. 41 Runden) trotz längerem Ontologie-Prompt-Block: weniger Fehl-Versuche durch klare canonical patterns. 4. **Cost-Trade-off**: Phase 2 ist 16 % teurer (0.0975 vs. 0.0841 CHF / 19 Fragen) für 10.5 Prozentpunkte mehr Accuracy. Klar gerechtfertigt. 5. **Side-Fix entdeckt**: `aggregateTable` exponierte den `filters`-Parameter nicht im Tool-Schema -- LLM konnte keine gefilterten Aggregate machen. Behoben in `featureDataAgent._aggregateTable` + Schema; ohne diesen Fix wäre die Trustee-Suite verzerrt. **AC-6 Ehrlichkeit:** Das Plan-Kriterium ≥ 25 Prozentpunkte Accuracy-Gain (Baseline → Phase 2) wurde **nicht erreicht** (real: +10.5 Pp). Grund: Die heutigen Trustee-Domain-Hints sind bereits hochwertig (Baseline schon bei 89.5 %), das Verbesserungs-Headroom war kleiner als angenommen. Die Forschungs-Vorlage hat von 16 % → 47 % (Hein 2025) gemessen -- bei einem deutlich schwächeren Baseline-Prompt. Die qualitative Aussage "Ontologie + Repair-Loop verhindern bekannte Halluzinations-Klassen" bleibt belegt (q09 ausschließlich durch Ontologie, q13 ausschließlich durch Repair-Loop).