fix(orchestrator): semantischer Fallback am Stage-5-Volltext-Check (#127)#339
Merged
Merged
Conversation
) concept_text_window() ist rein lexikalisch: ein deutscher Planner-Titel auf einer englischen Quelle hat 0 Token-Overlap und wird verworfen, obwohl er planner.filter_hallucinated's #66-Rettungsanker (Stage 4) bereits passiert hat -- bei fast-Profil und kleiner englischer Quelle (Knowles-PDF) fuehrte das zu 0 Draft-Notes trotz thematisch passender Konzepte. Neue Funktion pdf_chunker.semantic_concept_window(): MAX-Cosine zwischen Titel- und Satz-Embeddings (multilinguales MiniLM, generative/embeddings.py) gegen dieselbe Schwelle TITLE_PRESENCE_COSINE_THRESHOLD wie #66 -- Fenster- Geometrie (_iter_word_windows, Seiten-Marker) aus concept_text_window wiederverwendet statt eines zweiten Chunking-Schemas. Faellt vor dem endgueltigen [skip] gezielt darauf zurueck, fail-closed bei Modellfehler oder Score unter Schwelle. run_extractors_per_concept bekommt dafuer den injizierbaren Parameter semantic_window_fn (Default: pdf_chunker-Funktion mit config-Schwelle), analog filter_hallucinated's semantic_presence_fn. Kalibrierung (offline, echte PDF-Volltexte, keine LLM-Calls): die drei realen Skip-Faelle aus Issue #127 (Andragogik, Paedagogisches Modell, Selbstkonzept als selbstgesteuert Lernender) liegen bei 0.72-0.82, themenfremde Kontrollfaelle bei 0.24-0.40 -- Schwelle 0.50 trennt klar. Suite: 6199 passed, 3 skipped, 9 deselected (generative + lib/decision_engine + shared, 337s). ruff check + format sauber.
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.
Closes #127.
Problem
Der Stage-5-Volltext-Check (
run_extractors_per_conceptingenerative/orchestrator.py) ist rein lexikalisch:pdf_chunker.concept_text_window()sucht Titel + Titel-Tokens als Substring/Wortgrenzen-Regex im Volltext. Liefert deutscher Planner-Output ("Andragogik", "Pädagogisches Modell", "Selbstkonzept als selbstgesteuert Lernender") auf einer englischen Quelle (Knowles-PDF, 25 S.) 0 Token-Overlap, wird jedes Konzept mit[skip] '<titel>' nicht im Volltext gefunden (Halluzinations-Schutz)verworfen — beifast-Profil (max_concepts=3) macht das 0 Draft-Notes, obwohl die Konzepte thematisch exakt zur Quelle passen.Der cross-linguale Rettungsanker aus #66 (
planner.filter_hallucinated, semantische MAX-Cosine als OR-Kanal) sitzt eine Stage vorher — die drei Konzepte überleben Stage 4 (Planner), sterben aber am rein lexikalischen Stage-5-Skip, der keinen semantischen Kanal hat.Fix
run_extractors_per_conceptversucht vor dem endgültigen[skip]denselben semantischen Rettungskanal wieplanner.filter_hallucinated(#66): multilinguales MiniLM-Embedding (generative/embeddings.py,paraphrase-multilingual-MiniLM-L12-v2— bereits als multilingual verifiziert, dasselbe Modell wie #66), MAX-Cosine zwischen Titel-Embedding und Satz-Embeddings, gegen dieselbe SchwelleTITLE_PRESENCE_COSINE_THRESHOLD(0.50) — keine neue Magic Number.Neue Funktion
pdf_chunker.semantic_concept_window(full_text, title, threshold, window_words=400):_iter_word_windows, 50%-Stride) wie der lexikalische Scorer inconcept_text_window— beide Funktionen wurden auf einen gemeinsamen Helper refactored, kein zweites Chunking-Schema._default_semantic_presence/fix: cross-lingualer Recall + typ-bewusste Dedup + Body-Redundanz-Flag #66) bleibt die kalibrierte Schwelle 0.50 gültig.model.encode()-Call läuft (statt einem Call pro Fenster — erste Implementierung brauchte dadurch 80-130s pro Dokument, s.u.).(Volltext, window_words)gecacht — mehrere in Stage 5 leer gebliebene Konzepte desselben Laufs (z.B. alle drei Top-Konzepte einesfast-Profils) teilen sich EIN Encode.("", score), der Aufrufer behält den bestehenden[skip]+ Funnel-Event unverändert. Anders als_default_semantic_presence(fail-open, reiner Zusatzkanal) darf dieser Fallback nicht "blind durchwinken", weil das Ergebnis direkt als LLM-Kontext weiterverwendet wird.run_extractors_per_conceptbekommt einen neuen optionalen Parametersemantic_window_fn(Default:pdf_chunker.semantic_concept_windowmit der config-Schwelle) — injizierbar für deterministische Tests ohne ML-Modell-Load, analog zufilter_hallucinatedssemantic_presence_fn.Neue Log-Zeile bei Rettung:
[semantic-window-fallback] '<titel>' lexikalisch nicht gefunden, semantisch gerettet (cosine=0.xx). Der bestehende[skip]-Zweig +_trace_stage_outcome(..., drop_reason="empty_extraction", detail="not in fulltext")bleiben für den echten Ablehnungsfall unverändert.Kalibrierung (offline, keine LLM-Calls, keine Pipeline-Läufe)
Gemessen mit der finalen (satzbasierten) Implementierung gegen echte PDF-Volltexte (
pdftotext-Extraktion, keine Pipeline):Knowles — From Pedagogy to Andragogy (EN, 25 S.) — die drei echten Skip-Funde aus Issue #127:
Porst — Fragebogen: Ein Arbeitsbuch (DE, deutsches Baseline-PDF):
Klare Trennung: echte Konzepte 0.72–0.82, themenfremde Kontrollfälle 0.24–0.40. Die Schwelle 0.50 (identisch zu #66, dort auf n=1 mit Werten 0.575–0.825 vs. 0.357/0.358 gemessen) trennt hier mit vergleichbarem Abstand — Wiederverwendung ohne Anpassung ist tragfähig.
Performance: Erster Fallback-Aufruf pro Dokument (Cache-Miss) kostet ~50-70s CPU (Satz-Encode über alle Fenster-Sätze, dedupliziert). Folgeaufrufe für weitere Konzepte DESSELBEN Volltexts (Cache-Hit) ~0.1-0.2s. Das ist ein einmaliger Kosten pro Lauf im seltenen Totalausfall-Fall (lexikalischer Scan komplett leer) — kein Zusatzaufwand im Normalfall, da der Fallback nur bei leerem
ctextüberhaupt aufgerufen wird (per Test verifiziert:test_semantic_fallback_not_consulted_when_lexically_present).Tests (RED → GREEN)
Neu:
generative/tests/test_stage5_semantic_fallback.py(Orchestrator-Ebene,semantic_window_fn-Injection) + Erweiterunggenerative/tests/test_pdf_chunker.py(semantic_concept_window-Unit-Tests mit deterministischem One-Hot-Fake-Modell + ein@pytest.mark.slow-Test mit echtem Modell):dropped-Event, Log-Signatur[semantic-window-fallback]mit Cosine-Wert[skip]+dropped/empty_extraction-Funnel-Event, kein Extractor-Callsemantic_window_fnwird gar nicht erst aufgerufen (Performance-Garantie im Normalfall)pdf_chunker.semantic_concept_windowmitTITLE_PRESENCE_COSINE_THRESHOLDsemantic_concept_window: Rettung bei Treffer, leer bei Unterschreitung, Cache-Wiederverwendung über mehrere Konzepte desselben Volltexts, fail-closed bei kaputtem Modell, Kurzschluss bei leerem Titel/Text, echtes Modell (cross-lingual,@pytest.mark.slow)test_run_extractors_empty_ctext_emits_stage_outcomeangepasst (No-Op-semantic_window_fninjiziert, damit er ohne ML-Modell schnell/deterministisch bleibt — das Rettungsverhalten selbst ist dediziert getestet)Refactor (verhaltensneutral, per bestehender
test_pdf_chunker.py-Suite verifiziert):concept_text_windows Fenster-Loop und Seiten-Marker-Logik wurden in_iter_word_windows/_page_at_word_map/_prefix_page_markerextrahiert, damitsemantic_concept_windowdieselbe Chunking-Infrastruktur nutzt statt ein zweites Schema zu bauen.Suite:
uv run pytest generative lib/decision_engine/tests shared/tests -q→ 6199 passed, 3 skipped, 9 deselected (337s), keine Fehler.uv run ruff check .→ All checks passed.uv run ruff format --check .→ alle Dateien formatiert. Zusätzlich gezieltuv run pytest generative/tests/test_pdf_chunker.py -m slow -k semantic_window→ 1 passed (echtes Embedding-Modell, cross-lingual, 42s).Scope
Ausschließlich innerhalb
run_extractors_per_concept(orchestrator.py) + additive Erweiterung vonpdf_chunker.py+ Tests.planner.py,db.py,eval_quality_v4.py,eval_dashboard_server.py,extractor.pyunangetastet; Lauf-Ende/Summary/DB-Schreibpfade sowie Frühausstiegs-Pfade vor dem Extractor-Dispatch inorchestrator.pyunangetastet (parallele #330-Arbeit).