feat(search): deterministischer Backstop für heikle Kategorien

Schliesst die ~5-15 % Erst-Miss bei Fragen, deren veraltete Antwort
konfident-falsch waere (Amtstraeger, Wohnort lebender Personen, "lebt X noch").

- app/pipeline/search_backstop.py: should_force_search() erkennt solche
  Phrasierungen per mehrsprachiger Regex (de/en/nl + etwas fr/es) mit
  Gegenwarts-Schutz ("wer ist", nicht "wer war"). 22 Offline-Tests.
- ToolCallingLLM: bei Treffer wird die Suche DETERMINISTISCH erzwungen.
  Wichtig: NICHT per tool_choice-Forcen (das honoriert das Modell nicht
  zuverlaessig -> beobachtet: ignoriert, veraltete Antwort). Stattdessen
  fuehrt der Wrapper die Suche selbst aus und speist das Ergebnis als
  synthetischen Tool-Turn ein (_inject_forced_search). Metrik
  search_forced_total. Filler/Geduld feuern auch hier.
- Haertung: _chat faengt non-JSON-200 ab (war ein Crash-/Hänger-Ausloeser,
  betrifft auch Produktion).

Live verifiziert: "Wer ist Bundeskanzler?" sucht jetzt 3/3 -> Merz.
Tests: 312 gruen. Doc: §5.7.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-29 22:58:03 +02:00
commit 6cd6e783aa
4 changed files with 148 additions and 6 deletions

View file

@ -0,0 +1,46 @@
"""Offline-Tests für den deterministischen Such-Backstop (heikle Kategorien)."""
import pytest
from app.pipeline.search_backstop import should_force_search
FORCE = [
"Wer ist Bundeskanzler?",
"Wer ist aktuell Bundeskanzler?",
"Who is the president of the United States?",
"Wie heißt der Papst?",
"Wie is de huidige minister-president van Nederland?",
"Wer ist gerade Papst?",
"Wo wohnt Heino?",
"Wo lebt Heino heute?",
"Where does Madonna live these days?",
"Waar woont André Rieu tegenwoordig?",
"Lebt Heino noch?",
"Is Brigitte Bardot still alive?",
"Leeft Willeke Alberti nog?",
]
NO_FORCE = [
"Was ist die Hauptstadt von Frankreich?",
"Erzähl mir einen Witz.",
"Wie alt ist Heino?",
"Wer war der erste Bundeskanzler?", # historisch (Vergangenheit)
"Wann hat Heino Geburtstag?",
"Wie geht es dir heute?",
"Kannst du mir ein Rezept für Kartoffelsuppe geben?",
"Wer hat den Faust geschrieben?",
]
@pytest.mark.parametrize("text", FORCE)
def test_forces_on_heikle_kategorien(text):
assert should_force_search(text) is True, text
@pytest.mark.parametrize("text", NO_FORCE)
def test_no_force_on_harmless(text):
assert should_force_search(text) is False, text
def test_empty_and_none():
assert should_force_search(None) is False
assert should_force_search("") is False