Idee in 30 Sekunden
Debugging von Agent-Runs hilft, vom Symptom zur Ursache zu kommen: was genau kaputt ist, in welchem Schritt, und warum.
Dafür müssen Tracing, Logs und Metriken eines problematischen Runs zusammengeführt werden.
Ohne das sieht das Team oft nur den finalen Fehler, aber nicht den gesamten Weg dorthin.
Das Hauptproblem
In Agent-Systemen hat ein Incident selten nur eine offensichtliche Ursache.
Der finale Fehler kann nur eine Folge sein: Das eigentliche Problem kann früher begonnen haben, zum Beispiel durch einen langsamen tool call, ein fehlerhaftes retry oder eine Regression nach dem Release. Ohne systematisches debugging ist das schwer schnell zu lokalisieren.
Als Nächstes schauen wir, wie man diese Signale liest und stabil root cause findet.
In Production sieht das oft so aus:
- in Logs gibt es viele Events, aber keine klare Reihenfolge;
- die Ursache vermischt sich mit Begleitfehlern;
- der Incident wurde "gefixt", kommt aber nach dem Release zurück;
- MTTR steigt, weil das Team den Incident-Kontext jedes Mal neu aufbauen muss.
Genau deshalb sollte debugging eines Runs ein eigener operativer Prozess sein und keine manuelle Suche nach dem "ersten error".
So funktioniert es
Ein praktischer debugging run besteht meist aus drei Ebenen:
- Run-Kontext (
run_id,trace_id,release,workflow); - Evidence -> Analyse (
spans,logs,metrics,stop_reason); - Entscheidung (Hypothese -> Fix -> Verifikation via replay und Tests).
Diese Ebenen beantworten: wo das Problem liegt, warum es entstanden ist und ob der Fix es wirklich entfernt. Tracing zeigt den Pfad, Logs zeigen Ereignisse, und Metriken zeigen Ausmaß und Trend.
Viele Logs != schnelles debugging. Geschwindigkeit entsteht nicht aus Datenmenge, sondern aus Korrelation rund um einen Run.
Typische Production-Signale für debugging runs
| Signal | Wo prüfen | Warum es wichtig ist |
|---|---|---|
| first_error_span | Tracing | Punkt finden, an dem der Fehler zuerst erschien |
| slowest_span | Tracing + Metriken | Bottleneck-Kandidat (muss verifiziert werden) |
| stop_reason | Log run_finished | verstehen, wie der Run beendet wurde |
| error_class | tool_result / llm_result Logs | Timeout von Logikfehler trennen |
| repeated_tool_calls | tool_call Logs + tool metrics | wiederholte Aufrufe erkennen (loops, retries, tool spam) |
| run_latency_p95 | Metriken | prüfen, ob Incident bereits systemisch ist |
| release_diff | Release-Vergleichsdashboard | Regression nach Änderungen erkennen |
| synthetic_run_status | health checks | Auswirkung auf kritischen workflow prüfen |
Damit debugging stabil bleibt, werden diese Signale typischerweise nach release, workflow, model und tool segmentiert.
Wichtig: keine high-cardinality Labels (run_id, request_id, user_id) in Metriken aufnehmen. Dafür sind Logs und Tracing besser geeignet.
Wie man den Debugging-Layer liest
Welcher Run ist fehlgeschlagen -> in welchem Schritt -> warum genau. Diese drei Ebenen sollte man immer gemeinsam betrachten.
Wichtig ist, auf Trends und Release-Deltas zu schauen, nicht nur auf ein einzelnes Notfall-Event.
Typische Signalkombinationen:
first_error_span=tool_call+tool_error_rate↑ -> Problem in einem konkreten tool-layer;run_latency_p95↑ +tool_latency_p95stabil -> wahrscheinlich Problem in LLM oder runtime-Logik;repeated_tool_calls↑ +stop_reason=max_steps-> Agent steckt in einer Schleife;error_rate↑ nach Release + positivesrelease_diff-> Änderungsregression, kein einmaliger Incident;synthetic_run_status=fail+health_score↓ -> Problem beeinflusst bereits kritischen workflow.
Wann verwenden
Ein formaler debugging-flow ist nicht immer nötig.
Für ein einfaches single-shot Szenario ohne tools reichen manchmal Basis-Logging und manuelle Fehlerprüfung.
Ein systematischer debugging-Ansatz wird aber kritisch, wenn:
- Runs mehrere reasoning-Schritte und tool calls enthalten;
- Incidents Latenz, Kosten oder SLO beeinflussen;
- Releases häufig kommen und Regressionen früh erkannt werden müssen;
- es einen on-call Prozess gibt und ein planbares MTTR nötig ist.
Implementierungsbeispiel
Unten ist eine vereinfachte Funktion, die Evidence für einen Run sammelt und eine Basis-Hypothese bildet. Das ersetzt kein vollständiges incident tooling, zeigt aber einen praktischen debugging-Prozess.
from collections import Counter
def debug_run(run_id, trace_events, log_events, debug_metrics_snapshot):
run_spans = sorted(
[s for s in trace_events if s.get("run_id") == run_id],
key=lambda s: s.get("started_at_ms", 0),
)
run_logs = sorted(
[e for e in log_events if e.get("run_id") == run_id],
key=lambda e: e["event_seq"],
)
first_error_span = next((s for s in run_spans if s.get("status") == "error"), None)
# slowest_span kann None sein, wenn der Run keine spans enthält
slowest_span = max(run_spans, key=lambda s: s.get("latency_ms", 0), default=None)
stop_reason = "unknown"
for event in reversed(run_logs):
if event.get("event") == "run_finished":
stop_reason = event.get("stop_reason", "unknown")
break
seen_signatures = set()
repeated_tools = Counter()
unclassified_tool_calls = 0
for event in run_logs:
if event.get("event") != "tool_call":
continue
if not event.get("tool") or not event.get("args_hash"):
unclassified_tool_calls += 1
continue
signature = (event["tool"], event["args_hash"])
if signature in seen_signatures:
repeated_tools[event.get("tool")] += 1
else:
seen_signatures.add(signature)
hypotheses = []
if first_error_span and first_error_span.get("step_type") == "tool_call":
hypotheses.append("Wahrscheinlicher Ausfall im tool-layer: Tool-Verfügbarkeit und timeout policy prüfen.")
if repeated_tools:
hypotheses.append("Wiederholte tool calls erkannt: dedupe/cache und stop conditions prüfen.")
if slowest_span and debug_metrics_snapshot.get("run_latency_p95_ms", 0) > debug_metrics_snapshot.get("slo_latency_ms", 2500):
hypotheses.append("p95 latency liegt über SLO: Bottleneck über slowest_span lokalisieren.")
if debug_metrics_snapshot.get("release_error_rate_delta", 0) > 0:
hypotheses.append("error_rate ist nach Release gestiegen: Änderungen in prompt/runtime/tool routing prüfen.")
return {
"run_id": run_id,
"first_error_span": first_error_span,
"slowest_span": slowest_span,
"stop_reason": stop_reason,
"repeated_tools": dict(repeated_tools),
"unclassified_tool_calls": unclassified_tool_calls,
"hypotheses": hypotheses,
}
Ausführbare Testdaten: Timeout und wiederholter Tool-Aufruf
Speichere debug_run oben und den folgenden Code gemeinsam als debug_demo.py. Starte python3 debug_demo.py mit Python 3.10+: ohne zusätzliche Bibliotheken, Schlüssel oder Netzwerkanfragen. event_seq ordnet Ereignisse innerhalb eines Runs; die Runtime muss diese Reihenfolge vergeben, nicht der Log-Eingang. Das Beispiel liefert die Einträge absichtlich in umgekehrter Reihenfolge.
run_id = "demo-timeout"
spans = [{"run_id": run_id, "step_type": "tool_call",
"started_at_ms": 100, "latency_ms": 2000, "status": "error"}]
logs = [
{"run_id": run_id, "event_seq": 3, "event": "run_finished",
"stop_reason": "tool_error"},
{"run_id": run_id, "event_seq": 2, "event": "tool_call",
"tool": "create_ticket", "args_hash": "fixture-a"},
{"run_id": run_id, "event_seq": 1, "event": "tool_call",
"tool": "create_ticket", "args_hash": "fixture-a"},
]
report = debug_run(run_id, spans, logs, {})
assert report["stop_reason"] == "tool_error"
assert report["repeated_tools"] == {"create_ticket": 1}
assert debug_run("other-run", spans, logs, {})["repeated_tools"] == {}
unknown = [{"run_id": run_id, "event_seq": i, "event": "tool_call",
"tool": "create_ticket"} for i in range(2)]
report = debug_run(run_id, [], unknown, {})
assert report["repeated_tools"] == {}
assert report["unclassified_tool_calls"] == 2
print("debug fixture: 5 passed")
Erwartete Ausgabe: debug fixture: 5 passed. Wiederholt wird die Analyse aufgezeichneter Ereignisse, nicht die Agent-Ausführung. Der wiederholte Aufruf von create_ticket muss untersucht werden: Ein Timeout beweist nicht, dass der erste Versuch kein Ticket angelegt hat. Gleiche operation_id mit dem externen System ab; teste die Runtime mit aufgezeichneten Tool-Antworten und ohne Schreibzugriff auf Produktion. AWS erklärt Idempotenz für sichere Wiederholungen.
args_hash muss eine stabile Signatur normalisierter Argumente sein, ohne Geheimnisse in Logs. Gleiche Argumente beweisen keine gleiche Absicht. Fehlt die Signatur, zählt das Beispiel einen nicht klassifizierten Aufruf statt eines Duplikats.
Wann die Belege einen Fix stützen
Bewahre Runtime-Version, Prompt, Modell, bereinigte Eingaben und Tool-Ergebnisse auf. Vergleiche dasselbe Szenario vor und nach der Änderung. Ergänze einen Regressionstest, der Ergebnis und Anzahl externer Aktionen prüft, nicht nur das Ausbleiben einer Exception.
Der zuerst gestartete Error-Span ist ein Kandidat, keine garantierte Grundursache: Ein Parent-Span kann nur einen Child-Fehler widerspiegeln. Replay deckt das aufgezeichnete Szenario ab, nicht jede Race Condition oder externe Serviceänderung. Ist ein Incident nicht reproduzierbar, halte die Unsicherheit fest und prüfe die Hypothese mit zusätzlicher Telemetrie.
Es folgt ein illustrativer Debugging-Snapshot:
| Run | first_error_span | slowest_span | stop_reason | Fazit |
|---|---|---|---|---|
| run_9fd2 | tool_call: search_docs | tool_call: search_docs (1.8s) | tool_error | tool degradiert + retries |
| run_a113 | llm_generate | llm_generate (2.4s) | step_error | Modellfehler nach Release |
| run_d77c | — | reasoning (3.1s) | max_steps | Loop ohne expliziten Fehler |
Investigation
Wenn ein Incident-Signal auslöst:
run_id,trace_id,releaseund betroffenen workflow festhalten;- im Tracing
first_error_spanundslowest_spanfinden; - in Logs
stop_reason,error_class,repeated_tool_callsprüfen; - in Metriken Ausmaß bestätigen (Spike oder Trend) und Release-Deltas vergleichen.
Typische Fehler
Auch mit observability scheitert debugging oft an typischen Fehlern.
Analyse beginnt bei irgendeinem Log-Fehler
Ohne Bindung an eine konkrete run_id mischt das Team Symptome aus verschiedenen Incidents.
So wird es schwer, lokales Problem von kaskadierendem Ausfall zu trennen.
Keine Korrelation von trace + logs + metrics
Wenn Tracing, Logs und Metriken getrennt betrachtet werden, widersprechen sich Hypothesen oft. Dadurch steigt MTTR selbst bei einfachen Tool-Ausfällen.
repeated calls und stop_reason werden ignoriert
Ohne diese Signale übersieht man leicht loops und retry storms. Das verdeckt oft die frühe Phase von Tool-Spam.
Kein Vergleich mit vorherigem Release
Ohne release_diff sieht das Team nicht, ob das Problem nach Änderungen aufgetreten ist.
Dadurch bleibt Regression länger in Production.
Incident wird ohne replay und Verifikation geschlossen
Ein Fix kann nur das Symptom entfernen, nicht die Ursache. Das erhöht das Risiko einer wiederholten teilweisen Störung.
Selbst-Check
Unten ist eine kurze Baseline-Checkliste für debugging-flow vor dem Release.
Fortschritt: 0/9
⚠ Grundlegende Observability fehlt
Das System wird in production schwer zu debuggen sein. Starten Sie mit run_id, structured logs und tracing von tool calls.
FAQ
Q: Womit beginnt debugging eines problematischen Runs?
A: Starte mit run_id und trace_id: finde first_error_span, prüfe stop_reason und bestätige dann das Ausmaß in den Metriken. first_error_span ist der schnellste Weg zum Fehlerpunkt.
Q: Was ist wichtiger für debugging: Tracing oder Logs?
A: Beides zusammen: Tracing zeigt den Schrittpfad, Logs liefern Event-Details (error_class, args_hash, policy decision).
Q: Wie erkenne ich Release-Regression statt einmaligem Ausfall?
A: Vergleiche error_rate, latency_p95, repeated_tool_calls zwischen Releases. Wenn das Signal nach dem Release stabil schlechter ist, ist es Regression.
Q: Was ist das Minimum an Daten für Debugging in 10-15 Minuten?
A: Minimum: run_id, trace_id, first_error_span, stop_reason, error_class, latency_p95 und Release-Kontext.
Verwandte Seiten
Weiter zum Thema:
- Agent-Tracing — wie man den Pfad eines Runs Schritt für Schritt sieht.
- Agent-Logging — welche Events für Incident-Analyse nötig sind.
- Agent-Metriken — wie man einmaligen Ausfall von Trend trennt.
- Agent Health Checks — frühe Degradationssignale vor dem Incident.
- Fehler-Alerting für KI-Agenten — wie Investigation rechtzeitig gestartet wird.