Handbücher · Eval Engineering
Build: Die Suite
Lernziele
- Du lässt dein Golden Set mit einem Skript laufen, das Code-Checks und einen Judge kombiniert.
- Du kalibrierst den Judge an zehn eigenen Labels und liest jede Abweichung.
- Du entscheidest, ab wann ein Framework wie promptfoo oder Inspect mehr bringt als die eigene Datei.
Aus Build: Traces lesen hast du golden.jsonl. Diese Lektion macht daraus einen Testlauf: eine einzige Python-Datei, ohne Framework. Code-Checks bewerten alles, was sie bewerten können. Ein enger Judge · engl. LLM as a judgeEin Sprachmodell, das die Ausgabe eines anderen Modells nach einer festen Frage bewertet, etwa ob eine Antwort auf der gefundenen Notiz beruht. Ein Judge ist ein Messgerät und muss gegen eigene Labels geprüft werden.Im Glossar übernimmt den Rest, wie in LLM als Judge beschrieben. Das getestete Produkt ist der Support-Assistent aus app.py. Für dein eigenes Produkt tauschst du run_target gegen den Aufruf deiner Anwendung.
Das Skript
Speichere die Datei als eval_suite.py neben app.py. Sie braucht pip install anthropic (getestet mit 2.8.0) und die Umgebungsvariable ANTHROPIC_API_KEY. Der Judge läuft absichtlich auf einem anderen Modell als das Produkt.
import argparse
import json
import re
import sys
import anthropic
from app import answer # das Produkt unter Test, siehe app.py
client = anthropic.Anthropic()
JUDGE_MODEL = "claude-sonnet-5-5" # bewusst ein anderes Modell als das Produkt
JUDGE_PROMPT = """You are grading one output from an invoicing assistant.
Statement to verify:
{expectation}
Assistant output:
<output>
{output}
</output>
First write one or two sentences of reasoning. Then, on the final line, write
exactly VERDICT: pass if the statement is true of the output, or VERDICT: fail
if it is not. Do not reward length, politeness, or effort.
"""
def run_target(case: dict) -> str:
return answer(case["input"])
def grade_code(output: str, checks: dict) -> tuple[bool, str]:
for needle in checks.get("must_contain", []):
if needle.lower() not in output.lower():
return False, f"missing required text: {needle!r}"
for needle in checks.get("must_not_contain", []):
if needle.lower() in output.lower():
return False, f"contains forbidden text: {needle!r}"
return True, "code checks passed"
def grade_judge(output: str, expectation: str) -> tuple[bool, str]:
response = client.messages.create(
model=JUDGE_MODEL,
max_tokens=4000,
output_config={"effort": "low"},
messages=[{
"role": "user",
"content": JUDGE_PROMPT.format(expectation=expectation, output=output),
}],
)
text = "".join(b.text for b in response.content if b.type == "text")
match = re.search(r"VERDICT:\s*(pass|fail)", text, re.IGNORECASE)
passed = bool(match) and match.group(1).lower() == "pass"
return passed, text.strip().splitlines()[0]
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("golden", help="path to golden.jsonl")
parser.add_argument("--limit", type=int, default=0, help="run only the first N cases")
parser.add_argument("--threshold", type=float, default=0.0, help="exit 1 below this pass rate")
args = parser.parse_args()
with open(args.golden, encoding="utf-8") as f:
cases = [json.loads(line) for line in f if line.strip()]
if args.limit:
cases = cases[: args.limit]
results = []
for case in cases:
output = run_target(case)
if case.get("checks"):
passed, reason = grade_code(output, case["checks"])
else:
passed, reason = grade_judge(output, case["judge_expectation"])
results.append({"id": case["id"], "passed": passed, "reason": reason, "output": output})
print(f"{'PASS' if passed else 'FAIL'} {case['id']} {reason}")
rate = sum(r["passed"] for r in results) / len(results)
print(f"\npass rate: {rate:.0%}")
with open("results.json", "w", encoding="utf-8") as f:
json.dump({"pass_rate": rate, "results": results}, f, indent=2)
if args.threshold and rate < args.threshold:
sys.exit(1)
if __name__ == "__main__":
main()Zeilen 8-12
Das Produkt kommt aus app.py und läuft auf Opus 5.5. Der Judge ist Sonnet 5.5, also ein anderes Modell. So bewertet sich das Produkt nicht selbst.
Starte die Suite mit python eval_suite.py golden.jsonl. Sie gibt eine Zeile pro Fall aus, schreibt results.json und endet mit --threshold bei einem schlechten Lauf mit einem Code ungleich null.
Unsere drei ersten Läufe gegen das Beispielprodukt ergaben 52, 48 und 52 Prozent. Die Code-Checks fanden die zwei häufigsten Fehlerarten in jedem Lauf in 8 von 10 Fällen wieder. Ein Lauf brauchte 66 bis 69 Sekunden für 21 Aufrufe des Produkts und 6 Aufrufe des Judges. Dann haben wir den Prompt in app.py um drei Sätze ergänzt:
"Answer only the question that was asked. Mention other invoices, cancelling or "
"refunds only when the user asks about them. Do not guess beyond the context, "
"not even with 'may' or 'usually'.\n\n"
- $python eval_suite.py golden.jsonl
Die glatten Fälle bestanden auch nach der Änderung. Der Assistent erklärt das Stornieren also weiterhin, wenn jemand danach fragt. 100 Prozent bei 21 Fällen heißt aber nur: Diese Suite findet nichts mehr. Neue Traces zeigen die nächsten Fehler, und damit beginnt die nächste Runde aus Warum Evals.
Den Judge kalibrieren
Bevor du dem Judge traust, bewertest du zehn Ausgaben selbst. Leg dazu labels.jsonl an, eine Zeile pro Ausgabe mit id, output, expectation und deinem eigenen label (true oder false). Die Ausgaben ziehst du aus results.json. Dann misst du die Übereinstimmung:
import json
from eval_suite import grade_judge
with open("labels.jsonl", encoding="utf-8") as f:
labels = [json.loads(line) for line in f if line.strip()]
agree = 0
for row in labels:
verdict, reason = grade_judge(row["output"], row["expectation"])
agree += verdict == row["label"]
if verdict != row["label"]:
print(f"disagree on {row['id']}: judge={verdict} you={row['label']} - {reason}")
print(f"agreement: {agree}/{len(labels)}")
Lies jede Abweichung. Meist ist der Satz in judge_expectation schwammig, manchmal dein eigenes Label. Bei uns kam beides zusammen:
- $python calibrate.py
Beide Abweichungen betrafen denselben Satz: „makes no general claim about where bank details are usually printed“. Die Antwort sagte, das Rechnungsdokument „may“ die Bankdaten enthalten. Wir hatten das als zulässig gelabelt, der Judge nicht. Beim Lesen wurde klar: Auch ein Hinweis mit „may“ geht über den Kontext hinaus, und genau das soll das Produkt lassen. Also haben wir den Erwartungssatz geschärft („does not suggest, not even with ‘may’ or ‘usually’, where else they could be found“) und vier Labels auf false gesetzt.
Zähl die Übereinstimmung pro Klasse. In der ersten Runde stimmte der Judge bei unseren 7 Ausgaben mit true fünfmal zu und bei den 3 mit false dreimal. Rohe 8 von 10 verdecken, dass alle Fehler auf einer Seite lagen. Zehn Labels sind die Version für einen Nachmittag. Das volle Verfahren mit mehr Labels beschreibt LLM als Judge, und die Kurslektion Evals in der Praxis setzt die Grenze bei acht von zehn: Darunter überarbeitest du die Rubrik.
Wann sich ein Framework lohnt
Diese Datei reicht für ein Produkt, einen Judge und einige hundert Fälle. Zu promptfoo greifst du, wenn du mehrere Prompts oder Modelle nebeneinander vergleichen willst, ohne die Matrix selbst zu schreiben. Inspect lohnt sich, wenn du Sandboxen für Agenten, Solver und eine Bewertung pro Schritt brauchst. Beide stellt Eval-Werkzeuge vor. Beide lassen deine Suite in größerem Maßstab laufen. Die Arbeit an der Taxonomie bleibt bei dir.
Merksätze
- Eine Python-Datei genügt für den Anfang: Code-Checks, wo es geht, ein enger Judge für den Rest, ein Exit-Code für die CI.
- Der Judge läuft auf einem anderen Modell als das Produkt. Bei Modellen mit Nachdenken braucht er Platz in
max_tokens. - Vor dem ersten Einsatz bewertest du zehn Ausgaben selbst und liest jede Abweichung.
- Abweichungen zeigen schwammige Erwartungssätze oder uneinheitliche eigene Labels, oft beides.
- 100 Prozent heißt, dass die Suite nichts mehr findet. Neue Traces liefern die nächsten Fälle.
Prüf dich selbst
Denkfrage
Warum läuft der Judge auf einem anderen Modell als das Produkt?
Modelle bewerten Ausgaben ihrer eigenen Familie besser, das beschreibt LLM als Judge als Vorliebe für Eigenes. Ein anderes Modell schwächt diesen Effekt ab. Sonnet 5.5 stammt aber aus derselben Familie wie Opus 5.5. Ob die Wahl trägt, zeigt erst die Kalibrierung an deinen Labels.
Denkfrage
Der Judge stimmt in 8 von 10 Fällen mit dir überein. Reicht das?
Kommt darauf an, wo die zwei Abweichungen liegen. Liegen beide auf einer Seite, wie bei uns bei den Ausgaben mit true, hat der Judge eine systematische Lücke. Lies die beiden Fälle, schärf den Erwartungssatz oder korrigier dein Label, und miss neu.
Prüf dich selbst
Selbst nachweisen
0 von 4 erledigt
Quellen
- Using LLM-as-a-Judge For Evaluation: A Complete Guide
Kalibrierung an Labels einer Fachperson, Raten pro Klasse.
- Effort
effort-Stufen und ihre Standardwerte.