Ihr Agent hat die Aufgabe mit Bravour gemeistert. Wird er sie wieder meistern?

Ein Agent, der einmal erfolgreich ist, kann beim nächsten Lauf scheitern. Gestützt auf die Konsistenzarbeit von IBM Research zu ALTK-Evolve untersucht dieser Artikel, warum Benchmarks mit nur einem Lauf irreführen, wie wiederholte Versuche Varianz bei der Werkzeugnutzung und beim Reasoning offenlegen und welche praktischen Evaluierungsgewohnheiten Teams helfen zu beurteilen, ob sich der Erfolg eines Agenten wiederholen wird.

Vorlesen ist in diesem Browser nicht verfügbar
Ihr Agent hat die Aufgabe mit Bravour gemeistert. Wird er sie wieder meistern?

Tags

Kurze Zusammenfassung

Ein Agent, der einmal erfolgreich ist, kann beim nächsten Lauf scheitern. Gestützt auf die Konsistenzarbeit von IBM Research zu ALTK-Evolve untersucht dieser Artikel, warum Benchmarks mit nur einem Lauf irreführen, wie wiederholte Versuche Varianz bei der Werkzeugnutzung und beim Reasoning offenlegen und welche praktischen Evaluierungsgewohnheiten Teams helfen zu beurteilen, ob sich der Erfolg eines Agenten wiederholen wird.

Ihr Agent hat die Aufgabe gemeistert. Wird er sie noch einmal schaffen?

Der erste Lauf besteht. Der zweite Lauf besteht. Der dritte Lauf erstattet still und leise die falsche Bestellung, und niemand merkt es, bis es ein Kunde merkt. Diese Lücke – zwischen einem Agenten, der eine Aufgabe abschließen kann, und einem Agenten, der sie zuverlässig abschließt – ist der Ort, an dem die meiste Arbeit an Produktions-Agenten tatsächlich stattfindet.

Dieser Artikel handelt davon, diese Lücke zu messen und sie dann zu schließen. Es ist ein praktischer Engineering-Leitfaden: ein kleiner Harness, eine Handvoll Metriken und eine Reihe von Experimenten, die Sie heute Nachmittag ausführen können, um herauszufinden, ob der Erfolg Ihres Agenten eine Fähigkeit oder ein Zufall war.

Der Ausgangspunkt ist eine Frage, die IBM Research im Hugging-Face-Blog gestellt hat: Ihr Agent hat die Aufgabe gemeistert. Wird er sie noch einmal schaffen? (Quelle). Dieser Beitrag rahmt das Problem der Konsistenz von Agenten. Alles Folgende – der Harness, die Befehle, die Metrikdefinitionen – ist Standard-Zuverlässigkeitstechnik, angewendet auf Agenten; es ist keine Zusammenfassung der Methoden dieses Beitrags, und Sie sollten die Quelle direkt lesen, um ihre eigene Einordnung zu erhalten.

Die Demo-Falle

Agenten-Demos sind auf eine einzige erfolgreiche Trajektorie optimiert. Sie wählen eine Aufgabe aus, führen sie aus, sie funktioniert, Sie liefern aus. Die Demo ist eine Stichprobe der Größe eins, und eine Stichprobengröße von eins hat keine Fehlerbalken.

Drei Eigenschaften machen dies bei Agenten schlimmer als bei den meisten Softwareanwendungen:

Agenten sind standardmäßig stochastisch. Sofern Sie nicht ein lokales Modell mit festem Seed auf fester Hardware ausführen, kann derselbe Prompt bei verschiedenen Läufen unterschiedliche Tool-Aufrufe erzeugen. Sampling, Batching und Änderungen auf Anbieterseite verschieben die Verteilung allesamt.

Agenten hängen von der Welt ab. Ein Tool-Aufruf trifft auf eine API, die andere Daten zurückgibt, einen Suchindex, der neu indexiert wurde, eine Datenbank, deren Zeilen sich geändert haben. Zwei Läufe der „gleichen“ Aufgabe sind möglicherweise überhaupt nicht dieselbe Aufgabe.

Agenten scheitern still. Eine Pipeline, die abstürzt, lässt sich leicht debuggen. Ein Agent, der eine plausibel klingende falsche Antwort zurückgibt, sieht auf der Logging-Ebene identisch zu einem Erfolg aus.

Nichts davon bedeutet, dass Agenten unbrauchbar sind. Es bedeutet, dass die Einheit der Evidenz nicht ein Lauf ist. Sie ist k Läufe, und die entscheidende Metrik ist nicht „hat es funktioniert“, sondern „wie oft funktioniert es, und funktioniert es auf dieselbe Weise“.

Was „wieder“ tatsächlich bedeutet

Bevor Sie irgendetwas installieren, seien Sie präzise bei der Aussage, die Sie testen wollen. Es gibt mindestens drei verschiedene Eigenschaften, die Menschen unter „zuverlässig“ zusammenwerfen:

  • Verfügbarkeit – der Agent schließt ab, ohne dass eine unbehandelte Ausnahme auftritt.
  • Korrektheit – die Ausgabe erfüllt einen automatisierten Checker.
  • Konsistenz – wiederholte Ausführungen derselben Aufgabe erzeugen äquivalente Ergebnisse.

Diese können unabhängig voneinander fehlschlagen. Ein Agent kann perfekt konsistent und konsistent falsch sein. Er kann im Durchschnitt korrekt und in der Praxis unbrauchbar sein, weil 20 % der Läufe fehlschlagen. Er kann 100 % der Zeit verfügbar sein und dennoch bei jedem Lauf andere Antworten liefern.

Die Metrik, die die produktionsrelevante Frage erfasst, ist das, was ich pass^k nennen werde: Führen Sie dieselbe Aufgabe k Mal aus und zählen Sie die Aufgabe nur dann als bestanden, wenn alle k Versuche bestehen. Dies ist bewusst härter als pass@1 (die durchschnittliche Erfolgsrate), denn in den meisten Agenten-Deployments ist eine Aufgabe nicht „erledigt“, wenn sie drei von fünf Mal funktioniert. Pass^k bestraft Flakiness direkt, und es verschlechtert sich schnell: Ein Agent mit einer Erfolgsrate von 90 % pro Lauf besteht eine Prüfung mit 10 Läufen nur etwa 35 % der Zeit.

Diese Arithmetik ist das ganze Argument für diesen Artikel. Ein Agent mit 90 % klingt gut und verhält sich schlecht.

Voraussetzungen

Sie brauchen einen funktionierenden Agenten, den Sie als Funktion aufrufen können, und einen Checker, der entscheiden kann, ob eine Ausgabe korrekt ist. Alles andere ist Standard-Tooling.

  • Python 3.10 oder neuer.
  • pip (oder uv, wenn Sie schnellere Installationen bevorzugen).
  • Ein Aufgabensatz: idealerweise 10–50 repräsentative Aufgaben mit bekanntermaßen guten Ergebnissen.
  • Ein programmatischer Checker pro Aufgabe. String-Matching, Schema-Validierung, Unit-Tests für die Ausgabe oder ein kleines Assertion-Skript. Wenn Ihr einziger Checker ein Mensch ist, der die Ausgabe liest, fangen Sie damit an – aber automatisieren Sie zuerst die einfachen Fälle.
  • Optional: Docker, wenn Sie wollen, dass die Laufzeitumgebung auf verschiedenen Maschinen identisch ist.
  • Git, damit jeder Lauf an einen Commit gebunden ist.

Der Checker ist der schwierige Teil. Planen Sie den größten Teil Ihres Aufwands dort ein, nicht im Harness.

Schritt-für-Schritt-Installation

Erstellen Sie eine isolierte Umgebung, damit die Abhängigkeiten Ihres Harness nicht mit denen Ihres Agenten kollidieren.

python -m venv .venv && source .venv/bin/activate

Unter Windows aktivieren Sie stattdessen mit .venv\Scripts\activate. Wenn Sie uv bevorzugen, ist das Äquivalent:

uv venv && source .venv/bin/activate

Installieren Sie die Test- und Analyse-Tooling. pytest führt die Checks aus, pytest-repeat führt einen einzelnen Test N-mal erneut aus, und pandas/numpy übernehmen die Aggregation.

pip install "pytest>=8" pytest-repeat pandas numpy

Frieren Sie die exakte Umgebung ein, damit ein zukünftiger Lauf reproduziert werden kann. Diese Datei ist ein Artefakt, das Sie committen sollten.

pip freeze > requirements.lock

Zeichnen Sie die Codeversion zusammen mit jedem Ergebnisdatensatz auf. Ohne dies ist eine Änderung der Konsistenz nicht zuordenbar.

git rev-parse HEAD > .run-commit && cat .run-commit

Wenn Sie auch die Laufzeit isolieren wollen, bauen Sie einmal ein Image und führen Sie alle Versuche darin aus.

docker build -t agent-under-test:1.0 .

Legen Sie schließlich den Hash-Seed fest. Pythons Hash-Randomisierung ändert in einigen Codepfaden die Iterationsreihenfolge von Dictionaries, was eine echte Quelle für Variation zwischen Läufen beim Tool-Routing ist.

export PYTHONHASHSEED=0

Das ist die gesamte Toolchain. Kein Agenten-Framework erforderlich.

Aufbau des Wiederholbarkeits-Harness

Der Harness hat eine Aufgabe: den Agenten k Mal pro Aufgabe aufrufen, alles aufzeichnen und niemals einen Fehler verwerfen. Legen Sie dies in consistency/harness.py ab.

# consistency/harness.py
from __future__ import annotations

import json
import time
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Callable


@dataclass
class RunRecord:
    task_id: str
    attempt: int
    success: bool
    latency_s: float
    output: str
    error: str | None = None


def run_attempts(
    agent: Callable[[str], str],
    task_id: str,
    task_input: str,
    checker: Callable[[str], bool],
    attempts: int = 10,
    out_path: Path = Path("runs.jsonl"),
) -> list[RunRecord]:
    """Führt eine Aufgabe `attempts` Mal aus und hängt jedes Ergebnis an eine Datei auf der Festplatte an."""
    records: list[RunRecord] = []
    for i in range(attempts):
        t0 = time.perf_counter()
        output, error = "", None
        try:
            output = agent(task_input)
        except Exception as exc:  # aufzeichnen, niemals stillschweigend verschlucken
            error = f"{type(exc).__name__}: {exc}"
        latency = time.perf_counter() - t0
        records.append(
            RunRecord(
                task_id=task_id,
                attempt=i,
                success=bool(error is None and checker(output)),
                latency_s=round(latency, 3),
                output=output,
                error=error,
            )
        )

    with out_path.open("a", encoding="utf-8") as fh:
        for rec in records:
            fh.write(json.dumps(asdict(rec)) + "\n")
    return records

Drei Designentscheidungen sind hier wichtig. Ausgaben werden vollständig gespeichert, denn Sie können einen Flake nicht debuggen, den Sie nicht erfasst haben. Fehler werden aufgezeichnet statt ausgelöst, denn ein Absturz bei Versuch 3 sollte nicht die Daten aus Versuch 1 und 2 verbergen. Ergebnisse werden an JSONL angehängt, sodass ein abgestürzter Prozess nur die aktuelle Aufgabe verliert.

Binden Sie es in einen Test ein, der auf die Verteilung prüft, nicht auf einen einzelnen Lauf.

# tests/test_consistency.py
from consistency.harness import run_attempts


def test_refund_agent_is_stable(agent, refund_checker):
    records = run_attempts(
        agent,
        task_id="refund-order-42",
        task_input="Refund order 42 in full.",
        checker=refund_checker,
        attempts=10,
    )
    failures = [r for r in records if not r.success]
    assert not failures, (
        f"{len(failures)}/10 attempts failed. "
        f"First error: {failures[0].error or failures[0].output[:200]}"
    )

Dieser Test besteht nur, wenn der Agent 10 von 10 besteht. Bei einem echten Agenten wird er beim ersten Ausführen fehlschlagen, und dieser Fehler ist die nützliche Ausgabe.

Anwendungsbeispiele

Beispiel 1: Eine Prompt-Änderung absichern

Sie haben den System-Prompt neu geschrieben. Hilft es? Berechnen Sie pass^k vorher und nachher auf demselben Aufgabensatz.

import json
import pandas as pd

rows = [json.loads(line) for line in open("runs.jsonl", encoding="utf-8")]
df = pd.DataFrame(rows)

summary = (
    df.groupby("task_id")["success"]
      .agg(attempts="size", passes="sum")
      .assign(pass_at_1=lambda d: d["passes"] / d["attempts"])
      .assign(stable=lambda d: d["passes"] == d["attempts"])
)
print(summary)
print("pass^k (all attempts passed):", summary["stable"].mean().round(3))

Führen Sie den Block einmal gegen die runs.jsonl des alten Prompts und einmal gegen die des neuen aus. Wenn sich pass@1 von 0,82 auf 0,85 verbessert, aber pass^k von 0,60 auf 0,45 fällt, erkauft der neue Prompt Durchschnittsleistung mit Varianz – normalerweise der falsche Tausch.

Beispiel 2: Eine Abhängigkeit oder ein Modell-Upgrade per Canary testen

Führen Sie den Harness in derselben Sitzung gegen den Kandidaten und den Incumbent aus und vergleichen Sie dann pro Aufgabe statt aggregiert. Aggregierte Zahlen verbergen sich ausgleichende Änderungen: fünf Aufgaben repariert, fünf Aufgaben kaputt, ununterscheidbarer Durchschnitt.

pivot = (
    df.pivot_table(index="task_id", columns="variant",
                   values="success", aggfunc="mean")
)
pivot["delta"] = pivot["candidate"] - pivot["baseline"]
print(pivot.sort_values("delta").head(10))  # Regressionen zuerst

Aufsteigendes Sortieren zeigt Regressionen zuerst an, und genau das wollen Sie sich ansehen.

Beispiel 3: Einen flaky Fehler reproduzieren

Wenn eine Aufgabe 3 von 10 Mal fehlschlägt, steht die Fehlerausgabe in runs.jsonl, aber die Ursache liegt normalerweise im Trace. Umhüllen Sie Ihre Tool-Aufrufe mit einer Record/Replay-Schicht, damit ein fehlgeschlagener Versuch erneut ausgeführt werden kann, ohne die Live-Welt zu treffen.

import hashlib
import json
from pathlib import Path

CASSETTE = Path("cassettes/tools.json")


def tool_key(name: str, args: dict) -> str:
    payload = json.dumps({"name": name, "args": args}, sort_keys=True)
    return hashlib.sha256(payload.encode()).hexdigest()[:16]


def load_cassette() -> dict:
    return json.loads(CASSETTE.read_text()) if CASSETTE.exists() else {}

Im Replay-Modus schlagen Sie tool_key(name, args) in der Cassette nach und geben die aufgezeichnete Antwort zurück. Im Record-Modus rufen Sie das echte Tool auf und schreiben die Antwort zurück. Dies verwandelt ein nicht reproduzierbares Symptom in einen deterministischen Unit-Test, und es ist der Schritt mit dem höchsten Hebel im gesamten Workflow.

Woher die Varianz kommt

Sobald Fehler reproduzierbar sind, ordnen Sie sie zu. In der Praxis clustert die Inkonsistenz von Agenten an fünf Stellen.

Sampling. Die Temperatur auf null zu setzen reduziert die Sampling-Varianz, garantiert aber nicht identische Ausgaben über Läufe hinweg; Batching auf Anbieterseite, Hardware- und Versionsänderungen können Ergebnisse weiterhin verschieben.

Zeit und Umgebung. Prompts, die das aktuelle Datum, das Locale des Benutzers oder eine Session-ID einbetten, unterscheiden sich konstruktionsbedingt zwischen Läufen. Frieren Sie diese explizit ein.

Nichtdeterminismus von Tools. Live-APIs geben unterschiedliche Daten zurück. Record/Replay entfernt dies als Confounder – es behebt es nicht in der Produktion, aber es sagt Ihnen, ob Ihre Flakiness Ihre eigene ist oder die der Welt.

Retrieval-Drift. Wenn der Agent einen Index abfragt, der zwischen Läufen neu aufgebaut wird, ändert sich der abgerufene Kontext. Erstellen Sie für einen Benchmark-Lauf einen Snapshot des Index.

Kontrollfluss. Mehrstufige Agenten haben viele Entscheidungspunkte. Kleine Fehlerraten pro Schritt summieren sich: Zehn Schritte mit 98 % Zuverlässigkeit pro Schritt ergeben grob 82 % Ende-zu-Ende. Messen Sie pro Schritt, nicht nur Ende-zu-Ende, sonst wissen Sie nicht, wo Sie den Aufwand investieren sollen.

Die Zahlen ehrlich lesen

Kleines k erzeugt verrauschte Schätzungen, und es ist leicht, sie zu überinterpretieren. Verwenden Sie ein Intervall.

import math


def wilson(passes: int, n: int, z: float = 1.96) -> tuple[float, float]:
    """95%-Wilson-Score-Intervall für einen Binomialanteil."""
    if n == 0:
        return (0.0, 1.0)
    p = passes / n
    denom = 1 + z**2 / n
    centre = (p + z**2 / (2 * n)) / denom
    half = (z * math.sqrt(p * (1 - p) / n + z**2 / (4 * n**2))) / denom
    return (round(max(0.0, centre - half), 3), round(min(1.0, centre + half), 3))


print(wilson(8, 10))  # z. B. (0.49, 0.94)

Eine „Erfolgsrate von 80 %“, gemessen über 10 Läufe, ist mit einer wahren Rate von ungefähr 50 % bis 95 % vereinbar. Das ist nicht genug Evidenz, um ein Release daran zu knüpfen. Zehn Versuche pro Aufgabe über 20 Aufgaben – 200 Läufe – ergeben ein viel engeres Bild, zu 20-fachen Kosten. Wählen Sie die Stichprobengröße bewusst und geben Sie sie an, wann immer Sie eine Rate berichten.

Zwei weitere Berichtsgewohnheiten:

Trennen Sie verifizierte Fakten von Interpretation. „Der Agent ist bei Aufgabe X in 4 von 50 Läufen mit Fehler Y fehlgeschlagen“ ist ein Fakt. „Der Retriever ist der Engpass“ ist eine Interpretation, bis Sie sie testen.

Berichten Sie die schlechteste Aufgabe, nicht nur den Mittelwert. Ein pass@1 von 0,95 mit einer Aufgabe bei 0,30 ist ein anderes System als ein gleichmäßiges 0,95. Der Mittelwert verbirgt den Tail, den Benutzer finden werden.

Eine praktische Checkliste

  • Jede Aufgabe hat einen automatisierten Checker, selbst wenn er grob ist.
  • Jeder Lauf wird vollständig protokolliert, einschließlich der fehlgeschlagenen Ausgabe.
  • Jeder Ergebnisdatensatz ist mit einem Commit-Hash und einer gesperrten Abhängigkeitsdatei gekennzeichnet.
  • Pass^k wird zusammen mit pass@1 berichtet, niemals anstelle davon.
  • Tool-Aufrufe werden aufgezeichnet und sind replaybar.
  • Zeit-, Locale- und Session-Identifikatoren werden eingefroren oder injiziert.
  • Regressionen werden pro Aufgabe geprüft, nach Delta sortiert, vor jeder Aggregation.

Fazit

„Es hat funktioniert“ ist eine Hypothese, kein Ergebnis. Die Frage, die der IBM-Research-Beitrag aufwirft – wird er es wieder schaffen? –, ist die richtige, die man jedem Agenten stellen sollte, bevor er die Produktion berührt, und ihre Beantwortung erfordert kein neues Framework. Sie erfordert eine Schleife, einen Checker, ein Log und die Bereitschaft, die pass^k-Zahl zu berichten statt den besten Lauf.

Führen Sie Ihre beste Aufgabe diese Woche zehnmal aus. Wenn sie alle zehn besteht, führen Sie sie zwanzigmal aus. Wenn nicht, haben Sie die Arbeit gefunden – und Sie haben sie in einer Testsuite gefunden statt im Konto eines Kunden.

Quellen