feat(generative): Stage-Outcome-Events + Drop-Gründe an den Gate-Punkten (#197 Schritt 1+2)#220
Open
TillQuandel wants to merge 2 commits into
Open
feat(generative): Stage-Outcome-Events + Drop-Gründe an den Gate-Punkten (#197 Schritt 1+2)#220TillQuandel wants to merge 2 commits into
TillQuandel wants to merge 2 commits into
Conversation
…ten (#197 Schritt 1+2) Schritt 1: Neues `stage_outcome`-Trace-Event (ein konsistentes Event pro Note an jedem Gate-Punkt) mit stage/outcome/drop_reason/detail. Ergänzt das bestehende Event-Vokabular (note_outcome/score_result/anchor_stats/plan_stats) um genau eine Klasse über denselben trace_event()-Pfad — kein zweites Tracing-System. Füllt die bisher stummen Gates: Faithfulness (kein Event) und die Drop-Klassen Artifact-Drop, Stage-6-Crash, Exact-Dedup, Sibling-Dedup (vorher nur aggregierte Zähler/stderr-Prints, aus dem Trace nicht rekonstruierbar). Verifier/Critic tragen ihr per-Note-Urteil bereits via anchor_stats bzw. score_result. Schritt 2: Neues DB-Feld n_extracted ("nach Planner/Extractor generiert", Funnel-Top) additiv ergänzt (SCHEMA_SQL + init_db-ALTER + insert_run, Muster wie n_dropped/cost_usd). n_generated (= geschriebene Notes) bleibt semantisch unangetastet — Alt- und Neu-Zeilen bleiben vergleichbar, keine Migration/ Mutation bestehender Zeilen. Nur Schritt 1+2 aus #197; Funnel-Chart folgt separat, sobald Events aus echten Läufen vorliegen. Tests: generative/tests/test_stage_outcome_events.py (12 Tests, alle Gates + DB-Feld). Suite 5705 passed (Baseline 5693 + 12).
…/Extractor/Faithfulness-Skip-Events + Dashboard-Event-Robustheit (#197 Nachbesserung) Extern verifizierte Funde (Mistral-Resample + Fable). Der Gate-Funnel ist oben dicht: jeder Note-Verlust traegt jetzt ein stage_outcome-Event. Funnel-Luecken (orchestrator.py, TDD): - entity_resolution Cluster-Merge (consumed-Set): dedup/dropped/ entity_resolution_merge, Survivor als detail (strukturell wie sibling-Dedup). - _collect_stage6_results BaseException-Zweig: droppte ohne Event UND ohne Crash-Report — jetzt beides (Crash-Report ohne per-Note-Payload + Event). - run_extractors_per_concept: empty-ctext (Konzept nicht im Volltext), None (leere Extraktion) und Exception (call_failed) → extractor/dropped-Events. - _apply_faithfulness_gate: Gate aktiv + create, aber leerer page_index (PDF ohne [S. N]-Marker) → faithfulness/skipped/no_page_index. Dashboard-/DB-Event-Robustheit (minimale Hunks): - eval_dashboard._read_token_runs: Event-Records (kein model) vom call-Zaehler ausschliessen — verhindert aufgeblaehte calls + 0-Token-Phantomzeile. - eval_dashboard_server model-Backfill: Scan-Fenster 5 -> 50 Zeilen (Events am Trace-Anfang tragen kein model). - db.init_db: PRAGMA busy_timeout=5000 (kein WAL-Umbau) + _add_column-Helper, der nur 'duplicate column name' verschluckt und andere OperationalError (z.B. 'database is locked') re-raised. Tests: 8 neu (5 orchestrator TDD in test_stage_outcome_events.py, 3 in neuem test_stage_outcome_robustness.py). Suite 5705 -> 5713 passed, 0 failed. ruff format/check clean. Konflikt: #216 fasst run_extractors_per_concept Exception-Zweig an (failures) — beim Merge beide Zeilen behalten. #222/#212 konfliktfrei zu Punkt 5/6.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Schritte 1+2 aus #197; der Funnel-Chart folgt separat, sobald
stage_outcome-Events aus echten Läufen vorliegen. Keincloses #197.Schritt 1 —
stage_outcome-Event an den Gate-PunktenEin konsistentes Trace-Event pro Note, das ihren Weg durch die Pipeline maschinenlesbar macht. Ergänzt das bestehende Event-Vokabular (
note_outcome/score_result/anchor_stats/plan_stats) um genau eine Klasse über denselbentrace_event()-Pfad — kein zweites Tracing-System. Verifier und Critic tragen ihr per-Note-Urteil bereits viaanchor_statsbzw.score_result; dieses Event füllt die bisher stummen Gates: Faithfulness (hatte gar kein Event) und alle Drop-Klassen (vorher nur aggregierte Zähler / stderr-Prints, aus dem Trace nicht rekonstruierbar).Event:
trace_event("orchestrator", "stage_outcome", {title, stage, outcome, drop_reason?, detail?})Schema (stage × outcome × drop_reason)
stageoutcomedrop_reasondetail_drop_artifactsartifactdroppedabsence_artifactdedup_exactdedupdroppedexact_dupresolve_sibling_dupsdedupdroppedsibling_neardup_collect_stage6_resultsstage6droppedstage6_crashstep/phase_apply_faithfulness_gate(Fail)faithfulnessdowngradedfaithfulness_failN failed_apply_faithfulness_gate(Pass)faithfulnesspassedN supported_apply_faithfulness_gate(action≠create)faithfulnessskippedaction_not_createDamit sind die im Issue genannten fehlenden Drop-Klassen Artifact-Drop und Sibling-Dedup abgedeckt, plus Exact-Dedup, Stage-6-Crash und der bisher event-lose Faithfulness-Verdikt.
Bewusst ausgelassen (dokumentierter Follow-up): Extractor-Empty-Drops (bereits in
n_droppedgezählt, und der Loop ist die direkte Konfliktfläche von #216 — Function bleibt unangetastet) und Entity-Resolution-Cluster-Merges (content-erhaltender Merge, kein Drop; bräuchte Embedding-Mocks zum Testen). Beide sind für den Funnel über vorhandene Zähler/Prints ableitbar.stage_outcometrägt (wienote_outcome) keinmodel-Feld → wird von_is_llm_call_recordkorrekt als Bookkeeping klassifiziert und voneval_agent_stats.aggregate/run_totalssauber übersprungen (keine bestehende Aggregation betroffen).Schritt 2 — n_generated: neues Feld statt Semantikwechsel
Entscheidung:
n_generatedbleibt semantisch unangetastet (= geschriebene Notes; historische Zeilen inpipeline_runsbehalten ihre Bedeutung, Alt/Neu bleiben vergleichbar). Ein neues, korrekt benanntes Feldn_extracted(= „nach Planner/Extractor generiert", Funnel-Top =len(drafts)vor Artifact-/Dedup-/Stage-6-Drops) wird additiv ergänzt — exakt das Muster vonn_dropped/cost_usd:SCHEMA_SQL(frische DBs) +init_db-ALTER TABLE ADD COLUMN(bestehende DBs) +insert_run. Keine Migration/Mutation bestehender Zeilen, kein stiller Semantik-Bruch. Fehlt der Key (Alt-Aufrufer), wird0geschrieben (kein NULL, kein Crash);insert_runist im Orchestrator best-effort (try/except).RED-Beweis (TDD)
Vor der Implementierung,
generative/tests/test_stage_outcome_events.py:Die 2 anfangs grünen Tests sind die Negativ-Assertions (kein Event erwartet:
_drop_artifactsbehält, Faithfulness global deaktiviert). Nach Implementierung: 12 passed. Traces in Tests via echtesJsonlBackendauftmp_pathumgebogen (Muster austest_per_agent_tracking.py); DB-Tests viatmp_path/"test.db"— echte.cache-DB/JSONL nie angefasst.Suite
ruff format --check .clean ·ruff check .clean. Keine Pipeline-Läufe; keine Eval-Daten berührt.Cross-Model-Review (Mistral
magistral-medium-latest)Sauberer Durchlauf, keine realen Funde. Verifiziert: (1)
n_generated/n_dropped-Semantik unverändert; (2)n_extractedvor Drops gesetzt und korrekt persistiert; (3)stage_outcomebricht keine bestehende Aggregation, thread-sicher (bestehendestrace_event+JsonlBackend-Lock); (4) SQL-Spalten/VALUES/Params konsistent; (5) Abwärtskompatibilität (Default 0). Nichts zu fixen.Merge-Reihenfolge & Konfliktflächen (empirisch via
git merge-tree)Empfohlen: #216 → #218 → dieser PR.
git merge-treeexit 0). fix(generative): Timeout-Retry im legacy-Profil + verlorene Konzepte sichtbar (#210) #216 fasstrun_extractors_per_concept+ die frühenmain()-Returns an; ich habe diese Function bewusst unangetastet gelassen und dien_extracted-Erfassung nach dem erstenif not drafts:-Guard platziert.generative/orchestrator.py— fix(generative): pipeline_runs-Insert vom Inline-Eval entkoppeln + Test-Traces nach tmp (#198 P1+P2) #218 verschiebt denpipeline_runs-insert_run-Dict aus dem Stage-8-Eval-Block heraus (läuft unbedingt); dieser PR fügt an dessen alter Position eine Zeile"n_extracted": n_extracted,hinzu. Auflösung: #218s verschobenen Block nehmen und die eine Feldzeile dort re-einfügen. Semantisch unabhängig (beide wollen das Feld im Run-Dict, sie unterscheiden sich nur im Ort des Dicts), ~2 Min. Dien_extracted = len(drafts)-Erfassung (Funnel-Top) mergt sauber (außerhalb des Konfliktblocks).generative/db.py+shared/db_schema.py: konfliktfrei mit beiden PRs.Nachbesserung (2026-07-12) — Funnel-Lücken geschlossen + Dashboard-/DB-Event-Robustheit
Extern verifizierte Funde (Mistral-Resample + Fable) abgearbeitet. Der Gate-Funnel ist damit oben dicht: jeder Punkt, an dem eine Note verschwindet, trägt jetzt ein
stage_outcome-Event.Funnel-Lücken (orchestrator.py, TDD — RED→GREEN)
stageoutcomedrop_reasondetailentity_resolutionCluster-Merge (consumed-Set)dedupdroppedentity_resolution_merge_collect_stage6_resultsBaseException-Zweigstage6droppedstage6_crashrun_extractors_per_conceptKonzept nicht im Volltextextractordroppedempty_extractionnot in fulltextrun_extractors_per_conceptExtractor-None (leer)extractordroppedempty_extractionrun_extractors_per_conceptExtractor-Exceptionextractordroppedcall_failed_apply_faithfulness_gateGate aktiv, aber leerer page_indexfaithfulnessskippedno_page_indexresolve_sibling_dups(das im PR bereits eines bekam). Test mit gemockten Embeddings/merge_cluster(Muster austest_semantic_dedup.py) — die im PR-Body genannte Embedding-Mock-Hürde ist damit erledigt.asyncio.CancelledError): droppte die Note bisher ohne Event UND ohne Crash-Report — der Nachbar-Zweig (_Stage6Failure) hatte beides. Jetzt konsistent: Crash-Report (Payload ohne per-Note-Titel,"?") + Event.no_page_index: Bei aktivem Gate +action=create, aber leerempage_index(PDF ohne[S. N]-Marker, dokumentierter Fall) griff weder Gate- noch Skip-Zweig → gar kein Event.Dashboard-/DB-Event-Robustheit (minimale Hunks)
eval_dashboard._read_token_runs(Punkt 5): zählte jede nicht-gecachte JSONL-Zeile als „call". Die neuen (und bestehenden) Bookkeeping-Events tragen keinmodel→if "model" not in r: continue(inline statt Import —eval_dashboard_serverimportierteval_dashboard, ein Rück-Import wäre zirkulär). Verhindert aufgeblähtecallsund die 0-Token-Phantomzeile bei Runs mit 0 echten LLM-Calls.eval_dashboard_servermodel-Backfill (Punkt 6): scannte nur die ersten 5 Trace-Zeilen nachmodel; mitstage_outcome-Events am Trace-Anfang lief der Backfill leer. Fenster auf 50 Zeilen erweitert (Zeilen ohnemodelwurden ohnehin übersprungen,breaknur bei Treffer).db.init_db(Punkt 7):PRAGMA busy_timeout=5000nachconnect(kein WAL-Umbau) + neuer Helper_add_column, der nur „duplicate column name" verschluckt und jeden anderenOperationalError(v. a. „database is locked" bei Parallel-Prozessen) re-raised. Der frühere pauschaleexcept OperationalError: passdeutete einen Lock-Fehlschlag als „Spalte existiert" fehl — die Spalte fehlte danach und spätere Inserts crashten.Tests & Suite
test_stage_outcome_events.py(ER-Merge, BaseException+Report, Extractor-empty-ctext, Extractor-None/Exception, Faithfulness-no-page-index — TDD, RED vor Implementierung gezeigt), 3 in neuemtest_stage_outcome_robustness.py(Punkt 5 Call-Filter + Phantomzeile, Punkt 7_add_column-Diskriminierung).pytest generative lib/decision_engine/tests shared/tests -q→ 5713 passed, 3 skipped, 4 deselected (Nachbesserungs-Baseline 5705 + 8).ruff format --check .clean ·ruff check .clean. Keine Pipeline-Läufe, keine Eval-Daten berührt.Konfliktflächen — aktualisiert
run_extractors_per_concept. fix(generative): Timeout-Retry im legacy-Profil + verlorene Konzepte sichtbar (#210) #216 fügt im Exception-Zweig der Post-gather-Schleifefailures.append((concept_for_idx[i], str(r)))hinzu und erweitert Signatur/Return um ein 4. Tuple-Element; diese Nachbesserung fügt im selben Zweig_trace_stage_outcome(... call_failed ...)und imelif r is None-Zweig ein Event hinzu. Auflösung: beide Zeilen behalten (semantisch unabhängig — fix(generative): Timeout-Retry im legacy-Profil + verlorene Konzepte sichtbar (#210) #216 = Run-Summary/Exit-Code, hier = Trace-Event). Derif not ctext.strip():-Block (empty-ctext-Event) ist von fix(generative): Timeout-Retry im legacy-Profil + verlorene Konzepte sichtbar (#210) #216 nicht angefasst → konfliktfrei. Empfohlene Merge-Reihenfolge unverändert: fix(generative): Timeout-Retry im legacy-Profil + verlorene Konzepte sichtbar (#210) #216 → fix(generative): pipeline_runs-Insert vom Inline-Eval entkoppeln + Test-Traces nach tmp (#198 P1+P2) #218 → dieser PR (bei fix(generative): Timeout-Retry im legacy-Profil + verlorene Konzepte sichtbar (#210) #216-zuerst wird der Exception-Zweig hier aufgelöst).eval_dashboard._read_token_runsnicht an (ändert_calc_kpis~40 Zeilen tiefer); der Punkt-5-Hunk liegt vollständig in_read_token_runs.if run:-Filter sitzen unmittelbar ober- und unterhalb des model-Backfill-Loops, berühren den Loop-Body (Punkt 6) aber nicht.