Neu Kostenlos registrieren und 10 Aufrufe gratis nutzen. Bis zu 1 $, ohne Kreditkarte.

LLM-Agenten beobachten: Traces, Sessions, 10 Probleme reproduziert

Inhalt
  1. Was sind Traces, Spans und Sessions bei einem LLM-Agenten?
  2. Welche Ansicht beantwortet welche Frage?
  3. Warum ist ein LLM-Agent langsam?
  4. Warum ist ein LLM-Agent teuer?
  5. Wie debuggt man einen LLM-Agenten nach einer Nutzerbeschwerde?
  6. Kann man einen fremden Coding-Agenten nachverfolgen?
  7. Kann ein Modell die Traces für euch lesen?
  8. Was sollte jeder Agent protokollieren?
  9. Was deckt Tracing auf Gateway-Seite nicht ab?
  10. FAQ
  11. Was bedeutet Beobachtbarkeit bei LLM-Agenten?
  12. Weiterführende Artikel

Die Beobachtbarkeit eines LLM-Agenten beginnt damit, seine Modellaufrufe auf zwei Ebenen zusammenzufassen: Ein Trace zeigt einen Durchlauf des Agenten auf einer Zeitachse. Eine Session umfasst ein Gespräch samt Gesamtkosten. Mit drei Request-Headern pro Aufruf kann ein LLM-Gateway beides aus seinem Request-Log erzeugen, ganz ohne Tracing-SDK. Das Gateway sitzt als Proxy zwischen eurem Code und den Modellanbietern und protokolliert jeden Request. Wir haben diesen Aufbau mit einem kleinen Support-Agenten und mit Claude Code getestet und zehn typische Agentenprobleme reproduziert: drei, die einen Agenten langsam machen, vier, die ihn teuer machen, und drei, die zu Fehlern führen.

TL;DR

  • Drei Request-Header (X-Trace-Id, X-Span-Id, X-Session-Id) machen aus dem Request-Log des Synthorai-Gateways Traces und Sessions, ohne Tracing-SDK.
  • Drei unabhängige Prüfungen durch Subagenten parallel auszuführen, senkte ihre mediane Laufzeit bei gleichen Kosten von 3.7 s auf 1.8 s.
  • Nachdem wir bei GPT-6 Luna den Zeitstempel aus der ersten Prompt-Zeile entfernt hatten, stieg die Cache-Trefferquote von 0% auf 73%, ausgehend von einem leeren Cache.
  • Ein Durchlauf mit Claude Code kostete $0.1728. Die jeweils ersten Aufrufe seiner drei Agenten machten davon 72% aus.

Dieser Beitrag gibt einen Überblick über eine siebenteilige Artikelserie. Jeder der folgenden Abschnitte fasst einen Artikel mit seinen Messwerten zusammen und verlinkt auf Code und Screenshots. Alle Durchläufe stammen vom 10. und 11. Oktober 2026. Die Latenzwerte sind Mediane aus fünf Durchläufen; alle anderen Werte stammen aus jeweils einem Durchlauf. Der Code liegt unter github.com/synthorai-io/use-cases.

Was sind Traces, Spans und Sessions bei einem LLM-Agenten?

Ein Trace umfasst alles, was ein Agent zur Bearbeitung einer Nutzernachricht oder Aufgabe tut. Ein Span ist ein einzelner Schritt innerhalb dieses Traces. Eine Session ist das gesamte Gespräch und besteht aus mehreren Traces. Bei unserem Support-Agenten bildet ein Kundengespräch mit zwei Gesprächsrunden eine Session mit zwei Traces. Jeder Trace enthält fünf oder sechs Modellaufrufe. Einige davon stammen von einem Subagenten: einem zweiten Agenten, den der Hauptagent für eine Teilaufgabe startet, mit eigenem Prompt und oft einem günstigeren Modell.

BegriffUmfangHeaderDenselben Wert senden bei
Traceein Durchlauf des AgentenX-Trace-Idjedem Aufruf des Durchlaufs
Spanein Modellaufruf im DurchlaufX-Span-Idnur diesem Aufruf
Sessionein GesprächX-Session-Idjedem Aufruf des Gesprächs
Agentwelcher Subagent den Aufruf ausgeführt hatX-Agent-Idjedem Aufruf dieses Subagenten
LabelsNutzer, Funktion, ReleaseX-Synthorai-Metadataallen Aufrufen, die ihr gruppieren oder filtern wollt

Die IDs sind frei wählbare Strings mit jeweils bis zu 128 druckbaren ASCII-Zeichen. Labels sind ein flaches JSON-Objekt mit String-Werten, höchstens 10 pro Request und bis zu 128 Bytes pro Wert. Das Gateway gibt die IDs in der Response zurück und leitet keinen dieser Header an den Modellanbieter weiter. Im OpenAI Python SDK stehen die Header für einen einzelnen Request in extra_headers:

resp = client.chat.completions.create(
    model="gpt-6-luna",
    messages=messages,
    extra_headers={
        "X-Session-Id": session_id,   # one per conversation
        "X-Trace-Id": trace_id,       # one per user message
        "X-Span-Id": "plan",          # one per call
        "X-Synthorai-Metadata": '{"feature":"support-agent","_user":"u_1002"}',
    },
)

Sendet euer Agent bereits Trace-IDs über ein anderes Tool, müsst ihr möglicherweise nichts ergänzen. Fehlt eine der drei IDs im Request, liest das Gateway den entsprechenden Header von OpenTelemetry, LiteLLM, Langfuse, Open WebUI, Claude Code oder der OpenAI Codex CLI. OpenTelemetry ist der offene Standard für Tracing; sein Header traceparent enthält eine Trace-ID. Einen LLM-Agenten mit 3 HTTP-Headern nachverfolgen baut den Demo-Agenten Schritt für Schritt auf: ein Planungsaufruf mit GPT-6 Luna, zwei lokale Tools, drei parallele Prüfungen mit DeepSeek V4.1 Flash und ein Aufruf für die Antwort. Im Demo-Durchlauf entstanden 21 Modellaufrufe für $0.0017.

Welche Ansicht beantwortet welche Frage?

Jede Frage aus der Serie lässt sich an einer bestimmten Stelle in der Konsole oder durch einen bestimmten externen Zugriff auf das Log beantworten. Die Konsole zeigt jeden Trace als Wasserfalldiagramm: ein Balken pro Modellaufruf auf einer gemeinsamen Zeitachse. Parallele Aufrufe überlappen sich, voneinander abhängige Aufrufe bilden eine Treppe.

FrageWo nachsehen?Artikel
Warum dauerte dieser Durchlauf 8 Sekunden?Trace-Zusammenfassung: Zeit im Gateway gegenüber Zeit in der AppLatenz von Agenten
Welche Gesprächsrunde war am teuersten?Session-Seite, eine Zeile pro TraceKosten von Agenten
Warum liegt die Cache-Trefferquote bei 0%?Spalte für Cache-Treffer auf der Session-SeiteKosten von Agenten
Was ist gestern mit dem Request dieses Nutzers passiert?Label-Suche in der Traces-ListeAgenten debuggen
Welcher Subagent von Claude Code hat die Kosten verursacht?Agententabelle auf der Session-SeiteClaude-Code-Kosten nach Subagent
Kann ich diese Fragen in einem Chat stellen?MCP-Connector in ClaudeClaude zu euren LLM-Logs befragen
Kann mein eigenes Programm das Log untersuchen?MCP-Tools oder REST APIEurem Agenten Tools für Logs geben

Jede Trace-Seite hat außerdem einen Compare-Button. Er stellt zwei Traces auf einer Seite gegenüber, ordnet die Aufrufe nach Namen zu und zeigt beide Wasserfalldiagramme auf einer Zeitachse. Jede Korrektur in der Serie wurde so geprüft: mit einem Vergleich des Durchlaufs vor und nach der Änderung.

Warum ist ein LLM-Agent langsam?

Beginnt mit der Zeitaufteilung oben im Trace. Sie zerlegt die Gesamtdauer, also die Wartezeit vom ersten Request bis zur letzten Response, in zwei Teile: Zeit im Gateway, während mindestens ein Modell-Request offen war, und Zeit in eurer App, während nur euer Code lief. Im Demo-Trace entfielen von 8.5 s Gesamtdauer 7.0 s auf das Gateway und 1.6 s auf die App. Die Modellaufrufe waren also der richtige Ansatzpunkt. Der Artikel zur Latenz reproduzierte drei Ursachen mit jeweils fünf Durchläufen:

UrsacheIm Trace sichtbar alsKorrekturMedian vorherMedian nachher
Unabhängige Aufrufe werden nacheinander gesendettreppenförmig angeordnete Balkengemeinsam mit asyncio.gather ausführen, womit Python auf mehrere Aufrufe gleichzeitig wartet3.7 s1.8 s
Langsamer Code zwischen den Aufrufenlange Zeile your appdie vier Abfragen parallel ausführen11.0 s9.2 s
Reasoning für einzeilige Antwortenviel mehr Ausgabe-Token als für die Antwort nötigreasoning_effort="none"25.6 s für fünf Antworten10.2 s

Beim langsamen Code dauerten die Modellaufrufe in beiden Durchläufen ungefähr gleich lang. Geändert hatte sich die Zeit in der App: Im gezeigten Vergleich sank die Zeile your app von 4.6 s auf 1.3 s. Ein anderes Modell kann diesen Teil nicht verkürzen. Die Fehlersuche beginnt deshalb im eigenen Profiler oder bei den OpenTelemetry-Spans. Der dritte Fall lief mit GPT-6 Luna. Der Request-Parameter reasoning_effort steuert, wie viel das Modell vor der Antwort nachdenkt. Mit none sank die Zahl der Ausgabe-Token für die fünf Antworten von 1,150 auf 190. Warum ist mein LLM-Agent langsam? zeigt den Code und die Traces im direkten Vergleich für alle drei Fälle.

Warum ist ein LLM-Agent teuer?

Sortiert zunächst die Traces-Liste nach Kosten. Öffnet dann die teuerste Session und prüft für jede Gesprächsrunde die Spalten für Cache-Treffer und Eingabe. Die Cache-Trefferquote gibt an, welcher Anteil der Eingabe-Token aus dem Prompt-Cache des Anbieters gelesen wurde. Bereits bekannte Prompt-Präfixe werden dort nur mit einem Bruchteil des Eingabepreises berechnet. Der Artikel zu den Kosten reproduzierte vier Ursachen. Weil die Demo klein ist, liegen die Beträge unter einem Cent. Übertragbar sind vor allem die Verhältnisse:

UrsacheWo sichtbar?VorherNachher
Zeitstempel in der ersten Prompt-ZeileCache-Trefferquote bleibt bei 0%0% aus dem Cache73% aus dem Cache, Eingabekosten um 66% gesunken
Verlauf sendet jedes Tool-Ergebnis erneutEingabe wächst mit jeder Gesprächsrunde24,546 Eingabe-Token8,053 Eingabe-Token
Retry-Schleifeein Trace mit zu vielen Aufrufen, alle mit Status 20010 Aufrufe2 Aufrufe
Teures Modell für einen einfachen SchrittAgententabelle auf der Session-Seite$0.0012$0.0003

Die erste Zeile lief mit GPT-6 Luna, ausgehend von einem leeren Cache. Der Artikel zeigt noch eine zweite Ursache für ausbleibende Cache-Treffer: Eine Anweisung wird beim Senden an jede Nutzernachricht angehängt, aber nicht mitgespeichert. Dadurch entsteht in jeder Gesprächsrunde ein Prompt, der so in der vorherigen Runde nie gesendet wurde. Die Nachricht genauso zu speichern, wie sie gesendet wurde, erhöhte die Trefferquote dieses Gesprächs von 0% auf 76%. Beim Verlauf verursachte die dreifache Menge an Eingabe-Token nur 21% mehr Eingabekosten, weil 71% davon aus dem Cache kamen. In der letzten Zeile machten drei Prüfungen durch Subagenten mit GPT-6.1 Sol 81% der Session-Kosten aus. Dieselben Prüfungen kosteten mit DeepSeek V4.1 Flash nur ein Zehntel. Warum ist mein LLM-Agent teuer? geht jeden Fall einzeln durch.

Wie debuggt man einen LLM-Agenten nach einer Nutzerbeschwerde?

Sucht die Durchläufe des Nutzers über ein Label. Prüft dann im Trace den fehlerhaften Aufruf, der oft sogar Status 200 zurückgegeben hat. Wenn jeder Request ein _user-Label trägt, liefert die Suche metadata._user = u_1002 in der Traces-Liste die Sessions und Traces dieses Nutzers. Der Debugging-Artikel reproduzierte anschließend drei Fehler:

  • Ein 404 mit Retry. Ein Aufruf mit einer nicht existierenden Modell-ID schlug nach 95 ms fehl und kostete $0. Der Retry behielt die Trace-ID und bekam einen neuen Span-Namen (policy-check.retry). Dadurch stehen Fehler und Retry im Wasserfalldiagramm direkt nebeneinander.
  • Abgeschnittene Antworten mit Status 200. Mit max_tokens=40 und aktiviertem Reasoning lieferten drei aufeinanderfolgende Aufrufe von DeepSeek V4.1 Flash Status 200, jeweils genau 40 Ausgabe-Token und eine leere Antwort. Das Reasoning hatte das Limit aufgebraucht. Im Trace erscheint derselbe Span dreimal, jeweils mit einer Token-Zahl genau am Limit. Mit einem Limit von 2,000 und abgeschaltetem Reasoning reichte ein Aufruf mit 49 Token für die Antwort. Nur das Limit zu erhöhen funktionierte ebenfalls, aber dieser Aufruf verbrauchte 1,876 Token für Reasoning und dauerte 22 s.
  • Ein 400 in der zweiten Gesprächsrunde. Im gespeicherten Verlauf fehlte ein Tool-Aufruf. Deshalb lehnte GPT-6 Luna die zweite Gesprächsrunde nach 0.5 s ohne Kosten ab, obwohl die erste funktioniert hatte.

Der zweite Fehler zeigt, warum jeder Aufruf finish_reason protokollieren sollte. Dieses Response-Feld gibt an, warum das Modell aufgehört hat. Der Wert length bedeutet, dass die Antwort das Limit max_tokens erreicht hat. Das sollte auch bei Status 200 als Fehler zählen. Einen LLM-Agenten debuggen nennt die fünf Felder, die jeder Agent protokollieren sollte. Sie stehen auch weiter unten in diesem Beitrag.

Kann man einen fremden Coding-Agenten nachverfolgen?

Claude Code lässt sich ohne Änderungen nachverfolgen, weil es bereits mit jedem Request eine Session-ID und eine Agent-ID sendet. Zwei Umgebungsvariablen richten den Zugriff über das Gateway ein. Eine dritte ergänzt den Typ jedes Subagenten:

export ANTHROPIC_BASE_URL="https://synthorai.io"   # no /v1: Claude Code adds /v1/messages itself
export ANTHROPIC_API_KEY="<your gateway key>"
export CLAUDE_CODE_GATEWAY_HINT_HEADERS=1          # optional: names sub-agent types such as Explore

Ein Durchlauf von Claude Code mit Claude Sonnet 5.5 und zwei Subagenten reparierte einen fehlschlagenden Test. Dafür waren 11 Requests für $0.1728 nötig. Die Session-Seite schlüsselte die Kosten nach Agent auf: Die beiden Subagenten verursachten 39% der Kosten, Cache-Schreibvorgänge 77% und die jeweils ersten Aufrufe der drei Agenten 72%. Eine Wiederholung desselben Durchlaufs eine halbe Minute später kostete mit gefülltem Cache $0.0812. Claude-Code-Kosten nach Subagent zeigt außerdem, wie sich die Ausgaben eines Teams nach Entwickler und Repository aufteilen lassen.

Kann ein Modell die Traces für euch lesen?

Ein Modell kann sie über MCP lesen, das Model Context Protocol, den Standard, um Modellen Tools bereitzustellen. Das Gateway stellt sein Request-Log dafür über einen MCP-Server bereit. Welcher Zugriff passt, hängt davon ab, wer die Frage stellt:

Wer fragt?ZugriffModell-TokenGemessen
Eine Person mit einer FrageClaude mit dem Gateway als Connectorim Chat-Tarif enthalten3 Fragen mit 14 Tool-Aufrufen, alle Werte stimmten mit den Abrechnungsdaten überein
Ein Programm mit einer festen AbfrageREST APIkeinedie 3 teuersten Sessions für $0
Euer eigener Agent, der selbst entscheidet, was er prüftMCP-Tools im Programmwerden berechneteine Kostenanalyse für $0.0015

Im Chat-Beispiel verband sich Claude Fable 5.1 per OAuth. Dabei ersetzt eine Anmeldung im Browser das Einfügen eines API-Keys. Es erhielt ausschließlich Lesezugriff auf das Request-Log: vier schreibgeschützte Tools und einen Key, der nach 7 Tagen abläuft. Die Tools liefern Kosten, Token und Status, aber keine Prompt- oder Antworttexte. Im Programmbeispiel fand GPT-6 Luna unter 26 Sessions die Fälle mit Cache-Problemen, falscher Modellwahl und Retry-Schleife mit einem Tool-Aufruf für $0.0015. Claude Sonnet 5.5 fand dieselben Sessions mit drei Tool-Aufrufen für $0.0631, nachdem es die Preise nachgeschlagen hatte. Claude zu euren LLM-Logs befragen und Eurem Agenten Tools für Logs geben beschreiben die beiden Varianten.

Was sollte jeder Agent protokollieren?

Fünf Felder decken alle Probleme der Serie ab:

  • Eine Trace-ID pro Durchlauf und eine Session-ID pro Gespräch, als Header gesendet und zusätzlich in den eigenen Logs gespeichert.
  • Einen Span-Namen pro Aufruf, der Schritt und Versuch benennt, etwa plan-2 oder policy-check.retry.
  • Labels für Nutzer, Funktion und Release, um bei einer Beschwerde oder Rechnung die zugehörigen Durchläufe einzugrenzen.
  • Den Response-Header x-request-id jedes Aufrufs, um dessen abgerechnete Kosten später nachzuschlagen.
  • finish_reason und die Zahl der Ausgabe-Token jedes Aufrufs, wobei length als Fehler zählt.

Setzt außerdem für jeden Aufruf einen Client-Timeout und einen Wert für max_tokens, beide großzügig bemessen. Ein nur auf die sichtbare Antwort ausgelegtes Token-Limit verursachte die leeren 200er oben. Ein Reasoning-Modell kann für einen einzelnen langen Aufruf mehrere Minuten brauchen.

Was deckt Tracing auf Gateway-Seite nicht ab?

Tracing aus dem Request-Log erfasst Modellaufrufe, aber keine Abläufe innerhalb eures Codes. Wie das Beispiel mit dem langsamen Code zeigt, leitet das Gateway die Laufzeit eures Codes aus den Lücken zwischen Requests ab. Für Spans eurer Tools, Retrieval-Schritte und Queues braucht ihr ein OpenTelemetry SDK. Beide Ansichten können dieselbe Trace-ID nutzen, weil das Gateway traceparent liest, wenn X-Trace-Id fehlt. Die Traces enthalten außerdem keine Prompt- und Response-Bodys und melden Kosten erst im Nachhinein. Für ein laufend aktualisiertes Budget müsst ihr in eurer Agentenschleife die usage jeder Response direkt nach ihrem Eingang bepreisen.

FAQ

Was bedeutet Beobachtbarkeit bei LLM-Agenten?

Beobachtbarkeit bei LLM-Agenten bedeutet, für jeden Durchlauf nachvollziehen zu können, was der Agent getan hat: welche Modelle er in welcher Reihenfolge aufgerufen hat, wie lange jeder Aufruf dauerte, was er kostete und welche Aufrufe fehlschlugen. Praktisch werden dafür Aufrufe zu Traces (ein Durchlauf) und Sessions (ein Gespräch) zusammengefasst und mit Labels für Nutzer und Funktion versehen.

Weiterführende Artikel

← Zurück zum Blog