Skip to content

Retrieval-Goldset + Evidence-in-Pool-Recall-Harness als #232-Abnahme (PR-A) - #254

Merged
TillQuandel merged 2 commits into
masterfrom
feat/232-retrieval-goldset
Jul 13, 2026
Merged

Retrieval-Goldset + Evidence-in-Pool-Recall-Harness als #232-Abnahme (PR-A)#254
TillQuandel merged 2 commits into
masterfrom
feat/232-retrieval-goldset

Conversation

@TillQuandel

Copy link
Copy Markdown
Owner

Zweck

Additive Abnahme-Grundlage fuer den kommenden #232-Retrieval-Fix (PR-B). Diese PR aendert keinen Retrieval-/Chunking-Code — sie konserviert die handadjudizierten Kalibrierungs-Datenpunkte aus der Diagnose und macht den Bug (Granularitaets-Mismatch Chunk- vs. Satz-Retrieval) als deterministische Kennzahl messbar.

Was drin ist

  • generative/calibration/retrieval-goldset/anchors.jsonl — 11 hand-adjudizierte Anker aus 3 Diagnose-Laeufen, jeder evidence_quote per pdftotext -layout gegen die Original-PDF und gegen den echten Chunk.text-Output der Pipeline verifiziert:
    • Hrastinski 2008 „Synchronous E-Learning" (Lauf 5, run 20260712-215118): 6 Anker
    • Bates 2017 „Disziplinaere Ausweitung der IB-Forschung" (Lauf 4, run 20260712-213647): 2 Anker
    • Suehl-Strohmenger 2008 „Learning Library" (Lauf 3, run 20260712-205627): 3 Anker
  • generative/tests/test_retrieval_goldset.py — deterministischer Harness, kein LLM. Nutzt die Produktions-Bausteine (_chunks_from_sentences, _retrieve_claim_contexts, _build_context_pool, _normalize_for_evidence).
  • generative/calibration/retrieval-goldset/build_from_reports.py — Regex-Parser fuer die Anker-Forensik-Tabellen der Diagnose-Berichte (Schritt 1 der Pipeline).
  • README.md — Herkunft, Format, Adjudikations-Legende, reale Retrieval-Zahlen, verworfene Kandidaten, Erweiterungs-Anleitung.
  • .gitignore — scoped Ausnahme fuer anchors.jsonl (der globale *.jsonl-Filter zielt auf Run-Output, nicht auf dieses versionierte Abnahme-Artefakt).

Anker-Aufschluesselung

Adjudikation Anzahl
false_positive_retrieval_miss 10
true_hallucination (Negativ-Kontrolle idx8) 1
Summe 11
  • Cross-Lingual (DE-Claim / EN-Quelle) mit niedriger Presence-Cosine 0,64–0,67: 2 (Hrastinski idx4 = 0,666, idx13 = 0,640) — bewusst NICHT die „leichten" Flags, damit der Fix nicht nur auf einfach findbare Ankertexte kalibriert. Zusaetzlich 3x DE/DE-Kontrastfall (Suehl-Strohmenger) → belegt, dass die Chunk-Granularitaet die Ursache ist, nicht primaer Cross-Lingual-Embedding-Schwaeche.
  • Negativ-Kontrolle: idx8 (Hrastinski Table-3-Attribution vertauscht → expected_label: contradicted, adjudication: true_hallucination). Der spaetere Fix darf sie NICHT wegkalibrieren; der Test prueft explizit, dass ihre kontrahierende Evidenz korrekt im Pool landet (bereits heute gruen).

Der Bug in Zahlen (master)

Evidence-in-Pool-Recall ueber alle 10 false_positive_retrieval_miss-Anker: 2/10 = 20,0 % (batched wie Produktion, ein gemeinsamer Kontext-Pool je Note). Deterministisch reproduziert; die Chunk-Top-Cosine deckt sich exakt mit den Diagnose-Berichten. Die 2 Treffer sind Batch-Pooling-/_expand_context-Zufallsrettungen (bei isolierter Einzel-Claim-Retrieval waere der Recall 0/10), kein Fix.

Die scharfe Assertion recall >= 0.90 ist als @pytest.mark.xfail(strict=True) markiert: heute erwartet-rot (xfail, zaehlt nicht als Failure), nach PR-B (F1+F2) wird der Recall gehoben → XPASS → strict=True FAILt den Lauf und zwingt zum Entfernen des xfail-Markers → Abnahme scharf geschaltet.

Status

  • Voll-Suite pytest generative lib/decision_engine/tests shared/tests -q: 5789 passed, 3 skipped, 6 deselected (0 unerwartete Failures; die 2 slow-Goldset-Tests unter den Deselected).
  • ruff check + ruff format --check: sauber.
  • Keine Mutation von quality_history.jsonl, runs/, .cache/eval/baseline — reine Lese-Zugriffe.

Refs #232 (schliesst NICHT — #232 schliesst erst PR-B, der den xfail scharf schaltet).

🤖 Generated with Claude Code

https://claude.ai/code/session_019AnPkyEqHyKUSJKVqp3SZS

Tilltime added 2 commits July 13, 2026 20:48
-Abnahme (PR-A)

Additives Abnahme-Artefakt fuer den kommenden #232-Retrieval-Fix (PR-B). Aendert
keinen Retrieval-/Chunking-Code, sondern macht den Granularitaets-Mismatch
(Chunk- vs. Satz-Retrieval) messbar und reproduzierbar.

- generative/calibration/retrieval-goldset/anchors.jsonl: 11 hand-adjudizierte,
  pdftotext-verifizierte Anker aus 3 Diagnose-Laeufen (Hrastinski 2008, Bates
  2017, Suehl-Strohmenger 2008). 10x false_positive_retrieval_miss + 1x
  true_hallucination (idx8 Table-3-Attributionsfehler als Negativ-Kontrolle,
  darf vom Fix nicht wegkalibriert werden). 2 Cross-Lingual-Faelle mit niedriger
  Presence-Cosine (0,64/0,67).
- generative/tests/test_retrieval_goldset.py: deterministischer Harness ohne
  LLM. Misst Evidence-in-Pool-Recall ueber die FP-Anker (aktuell 2/10 = 20% auf
  master, der Bug-Beleg). Scharfe Assertion recall>=0.90 als xfail(strict=True),
  bis PR-B den Recall hebt. Negativ-Kontrolle bereits gruen. @pytest.mark.slow
  (MiniLM-Load), damit die regulaere Suite (-m "not slow") sie deselektiert.
- generative/calibration/retrieval-goldset/build_from_reports.py: Parser fuer
  die Anker-Forensik-Tabellen der LAUF-INFO.md-Diagnoseberichte (Schritt 1).
- README.md: Herkunft, Format, Adjudikations-Legende, reale Retrieval-Zahlen,
  verworfene Kandidaten, Erweiterungs-Anleitung.
- .gitignore: scoped Ausnahme fuer anchors.jsonl (vom *.jsonl-Run-Output-Filter).

Refs #232
…engig, keine fixe Referenzzahl

Nachbesserung nach unabhaengigem Review (harte Faktentreue-Regel): ein zweiter
Messlauf ergab 4/10 statt der zuvor als Fakt genannten 2/10. Ursache: Modell-/
Library-Versionsdrift (embeddings.position_ids | UNEXPECTED im BertModel-Load).

- "Reale Zahlen"-Sektion -> "Beobachtete Zahlen": die "bit-fuer-bit dieselbe
  Pipeline-Logik"-Behauptung entfernt; die feste 2/10-Ziffer durch einen Caveat
  ersetzt (Recall beobachtet 2/10-4/10 ueber verschiedene sentence-transformers-
  Zustaende, in JEDEM Fall robust unter der 0,90-Schwelle). Die Cosine-Tabelle
  bleibt als illustrative Momentaufnahme EINES Environments, ausdruecklich als
  driftend markiert.
- Abnahme fuer PR-B praezisiert: prueft die Recall-Verbesserung IN DERSELBEN
  Umgebung (before/after), NICHT das Erreichen einer absoluten Referenzzahl.
- Cross-Lingual-Presence-Cosine (idx4/idx13) von "deterministisch nachgerechnet"
  auf "gemessen, umgebungsabhaengig (~0,64-0,67)" entschaerft.
- build_from_reports.py: Regex-Edge-Case dokumentiert (pipe-foermiger Teilstring
  im Claim/Befund kann still fehlparsen; Absicherung = verpflichtende manuelle
  pdftotext-Gegenprobe vor Aufnahme in anchors.jsonl).

anchors.jsonl unveraendert (Anker sind PDF-verifiziert und korrekt).

Refs #232
@TillQuandel
TillQuandel merged commit 496518f into master Jul 13, 2026
4 checks passed
TillQuandel added a commit that referenced this pull request Jul 14, 2026
…g gegen Retrieval-Miss (#232) (#265)

* fix(eval): Satz-Level-Retrieval-Rescue + Titel-Chunk-Deprioritisierung gegen Retrieval-Miss (#232)

Root Cause (#232): Granularitaets-Mismatch. Das Eval-Retrieval
(_retrieve_claim_contexts) rankt Claims gegen CHUNK-Embeddings (100-180 Tok);
der stuetzende SATZ hat hohe Claim-Cosine, sein Home-Chunk landet aber weit
hinten, weil das Beleg-Signal ueber den Chunk gemittelt wird -> faellt aus
adaptive_k -> der Judge sieht den Beleg nie -> falsch-positives not_in_context
-> aufgeblaehte Halluzinationsrate.

F1 -- Satz-Level-Retrieval-Rescue VOR dem Judge (_rescue_chunk_indices,
_sentence_chunk_map): pro Claim werden die Home-Chunks des/der best-belegenden
Satzes/Chunks ADDITIV in den Kontext-Pool injiziert. KEIN Relabel -- der Judge
entscheidet weiter selbst. Zwei Pfade (Union): (1) semantisch nach max.
Satz-Cosine je Chunk (faengt Cross-Lingual DE<->EN, wo der Chunk-Mittelwert
verwaessert), (2) lexikalisch nach Content-Token-Overlap (faengt starke
Paraphrasen gleicher Sprache, wo die Satz-Cosine niedrig ist -- Cross-Lingual-
Netz gegen die Presence-Scorer-False-Negatives).

F2 -- Titel-/Front-Matter-Chunk-Deprioritisierung: der erste Chunk wird, wenn
front-matter-typisch (Title-Case-Dichte / sehr kurz), im adaptive_k-Ranking
mild gedaempft (nicht entfernt -- Abstracts enthalten echte Belege; F1 holt den
Chunk bei Bedarf zurueck).

Zitat-Marker-robuste Evidence-Normalisierung (_normalize_for_evidence): PyMuPDF
interleaviert hochgestellte Zitat-/Fussnoten-Marker als inline Ziffern-Tokens,
die den woertlichen Beleg-Substring-Abgleich brechen. Kurze freistehende
Ziffern-Tokens (1-3 Stellen) werden symmetrisch entfernt -- match-neutral fuer
echte Zahlen, Jahre bleiben erhalten.

Goldset #254 (test_retrieval_goldset.py): Evidence-in-Pool-Recall
2/10=0.200 (VORHER) -> 10/10=1.000 (NACHHER) in der Referenz-Umgebung.
xfail(strict=True) entfernt, Assertion recall>=0.90 jetzt hart. Negativ-
Kontrolle idx8 (true_hallucination) bleibt gruen.

EVAL_VERSION 4.1 -> 4.2 (Methodik-Aenderung invalidiert den Eval-Cache-
Namespace). quality_history.jsonl unveraendert (read-only); der eval_version-
Skew ist gewollt sichtbar, fairer Vor/Nach-Vergleich braucht Re-Eval-Sweep
(dokumentiert in calibration/retrieval-goldset/README.md). Snapshot in
test_audit_double_call.py auf 4.2 nachgezogen.

Suite: 5833 passed, 3 skipped (pytest -m "not slow"). ruff check + format sauber.

* fix(eval): Rescue-Budget cappen + Masking-Risiko ehrlich dokumentieren/testen (#232)

Nachbesserung nach Kontrolle (NEEDS-FIX): F1 loest die Ueber-Zaehl-Seite, trug
aber ein unbeschriftetes Masking-Risiko auf der Unter-Zaehl-Seite.

1. Ehrliche Doku: Die Floor-/Safety-Behauptung war empirisch falsch. Der
   RESCUE_SENTENCE_MIN_COSINE-Floor (0.45) ist ein OFF-TOPIC-Filter, KEIN
   Halluzinations-Filter -- eine themen-nahe, aber unbelegte Halluzination (max
   Satz-Cosine ~0.64) liegt in derselben Cosine-Range wie echte Cross-Lingual-
   Belege (0.64-0.67) und passiert ihn. Kommentare in eval_quality_v4.py +
   README korrigiert: F1 injiziert (anders als das flag-only Fix B) tatsaechlich
   on-topic Kontext in den Judge-Pool und traegt damit ein Masking-Risiko.

2. Budget-Cap: RESCUE_MAX_CHUNKS=6, an die Dokumentgroesse gekoppelt
   (RESCUE_MAX_DOC_FRACTION=0.30) -> min(6, round(0.30*n_chunks)). Begrenzt die
   injizierte Kontextmenge (Masking-Flaeche). Semantischer + lexikalischer Pfad
   werden vor dem Cap fair verschraenkt (Round-Robin), damit das Cap keinen Pfad
   aushungert. Goldset-Recall mit gebuendeltem Budget neu gemessen: bleibt 10/10.

3. Masking-Richtung testbar: zwei neue Goldset-Anker (Hrastinski) -- eine
   topical_hallucination_probe (fabrizierte, themen-nahe Aussage) und eine
   off_topic_control. Neue Tests belegen ohne Judge: (a) fuer die Probe wird
   on-topic Kontext injiziert, der fabrizierte Beleg landet aber NICHT im Pool,
   und das Budget-Cap greift; (b) Off-Topic bekommt 0 Rescue-Chunks. Beide sind
   aus der FP-Recall-Population ausgeschlossen (Metadaten-Test).

4. Nicht ueberclaimen: klar dokumentiert (Code + README + Test-Docstring), dass
   die volle Masking-Validierung (bleibt hallucination_rate ehrlich?) einen
   Re-Eval-Sweep mit echtem Judge braucht -- Recall-vs-Masking ist ein realer,
   offen benannter Trade-off.

Minor: _normalize_for_evidence-Ziffern-Kommentar praezisiert (match-neutral nur
bei Zahlgleichheit; bei Zahl-Differenz geht Diskriminanz verloren, akzeptabel da
Produktions-Verifikation fuzzy).

Suite: 5834 passed, 3 skipped (pytest -m "not slow"); Goldset 6/6 (-m slow),
Recall 10/10. ruff check + format sauber.

---------

Co-authored-by: TillQuandel <tillq@live.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants