Eval-Harness
Misst die Qualität der KI-Extraktion (Topic-Matching & Filter) gegen handgelabelte Ground-Truth-Fälle. Ziel: Änderungen an Prompts oder Modellen sollen messbar besser/schlechter werden, statt „gefühlt“.
| Suite | Misst | Komponente | Scoring | Cases |
|---|---|---|---|---|
watcher |
Tagesordnung → Thema | council/watcher.py |
Label-Sets | cases_watcher.json |
committee |
Routine-Filter (Inhalt ja/nein) | council/committee_summary.py |
binär | cases_committee.json |
qa |
KI-Frage: findet sie die richtigen Beschlüsse? | council/qa.py |
Label-Sets | cases_qa.json |
Jede Suite hat ein run_<suite>.py; eval/run_all.py fährt alle nacheinander.
Binär: eine Ja/Nein-Entscheidung pro Fall → TP/FP/TN/FN + Precision/Recall/F1.
Label-Sets: pro Fall wird eine Menge von Treffern vorhergesagt (z. B. die
(Thema, TOP)-Paare). Bewertung als Retrieval-Aufgabe: TP = vorhergesagt ∩ erwartet, FP = zu viel, FN = verpasst, aggregiert über alle Fälle. So werden
Über- und Unter-Matching gleichzeitig gemessen.
Ausführen
Abschnitt betitelt „Ausführen“Braucht OPENROUTER_API_KEY in der Umgebung / .env (echte LLM-Calls):
python eval/run_watcher.py # nur watcherpython eval/run_committee.py # nur committeepython eval/run_all.py # alle Suiten + Scoreboard
# Baseline-Workflow:python eval/run_all.py --save # Ergebnis nach eval/results/<suite>/ schreibenpython eval/run_all.py --compare # gegen letzte gespeicherte Baseline diffenpython eval/run_all.py --save --compare # diffen UND neue Baseline speichernErgebnisse landen in eval/results/<suite>/<timestamp>.json. Den jeweils
besten/aktuellen Lauf einchecken, damit --compare Regressionen zeigt.
Neue Fälle hinzufügen
Abschnitt betitelt „Neue Fälle hinzufügen“Am wertvollsten sind Fälle aus echten Fehltreffern (False Positives) und Verpassern (False Negatives) aus dem Produktivbetrieb.
- watcher (
cases_watcher.json):{id, note, session:{ksinr,committee,session_date,session_time,location,agenda_items:[{item_number,title,vorlage_nr,is_public}]}, topics:[{id,name,description}], expected_matches:[[topic_id, item_number], …]}(nicht-öffentliche TOPs werden nie klassifiziert → dürfen nicht inexpected_matchesstehen) - committee (
cases_committee.json):{id, note, committee, session_date, session_time, location, agenda_items:[…], expected:bool}
Nur das Erzeugen einer echten Baseline braucht den OPENROUTER_API_KEY.
Golden-Sets außerhalb des Harness
Abschnitt betitelt „Golden-Sets außerhalb des Harness“Zwei Prüfungen liegen bewusst neben dem Harness, weil sie nicht Treffer-Mengen messen, sondern die Qualität einer Bewertung:
| Skript | Prüft | Reißleine |
|---|---|---|
scripts/eval_ai.py |
Klassifikations-Qualität gegen ein Gold-Set | Regressionsguard vor Prompt-Änderungen |
scripts/eval_impact.py |
Tragweite-Score gegen scripts/golden_impact.json |
Rangkorrelation + Band-Trefferquote; unterschritten → kein Rollout |
scripts/eval_deep_gold.py |
Gründliche Recherche gegen handgeprüfte Pflichtfakten und verbotene Fehlbehauptungen (eval/cases_deep_gold.json); trennt Material (las der Bericht die Belegstelle?) von Bericht (steht der Fakt drin?) |
Abdeckung < 60 %, Kernfakt fehlt oder eine verbotene Behauptung → nicht bestanden |
eval/run_cities_transfer.py |
Einordnung fremder Ratsvorlagen (cities_classify) |
„taugt/taugt nicht“ unter 80 % → Regression |
eval/run_cities_fit.py |
Urteil über Oldenburg (cities_fit) |
Beleg-Disziplin unter 100 % → Regression |
eval/run_cities_sections.py |
Schnitt der Niederschriften (ohne Modell) | unter 75 % der Tagesordnungspunkte mit Abschnitt → Regression |
eval/run_cities_reason.py |
Das „Warum“ aus der Niederschrift (cities_reason) |
eine erfundene Begründung → Regression |
eval/run_cities_idea_fit.py |
Urteil je Idee über Oldenburg (cities_idea_fit) |
eine erfundene Kennung oder ein falsches „vorhanden“ → Regression; Status unter 70 % |
Warum der Städtevergleich zwei Prüfstände hat
Abschnitt betitelt „Warum der Städtevergleich zwei Prüfstände hat“cities_classify gibt einer fremden Vorlage ein Etikett und braucht dafür nur
sie selbst. cities_fit beantwortet dagegen zwei Fragen über Oldenburg —
„hat die Stadt das schon?“ und „lohnt ein Antrag?“ — und bekommt dafür Belege
aus dem eigenen Bestand mit: die nächsten Oldenburger Vorlagen, Treffer der
Volltextsuche und den Themenfeld-Rückblick.
Das macht ein drittes Maß nötig, und es ist das wichtigste: die
Beleg-Disziplin. Jede Kennung, die das Modell nennt, muss ihm vorgelegen
haben, und eine Behauptung über Oldenburg braucht mindestens eine. Ein Urteil,
das sich auf einen erfundenen Beleg beruft, ist nicht ungenau, sondern falsch —
deshalb als einziges Maß mit Schwelle 100 %. Im Betrieb verwirft
council/cities/fit.py solche Antworten und zählt sie.
Beide Prüfstände tragen ihre Fälle samt Belegen bei sich und brauchen keine Datenbank. Das ist keine Bequemlichkeit: Oldenburgs Bestand wächst, und ein Maßstab, der sich unter der Hand ändert, misst nichts.
Die dritte harte Zusage: kein erfundenes „Warum“
Abschnitt betitelt „Die dritte harte Zusage: kein erfundenes „Warum““cities_reason gibt wieder, was in der Niederschrift einer fremden Sitzung
steht — warum ein Rat so entschieden hat. Das ist das Wertvollste, was der
Vergleich zu bieten hat, und deshalb ist eine erfundene Begründung der
teuerste Fehler des ganzen Features: Sie sieht aus wie die beste Information
auf der Seite und ist eine Behauptung über einen echten Ratsbeschluss.
Die Nutzlast trägt dafür ein eigenes Feld: grounded — die Selbstauskunft des
Modells, ob im Abschnitt überhaupt eine Begründung steht. Der häufigere Fall
ist „nein“; die meisten Beschlüsse fallen ohne Aussprache. Dann zeigt die
Karte an dieser Stelle nichts. Der Prüfstand zählt grounded=true ohne
Begründung im Text wie eine erfundene Beleg-Kennung: Schwelle null.
Eine Falle, in die ich selbst getappt bin: Die ersten durchgerechneten
Beispiele im fit-Prompt waren Fälle aus dem Prüfstand. Die Trefferquote stieg
prompt — und maß ab da sich selbst. Die Beispiele sind jetzt erfunden, aber
typisch; keiner davon steht in cases_cities_fit.json.
eval_impact.py ist der erste Schritt des Ops-Workflows für die Tragweite: Nur
wenn das Gate hält, startet der Voll-Backfill. Siehe
Bewertungs-Scores und Betrieb.