Der Agentenkurs · Evals

Evals in der Praxis

Lektion 3/39 Min. LesezeitProfi

Lernziele

  • Du verwandelst echte Fehler deines Agenten in Fälle mit prüfbaren Verhaltenschecks.
  • Du lässt einen Runner ohne Modellaufrufe über gespeicherte Traces laufen und liest sein Ergebnis pro Check.
  • Du kalibrierst einen engen Judge an zehn eigenen Labels und planst eine wöchentliche Nachbewertung.

Diese Lektion ist der kleinste sinnvolle Aufbau: ein Nachmittag, fünf Schritte, eine Datei mit Fällen und ein Runner, keine Infrastruktur. Das Handbuch Eval Engineering baut daraus in drei Schritten die vollständige Pipeline. Alles, was du hier anlegst, übernimmst du direkt dorthin.

Schritt eins: zwanzig Fehler sammeln

Hol dir die jüngsten Läufe deines AgentEin System, in dem ein Sprachmodell seinen nächsten Schritt selbst wählt: Es ruft Werkzeuge auf, wertet das Ergebnis aus und macht weiter, bis das Ziel erreicht ist.Im Glossar und lies, bis du zwanzig echte Fehler hast. Ausgedachte Fehler zählen nicht. Schreib für jeden eine Zeile: Was war die Eingabe, und welches eine Verhalten hätte passieren müssen und ist ausgeblieben? „Hat geantwortet, ohne im Vault zu suchen.“ „Hat erstattet, ohne die Freigabe einzuholen.“ „Hat gemeldet, die Notiz sei angelegt, sie existiert aber nicht.“ Zwanzig Fehler bündeln sich nach unserer Einschätzung meist zu vier bis acht Verhaltensweisen.

Schritt zwei: ein Verhaltenscheck pro Fall

Jeder Fall wird ein Eintrag in YAML: die Eingabe, das erwartete Verhalten in Worten und Assertions über den TraceDie vollständige Aufzeichnung eines Agentenlaufs: Modellrunden, Tool-Aufrufe mit ihren Argumenten, Tool-Ergebnisse und Wiederholungen. Verhaltenschecks lesen den Trace.Im Glossar, die eine Maschine prüfen kann.

- id: refund_1042
  input: "Refund order #1042, customer says it arrived broken"
  expect: looks up the order before replying; asks approval before refund
  check:
    - trace has get_order before send_reply
    - trace has approval_request before refund

Speicher alle zwanzig Fälle als cases.yaml. Die Zeile expect ist für Menschen. Die Zeilen unter check sind der eigentliche Test, jede davon ein Verhaltenscheck · engl. behavioural checkEine enge Ja-Nein-Prüfung, ob auf dem Weg zum Ergebnis ein bestimmter Schritt passiert ist, etwa ob der Agent die Bestellung vor der Antwort nachgeschlagen hat. Er läuft als Code über den Trace.Im Glossar.

Schritt drei: die Checks auf Traces laufen lassen

Speicher den Trace jedes Laufs als traces/<id>.json, eine Liste von Ereignissen mit allen Tool-Aufrufen:

[
  {"type": "tool_call", "name": "get_order", "args": {"order_id": "1042"}},
  {"type": "tool_result", "name": "get_order", "ok": true},
  {"type": "tool_call", "name": "approval_request", "args": {"action": "refund"}},
  {"type": "tool_call", "name": "refund", "args": {"order_id": "1042"}},
  {"type": "tool_call", "name": "send_reply", "args": {"channel": "email"}}
]

Speicher diesen Runner als check_traces.py. Er braucht das Paket PyYAML (pip install pyyaml):

import json
import re
import sys
from pathlib import Path

import yaml


def tool_calls(trace: list) -> list:
    return [e["name"] for e in trace if e.get("type") == "tool_call"]


def check_rule(rule: str, calls: list) -> tuple[bool, str]:
    m = re.fullmatch(r"trace has (\w+) before (\w+)", rule.strip())
    if m:
        first, second = m.group(1), m.group(2)
        if first not in calls:
            return False, f"{first} never called"
        if second not in calls:
            return False, f"{second} never called"
        if calls.index(first) < calls.index(second):
            return True, "ok"
        return False, f"{second} came before {first}"
    m = re.fullmatch(r"trace has (\w+)", rule.strip())
    if m:
        name = m.group(1)
        return name in calls, "ok" if name in calls else f"{name} never called"
    m = re.fullmatch(r"trace lacks (\w+)", rule.strip())
    if m:
        name = m.group(1)
        return name not in calls, "ok" if name not in calls else f"{name} was called"
    return False, f"unparseable rule: {rule!r}"


def main() -> None:
    cases = yaml.safe_load(Path(sys.argv[1]).read_text(encoding="utf-8"))
    trace_dir = Path(sys.argv[2])
    failures = 0
    for case in cases:
        trace_file = trace_dir / f"{case['id']}.json"
        trace = json.loads(trace_file.read_text(encoding="utf-8"))
        calls = tool_calls(trace)
        for rule in case["check"]:
            passed, reason = check_rule(rule, calls)
            print(f"{'PASS' if passed else 'FAIL'}  {case['id']}  {rule}  ({reason})")
            failures += not passed
    print(f"\n{failures} failing check(s)")
    sys.exit(1 if failures else 0)


if __name__ == "__main__":
    main()

Der Runner versteht drei Formen von Regeln: trace has A before B, trace has A und trace lacks A. Bei der Reihenfolge vergleicht er jeweils den ersten Aufruf der beiden Tools. Eine Regel, die er nicht versteht, zählt als Fehler. So fällt ein Tippfehler in cases.yaml sofort auf.

Starte ihn mit python check_traces.py cases.yaml traces/. Er ruft kein Modell auf, bleibt deshalb weit unter dem Budget von fünf Sekunden und endet bei einem Fehler mit einem Code ungleich null, sodass deine CI darauf reagiert. Wir haben den Runner mit Python 3 und PyYAML 6 auf dem Beispiel laufen lassen. Er brauchte 0,03 Sekunden. Danach haben wir den Eintrag get_order aus dem Trace gelöscht und den Runner erneut gestartet. Der Lauf sah so aus:

Den Runner brechen1/7
  1. $python check_traces.py cases.yaml traces/

Berichte das Ergebnis pro Check, genau so, wie der Runner es ausgibt. Eine einzelne Erfolgsquote würde einen gebrochenen Check verdecken.

Schritt vier: einen engen Judge kalibrieren

Manche Verhaltensweisen sieht Code nicht, etwa ob die Antwort auf der gefundenen Notiz beruht. Dafür schreibst du einen 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 mit einer einzigen Frage und einem Ja-Nein-Urteil. Bevor du ihm traust, bewertest du zehn Ausgaben selbst und misst die Übereinstimmung. Das Handbuch Eval Engineering enthält dafür ein Skript mit rund dreißig Zeilen und beschreibt die Verzerrungen, auf die du achten musst.

Stimmt der Judge in weniger als acht von zehn Fällen mit dir überein, überarbeitest du die Rubrik. Den Schwellenwert lässt du, wo er ist. Achte dabei auf beide Klassen, wie in der Lektion Judges und Golden Sets: Unter zehn Labels sind oft nur ein oder zwei schlechte Ausgaben.

Schritt fünf: wöchentlich von Hand bewerten

Zieh jede Woche fünf zufällige echte Läufe und bewerte sie von Hand nach deinen eigenen Erwartungen. Weicht dein Urteil von der Suite ab, wird daraus ein neuer Fall. Ein Fehler, den die Suite nie gesehen hat, wird ein neuer Check. Fünfzehn Minuten pro Woche halten das Golden SetDie feste Sammlung von Testfällen, gegen die alle Evals laufen. Sie enthält gute und schlechte Beispiele und wächst mit jedem Fehler aus dem Betrieb.Im Glossar davon ab, zu altern.

Tipps

  • Code vor Judge. Wo eine Assertion trace has und ein Judge beide funktionieren würden, nimm die Assertion.
  • Stabile Kennungen. Halte die id jedes Falls stabil und mit grep auffindbar. In sechs Monaten suchst du danach.
  • Wackelnde Checks sind Befunde. Wackelt ein Check, verhält sich der Agent an dieser Stelle wirklich nicht deterministisch. Das ist ein Befund über den Agenten, und der Test ist in Ordnung.
  • Ein glatter Fall pro Verhalten. Leg für jedes Verhalten auch einen Fall an, der glatt durchläuft. So merkst du, wenn eine Korrektur übers Ziel hinausschießt.

Probier es selbst aus

Drei Experimente zeigen dir, dass deine Suite misst, was sie messen soll. Arbeite sie mit deinem eigenen Agenten ab.

Selbst nachweisen

0 von 3 erledigt

Weiter geht es mit dem nächsten Modul, Produktion. Wer tiefer einsteigen will, setzt im Handbuch Eval Engineering mit der cases.yaml aus dieser Lektion an: Traces auswerten, die Suite mit kalibriertem Judge bauen und ein Gate in der CI einrichten. Nichts von dem, was du hier gebaut hast, wird weggeworfen.

Merksätze

  • Du startest mit zwanzig echten Fehlern aus den Traces und beschreibst für jeden das eine fehlende Verhalten.
  • Jeder Fall in cases.yaml trägt prüfbare Assertions über den Trace, die Zeile expect ist nur für Menschen.
  • Der Runner liest Traces ohne Modellaufruf, läuft in Sekunden und berichtet pro Check.
  • Einen Judge setzt du nur für das ein, was Code nicht sieht, und kalibrierst ihn vorher an eigenen Labels.
  • Fünfzehn Minuten Handbewertung pro Woche liefern neue Fälle und halten das Golden Set aktuell.

Prüf dich selbst

Denkfrage

Ein Kollege schlägt vor, die zwanzig Fälle aus Schritt eins am Whiteboard auszudenken, das gehe schneller. Was entgegnest du?

Denkfrage

Dein Judge stimmt in sieben von zehn Fällen mit dir überein. Ein Teammitglied will den Schwellenwert des Judges senken. Was tust du stattdessen?

Prüf dich selbst

  1. 1Welche Zeile eines Falls in cases.yaml prüft der Runner?
  2. 2Im Trace fehlt approval_request ganz. Was meldet der Runner für die Regel „trace has approval_request before refund“?
  3. 3Warum läuft der Runner in Sekundenbruchteilen?
  4. 4Ein Check besteht mal und scheitert mal, ohne dass du etwas änderst. Wie liest du das?

Quellen

  1. PyYAML · PyYAML-Projekt, Stand: Nov. 2026

    yaml.safe_load liest cases.yaml.

Dein Fortschritt bleibt nur in diesem Browser gespeichert.