wiki/c-work/4-done/2026-05-feature-data-agent-ontology-and-repair.md
2026-06-02 09:42:12 +02:00

29 KiB
Raw Permalink Blame History

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:

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:

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):

- 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

  • datamodelOntology.py mit minimaler OntologyDescriptor, Constraint, QueryValidationError (auch ohne volle Ontologie nutzbar)
  • ToolResult.errorDetails: Optional[Dict[str, Any]] in datamodelAgent.py ergänzen
  • ToolCallLog.validationFailureCode: Optional[str] in datamodelAgent.py ergänzen
  • 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
  • queryValidator.QueryValidator mit FieldExists, OperatorCompatible, AggregateTarget, OrderByValid-Constraints (+ TYPE_MISMATCH für SUM auf nicht-numerischen Feldern)
  • 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
  • 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."
  • Unit-Tests test_queryValidator.py (24 Fälle: pro Constraint Happy + Sad Path, plus Ontologie-Override-Test als Phase-2-Readiness)
  • 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)
  • 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)
  • DB-Migration: nein
  • Frontend: nein
  • Neutralisierung betroffen? Nein
  • RBAC: nein
  • Billing-Impact? Nein (Repair-Hints kosten 0 zusätzliche AI-Calls)
  • Lint-Check: keine Warnings
  • 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

  • Ontology-Datenmodell vervollständigt (Entity, Relation, Invariant, CanonicalQueryPattern, SemanticType-Enum -- bereits in Phase 1 vorbereitet, jetzt aktiv genutzt)
  • platform-core/modules/features/trustee/trusteeOntology.py mit Trustee-Buchhaltungs-Ontologie (6 Entitäten inkl. BankAccount/CashAccount-Spezialisierungen, 3 Relations, 6 Constraints, 8 CanonicalPatterns)
  • getAgentOntology()-Hook in mainTrustee.py
  • OntologyToPromptCompiler -- generiert kompakten Prompt-Text deterministisch (ontologyToPromptCompiler.py)
  • _buildSchemaContext ruft Ontologie-Compiler bevorzugt (_loadFeatureOntologyBlock), fällt auf getAgentDomainHints() zurück; mit POWERON_DISABLE_FEATURE_ONTOLOGY=1 Eval-only Override
  • QueryValidator lädt Constraints aus Ontologie (NEVER_AGGREGATE via _isAggregateBlacklisted); _buildValidatorForFeature wired Validator+Ontologie automatisch
  • Migration: alte Trustee-Domain-Hints als _AGENT_DOMAIN_HINTS_LEGACY geparkt (Sicherheitsnetz)
  • Unit-Tests für Ontologie-Loading + Compiler (16 Tests in test_trusteeOntology.py); test_featureDataAgent_schema erweitert um Ontology-Pfad
  • Phase-2-Run des Eval-Harness -- Resultate in local/notes/trustee-benchmark-20260515-161126.md
  • Vergleichs-Tabelle Baseline vs. Phase 1 vs. Phase 2 -- gleicher Report
  • Side-Fix: aggregateTable Tool-Definition um filters-Parameter erweitert (war Code-Bug, blockierte SUM-on-account-Anfragen)

Abschluss vor Hein-Q&A

  • Vergleichs-Tabelle in präsentierbarem Format -- Markdown-Tabelle in local/notes/trustee-benchmark-20260515-161126.md (Summary-Sektion)
  • Ü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)
  • 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

  • b-reference/platform-core/ai-agent.md aktualisiert (FeatureSubAgent-Abschnitt: ToolResult.errorDetails, QueryValidator, Ontologie-Hook, Eval-Harness, Repair-Telemetrie auf AgentTrace)
  • b-reference/platform-core/features/trustee.md neuer Abschnitt "Agent-Ontologie"
  • wiki/d-guides/coding-conventions.md Migrations-Pattern für getAgentOntology()
  • TOPICS.md geprüft (kein neuer Eintrag erforderlich -- bestehende KI-Agent-Sektion verweist auf die aktualisierte b-reference-Seite)
  • 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).