Idee in 30 Sekunden
Agent Tracing zeigt den vollständigen Ausführungspfad eines einzelnen Runs.
Ein Trace besteht aus Spans (spans): Jeder Span ist ein einzelner Schritt, z. B. Reasoning, Tool-Aufruf oder LLM-Generierung.
Das gibt Sichtbarkeit auf Schritt-Ebene und vereinfacht Debugging in Production deutlich.
Hauptproblem
In vielen Systemen wird nur Start und Ende eines Runs geloggt.
Für Agenten reicht das nicht: Zwischen Start und finaler Antwort können Dutzende Schritte liegen. Ohne Tracing ist schwer zu verstehen, was der Agent genau gemacht hat und in welchem Schritt das Problem entstanden ist.
Dieselbe Anfrage kann unterschiedlich ablaufen: andere Schrittzahl, andere Tools, andere Latenz.
Ohne Tracing sind selbst Basisfragen schwer zu beantworten:
- Welcher Schritt war am langsamsten?
- Warum hat der Agent ein Tool erneut aufgerufen?
- Wo genau ist der Fehler aufgetreten?
- Warum sind die Tokens in diesem konkreten Run gestiegen?
Genau deshalb ist Tracing wichtig: Es zeigt den vollständigen Ausführungspfad des Runs und nicht nur das Endergebnis.
Wie es funktioniert
Im Tracing gibt es zwei zentrale Entitäten:
trace— der gesamte Pfad eines Runsspan— ein einzelner Schritt innerhalb dieses Trace
In der Praxis entspricht ein step im Runtime oft einem span, aber nicht immer.
Ein komplexer Schritt kann verschachtelte Spans enthalten, z. B. ein Tool-Aufruf, der intern mehrere HTTP-Requests oder Datenbankabfragen ausführt.
Jeder Span hat typischerweise Basisfelder:
trace_idundrun_idzur Korrelationspan_id(und bei Bedarfparent_span_id)step_type(reasoning,tool_call,llm_generate)latency_msundstatus(ok/error)
Diese Struktur (trace_id, span_id) basiert auf dem OpenTelemetry-Standard (OTel), der das Fundament der meisten modernen Monitoring-Systeme ist.
parent_span_id ist Teil des OTel-Modells für hierarchische Spans und ermöglicht den Aufbau eines Ausführungsbaums (trace tree).
Es gibt spezialisierte Tools für Agent-Tracing (z. B. LangSmith, Langfuse, Arize Phoenix), aber diese Prinzipien sind plattformunabhängig gleich.
Wie ein Trace eines Runs aussieht
Tracing versteht man am schnellsten an einem konkreten Anfragebeispiel.
In realen Systemen enthält jedes Span-Event trace_id, span_id und oft parent_span_id.
Im Beispiel unten sind diese Felder der Einfachheit halber reduziert.
trace_id: tr_9fd2
run_id: run_9fd2
user_query: "Find recent research about battery recycling"
span 1 llm_reasoning 320ms status=ok
span 2 tool_call: search 410ms status=ok
span 3 llm_reasoning 180ms status=ok
span 4 tool_call: fetch 260ms status=error
stop_reason: tool_error
Ein solcher Trace zeigt sofort:
- welche Schritte der Agent durchlaufen hat;
- welche Tools aufgerufen wurden;
- wie lange jeder Schritt gedauert hat;
- wo genau Verzögerung oder Fehler aufgetreten sind.
Traces sind nicht nur für Debugging nützlich. Sie sind auch wichtig für Evaluations und automatische Prüfung von Zwischenschritten: Ohne Traces ist schwer zu prüfen, ob der Agent korrekt gehandelt hat und nicht nur eine richtige finale Antwort geliefert hat.
Wann einsetzen
Tracing ist nicht immer notwendig.
Für einfache Szenarien — ein einzelner LLM-Aufruf ohne Tools und ohne Ausführungsschleife — reicht oft Basis-Logging.
Wenn ein Run aber mehrere Schritte, Tool-Aufrufe oder wiederholte Iterationen enthält, wird es ohne Tracing schwierig:
- Agent-Verhalten zu debuggen;
- Latenz und Kosten zu kontrollieren;
- zu erklären, warum das System eine konkrete Entscheidung getroffen hat.
Implementierungsbeispiel
Unten steht ein vereinfachtes Runtime-Instrumentation-Beispiel für Trace und Spans. Dieser Ansatz wird in LangGraph, CrewAI und in eigenen Agent-Runtimes verwendet. In diesem Beispiel wird der gesamte Run zusätzlich als Root-Span modelliert, und einzelne Agent-Schritte werden als Kind-Spans geloggt.
import contextvars
import logging
import time
import uuid
logger = logging.getLogger("agent")
trace_id_ctx = contextvars.ContextVar("trace_id", default=None)
def start_span(run_id, step_type, tool=None, parent_span_id=None):
span_id = str(uuid.uuid4())
started_at = time.time()
logger.info(
"span_started",
extra={
"trace_id": trace_id_ctx.get(),
"run_id": run_id,
"span_id": span_id,
"parent_span_id": parent_span_id,
"step_type": step_type,
"tool": tool,
},
)
return span_id, started_at
def finish_span(
run_id,
span_id,
step_type,
started_at,
status,
tool=None,
parent_span_id=None,
error=None,
):
logger.info(
"span_finished",
extra={
"trace_id": trace_id_ctx.get(),
"run_id": run_id,
"span_id": span_id,
"parent_span_id": parent_span_id,
"step_type": step_type,
"tool": tool,
"status": status,
"latency_ms": int((time.time() - started_at) * 1000),
"error": error,
},
)
def run_agent(agent, task):
trace_id = str(uuid.uuid4())
run_id = str(uuid.uuid4()) # in Multi-Agent-Systemen kann eine trace_id mehrere run_id enthalten
token = trace_id_ctx.set(trace_id)
logger.info("trace_started", extra={"trace_id": trace_id, "run_id": run_id, "task": task})
stop_reason = "max_steps"
step_count = 0
root_span_id, root_started_at = start_span(run_id, "run", parent_span_id=None)
try:
# in diesem Beispiel sind alle Schritte direkte Kinder des Root-Spans (ohne tiefe Verschachtelung)
for step in agent.iter(task): # step: reasoning oder tool execution
step_count += 1
step_type = step.type # reasoning | tool_call | llm_generate
tool_name = getattr(step, "tool_name", None)
span_id, started_at = start_span(
run_id,
step_type,
tool=tool_name,
parent_span_id=root_span_id,
)
try:
result = step.execute()
finish_span(
run_id,
span_id,
step_type,
started_at,
status="ok",
tool=tool_name,
parent_span_id=root_span_id,
)
except Exception as error:
finish_span(
run_id,
span_id,
step_type,
started_at,
status="error",
tool=tool_name,
parent_span_id=root_span_id,
error=str(error),
)
stop_reason = "tool_error"
raise
if result.is_final:
stop_reason = "completed"
break
finally:
if stop_reason == "completed":
root_status = "ok"
elif stop_reason == "max_steps":
root_status = "error" # in diesem Beispiel zur Vereinfachung als error markiert
else:
root_status = "error"
finish_span(
run_id,
root_span_id,
"run",
root_started_at,
status=root_status,
error=None if root_status == "ok" else stop_reason,
)
logger.info(
"trace_finished",
extra={
"trace_id": trace_id,
"run_id": run_id,
"steps": step_count,
"stop_reason": stop_reason,
},
)
trace_id_ctx.reset(token)
In realen Systemen müssen trace_id und run_id über die gesamte Aufrufkette propagiert werden.
In Python wird dafür oft contextvars genutzt, damit der Identifier nicht manuell an jede Funktion übergeben werden muss.
Zum Beispiel kann ein Span im strukturierten Log so aussehen:
{
"timestamp": "2026-03-21T15:17:00Z",
"event": "span_finished",
"trace_id": "tr_9fd2",
"run_id": "run_9fd2",
"span_id": "sp_21ab",
"parent_span_id": "sp_root_01",
"step_type": "tool_call",
"tool": "search_docs",
"latency_ms": 410,
"status": "ok"
}
Typische Fehler
Auch wenn Tracing bereits eingebaut ist, bleiben Systeme oft schwer zu diagnostizieren, weil die typischen Fehler unten auftreten.
Trace nur auf Run-Ebene, ohne Spans
Wenn nur Start und Ende eines Runs geloggt werden, verliert Tracing fast seinen ganzen Nutzen: Zwischenschritte sind unsichtbar, und Verzögerung oder Fehler lassen sich kaum lokalisieren.
trace_id fehlt in einem Teil der Events
Wenn ein Teil der Logs keine trace_id oder run_id enthält, lassen sich Events nicht zu einem Pfad zusammensetzen.
Dadurch dauert Debugging selbst bei einfachen Incidents deutlich länger.
Tool-Aufrufe werden nicht getraced
Tools sind oft der langsamste Teil eines Runs. Wenn tool calls nicht im Trace landen, ist es schwer, Ursachen für Verzögerungen und Wiederholungen zu finden. In Production kann das Tool-Ausfall oder Tool-Spam maskieren.
Kein stop_reason und kein Span-Status
Ohne stop_reason und status ist schwer zu erkennen, ob ein Run erfolgreich abgeschlossen wurde oder wegen Limits oder Fehlern gestoppt hat.
Dadurch wird Incident-Reproduktion und Alert-Feinjustierung schwieriger.
Selbstcheck
Unten ist eine kurze Checkliste für grundlegendes Tracing 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: Worin unterscheidet sich ein Trace von normalen Logs?
A: Logs beantworten „was ist passiert“. Ein Trace zeigt die Schrittfolge eines Runs und hilft zu verstehen, „wie genau es passiert ist“.
Q: Was sollte man zuerst für Agent-Tracing einführen?
A: Minimum: trace_id, run_id, span_id, Schritt-Typ, latency, status und stop_reason. Das reicht bereits für Basis-Debugging.
Q: Muss man sofort ein externes Tracing-Tool anschließen?
A: Nein. Man kann mit eigener Instrumentation und JSON-Logs starten. Externe Plattformen werden besonders nützlich, wenn Run-Zahl und Team-Zahl wachsen.
Q: Wann kann vollständiges Tracing übertrieben sein?
A: Für einfache Single-Shot-Szenarien ohne Tools und ohne Ausführungsschleife reicht oft Basis-Logging. Vollständiges Tracing wird besonders nützlich, wenn Runs mehrere Schritte, externe Tools oder wiederholte Iterationen enthalten.
Verwandte Seiten
Weiter zum Thema:
- Observability für KI-Agenten — Basismodell von traces, logs und metrics.
- Verteiltes Agent-Tracing — wie man Traces über mehrere Services verbindet.
- Debugging von Agent-Runs — wie man problematische Runs Schritt für Schritt analysiert.
- Agent-Logging — welche Events in Logs erfasst werden sollten.
- Agent-Metriken — welche Production-Indikatoren beobachtet werden sollten.