374 lines
29 KiB
Markdown
374 lines
29 KiB
Markdown
<!-- 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/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="<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** (`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).
|