wiki/c-work/3-validate/2026-05-feature-data-agent-ontology-and-repair.md
2026-05-16 22:54:27 +02:00

374 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- status: validate -->
<!-- started: 2026-05-15 -->
<!-- completed: 2026-05-15 -->
<!-- component: gateway -->
# 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/gateway/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:**
- `gateway/modules/serviceCenter/services/serviceAgent/datamodelAgent.py` (`ToolResult.errorDetails`, `ToolCallLog.validationFailureCode`, `AgentTrace`-Counter)
- `gateway/modules/serviceCenter/services/serviceAgent/agentLoop.py` (Aggregation der Repair-Counter in `AgentTrace` aus den `ToolCallLog`-Einträgen am Run-Ende)
- `gateway/modules/serviceCenter/services/serviceAgent/featureDataAgent.py` (Schema-Prompt-Build, Ontologie-Hook-Resolver, Pre-Execute-Validator-Call, Tool-Description-Ergänzung um `errorDetails`-Erklärung)
- `gateway/modules/serviceCenter/services/serviceAgent/featureDataProvider.py` (unverändert; Validator läuft davor)
- `gateway/modules/serviceCenter/services/serviceAgent/queryValidator.py` **(neu)** (zentrale Validator-Engine)
- `gateway/modules/serviceCenter/services/serviceAgent/datamodelOntology.py` **(neu)** (`OntologyDescriptor`, `Constraint`, `Entity`, `Relation`, `QueryValidationError`)
- `gateway/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)
- `gateway/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:**
- `gateway/tests/integration/featureDataAgent/` **(neu)** -- Goldstandard-Eval-Harness, läuft optional in CI als langer Job.
- `gateway/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="<code>: <short hint>", 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** (`gateway/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** (`gateway/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)
- [ ] `gateway/tests/fixtures/trusteeBenchmark/loadTrusteeBenchmarkFixture.py` (Python-Loader, Pydantic + `recordCreate`; analog zu `tests/unit/features/trustee/test_accountingDataSync_balances.py`)
- [ ] `gateway/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] `gateway/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 | gateway/tests/unit/services/test_queryValidator.py | DONE (24 tests) |
| T2 | 2 | unit | ja | gateway/tests/unit/services/test_queryValidator.py | DONE |
| T3 | 3 | eval | standalone runner | gateway/tests/eval/runTrusteeBenchmark.py | DONE (Phase 1 zeigt repair-deflection in q13: val-fail=2, accuracy 89.5% -> 94.7%) |
| T4 | 4 | unit | ja | gateway/tests/unit/services/test_featureDataAgent_schema.py, test_trusteeOntology.py | DONE |
| T5 | 5 | unit | ja | gateway/tests/unit/services/test_featureDataAgent_schema.py | DONE (test_skipsHintsForFeaturesWithoutHook, test_buildValidatorForFeature_unknownFeature_noOntology) |
| T6 | 6 | eval | standalone runner | gateway/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 | gateway/tests/unit/services/test_featureDataAgent_schema.py | DONE -- Features ohne Ontologie laufen unverändert |
| T8 | 8 | unit | ja | gateway/tests/unit/services/test_trusteeOntology.py | DONE -- 16 tests, einer prüft Validator + Compiler über gleiche Constraint |
| T9 | 9 | manuell | nein | gateway/modules/serviceCenter/services/serviceAgent/featureDataAgent.py:284-310 | DONE -- Tool-Description erklärt errorDetails-Format inline |
| T10 | 10 | unit | ja | gateway/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: `gateway/modules/serviceCenter/services/serviceAgent/featureDataAgent.py`, `mainTrustee.py:676-754` (`_AGENT_DOMAIN_HINTS`)
- Bezug Wiki: `b-reference/gateway/ai-agent.md` (Abschnitt FeatureSubAgent)
## Abschluss
- [x] `b-reference/gateway/ai-agent.md` aktualisiert (FeatureSubAgent-Abschnitt: `ToolResult.errorDetails`, `QueryValidator`, Ontologie-Hook, Eval-Harness, Repair-Telemetrie auf `AgentTrace`)
- [x] `b-reference/gateway/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).