Agent Tracing: wie man Agent-Entscheidungen nachverfolgt

Agent Tracing zeichnet jeden Schritt als Trace und Spans auf, macht Reasoning und Tool-Aufrufe sichtbar und hilft beim Debugging problematischer Runs in Production.
Auf dieser Seite
  1. Idee in 30 Sekunden
  2. Hauptproblem
  3. Wie es funktioniert
  4. Wie ein Trace eines Runs aussieht
  5. Wann einsetzen
  6. Implementierungsbeispiel
  7. Typische Fehler
  8. Trace nur auf Run-Ebene, ohne Spans
  9. trace_id fehlt in einem Teil der Events
  10. Tool-Aufrufe werden nicht getraced
  11. Kein stop_reason und kein Span-Status
  12. Selbstcheck
  13. FAQ
  14. Verwandte Seiten

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 Runs
  • span — 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_id und run_id zur Korrelation
  • span_id (und bei Bedarf parent_span_id)
  • step_type (reasoning, tool_call, llm_generate)
  • latency_ms und status (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.

TEXT
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.

PYTHON
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:

JSON
{
  "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:

⏱️ 7 Min. LesezeitAktualisiert 21. März 2026Schwierigkeit: ★★★
Integriert: Production ControlOnceOnly
Guardrails für Tool-Calling-Agents
Shippe dieses Pattern mit Governance:
  • Budgets (Steps / Spend Caps)
  • Tool-Permissions (Allowlist / Blocklist)
  • Kill switch & Incident Stop
  • Idempotenz & Dedupe
  • Audit logs & Nachvollziehbarkeit
Integrierter Hinweis: OnceOnly ist eine Control-Layer für Production-Agent-Systeme.

Autor

Nick — Engineer, der Infrastruktur für KI-Agenten in Produktion aufbaut.

Fokus: Agent-Patterns, Failure-Modes, Runtime-Steuerung und Systemzuverlässigkeit.

🔗 GitHub: https://github.com/mykolademyanov


Redaktioneller Hinweis

Diese Dokumentation ist KI-gestützt, mit menschlicher redaktioneller Verantwortung für Genauigkeit, Klarheit und Produktionsrelevanz.

Der Inhalt basiert auf realen Ausfällen, Post-Mortems und operativen Vorfällen in produktiv eingesetzten KI-Agenten-Systemen.