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

LLM-Agenten mit 3 HTTP-Headern tracen, ohne Tracing-SDK

Drei Request-Header pro Modellaufruf liefern einen Wasserfall für jeden Agentenlauf: parallele Aufrufe, Zeit im eigenen Code, Fehler und Kosten.

Unsere Empfehlungen

Live-Preise
Modell Fazit Preis
GPT-6 Luna Verarbeitet die Function-Calling-Schleife der Demo. Seine zwei Aufrufe pro Runde (Planung und Antwort) kosten im auf dieser Seite gezeigten Durchlauf zusammen $0.0002. Hauptagent in der Demo ab $0.1/Mio.
DeepSeek V4.1 Flash Führt die drei parallelen Ein-Satz-Prüfungen mit deaktiviertem Reasoning aus. Seine sechs Aufrufe in der auf dieser Seite gezeigten Sitzung kosten $0.0004, und ein siebter Subagent-Aufruf schlug bei Kosten von $0 fehl. Subagent in der Demo ab $0.3/Mio.
Inhalt
  1. Welche Angaben braucht ein Trace?
  2. Was macht der Demo-Agent?
  3. Wie liest man den Wasserfall?
  4. Was ergänzt eine Session gegenüber einem Trace?
  5. Wie lassen sich Kosten nach Feature oder Nutzer aufschlüsseln?
  6. Was passiert bei einem fehlgeschlagenen Aufruf?
  7. Wie bekommt man die Kosten jedes Aufrufs im Code?
  8. Was ist, wenn der Agent bereits Trace-Header sendet?
  9. Wofür nutzt man einen Trace?
  10. Was fehlt noch?
  11. FAQ
  12. Brauche ich OpenTelemetry für das Tracing eines LLM-Agenten?
  13. Was ist der Unterschied zwischen einem Trace und einer Session?
  14. Ändern Trace-Header das Routing, die Abrechnung oder die Daten, die das Modell sieht?
  15. Wie lange werden Traces aufbewahrt?
  16. Kostet ein fehlgeschlagener Aufruf etwas?
  17. Weiterführende Artikel

Wenn ein Agent für die Antwort auf eine Nachricht fünf Modellaufrufe braucht, hilft ein einfaches Request-Log beim Debuggen nur begrenzt. Es zeigt nicht, welche Aufrufe zum selben Lauf gehören, welche parallel liefen und wie viel der Wartezeit auf den eigenen Code entfiel. In dieser Anleitung ergänzen wir einen kleinen Support-Agenten um drei Request-Header. Das Ergebnis sehen wir in der Synthorai-Konsole als Trace (ein Lauf auf einer Zeitachse) und als Session (ein Gespräch samt Kosten). Dafür muss weder ein Tracing-SDK installiert noch ein Collector betrieben werden, also ein separater Dienst, der Traces entgegennimmt. Der Quellcode liegt unter agent-tracing/ in github.com/synthorai-io/use-cases. Voraussetzungen sind Python 3.10 oder neuer und ein API-Key aus der Konsole.

TL;DR

  • Drei Request-Header (X-Trace-Id, X-Span-Id, X-Session-Id) pro Modellaufruf machen aus dem Request-Log Traces und Sessions in der Synthorai-Konsole, ohne Tracing-SDK.
  • Im Demo-Trace verteilen sich 8.5 s Gesamtdauer auf etwa 7.0 s in Modellaufrufen und 1.6 s außerhalb davon, im eigenen Agentencode und im Netzwerk.
  • Ein fehlgeschlagener Aufruf mit Status 404 blieb im Trace sichtbar und kostete $0.
  • Der Demo-Lauf mit 21 Modellaufrufen kostete $0.0017.
git clone https://github.com/synthorai-io/use-cases
cd use-cases
cp .env.example .env    # put your API key in it
cd agent-tracing
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python run.py --fail

Bei unserem Test am 11. Oktober 2026 führte das Programm in weniger als einer Minute 21 Modellaufrufe aus und kostete $0.0017. Es gibt zwei Session-IDs und vier Trace-IDs aus. Unter Traces in der Konsole sind dieselben IDs aufgeführt. Die Konsole zeigt Zeiten in der Zeitzone des Browsers an. Deshalb steht in den Screenshots auf diesen Seiten 19:24 statt 11:24 UTC.

Ein Trace in der Konsole: 8.5 s Gesamtdauer, davon 7.0 s im Gateway und 1.6 s in der Anwendung, 6 Modellaufrufe, davon 1 fehlgeschlagen. Darunter ein Wasserfall mit einem plan-Aufruf, einer Lücke für lokale Tools, drei überlappenden Subagenten-Aufrufen, einer 95-ms-Markierung mit Status 404 und dem answer-Aufruf. Rechts zeigt ein Panel den fehlgeschlagenen Aufruf: 95 ms, Kosten $0, keine Berechnung und den Fehlertext

Welche Angaben braucht ein Trace?

Bei jedem Modellaufruf werden drei Header mitgeschickt. Ihre Werte sind frei wählbare Strings. Das Gateway protokolliert bereits jeden Request. Die Header geben lediglich an, welche Requests zusammengehören.

HeaderDenselben Wert senden fürErgebnis
X-Trace-Idalle Aufrufe eines Laufs (die vollständige Bearbeitung einer Nutzernachricht)die Aufrufe als gemeinsamer Wasserfall
X-Span-Idgenau einen Aufrufder Name der zugehörigen Zeile im Wasserfall
X-Session-Idalle Aufrufe eines Gesprächsgruppierte Läufe mit Summen für das Gespräch

Ein Span ist ein einzelner Schritt innerhalb eines Traces. In dieser Anleitung entspricht jeder Span einem Modellaufruf. Jede ID darf bis zu 128 druckbare ASCII-Zeichen enthalten. Das Gateway gibt die IDs in der Response zurück und leitet sie nicht an den Modellanbieter weiter.

Drei weitere Header sind optional. X-Agent-Id gibt an, welcher Subagent einen Aufruf ausgeführt hat. X-Parent-Agent-Id benennt den Agenten, der diesen Subagenten gestartet hat. X-Synthorai-Metadata enthält Labels, etwa für das Feature oder den Endnutzer. Alle stehen in der Referenz der Request-Header. Die Demo sendet X-Agent-Id und Labels, aber kein X-Parent-Agent-Id: Der einzige Subagent wird vom Hauptagenten gestartet, weitere Verschachtelungen gibt es nicht.

In der Demo stellt eine Funktion die Header für einen Aufruf zusammen. Diese Funktion ist die gesamte Tracing-Schicht:

@dataclass
class Run:
    """One agent run, which the gateway shows as one trace."""
    session_id: str
    labels: dict = field(default_factory=dict)
    trace_id: str = field(default_factory=lambda: new_id("trace"))

    def headers(self, span: str, agent: str | None = None, parent: str | None = None) -> dict:
        h = {
            "X-Session-Id": self.session_id,
            "X-Trace-Id": self.trace_id,
            "X-Span-Id": span,
        }
        if agent:
            # Only sub-agents need an id. Calls without one belong to the main agent.
            h["X-Agent-Id"] = agent
        if parent:
            h["X-Parent-Agent-Id"] = parent
        if self.labels:
            h["X-Synthorai-Metadata"] = json.dumps(self.labels, separators=(",", ":"))
        return h

Alle Modellaufrufe des Agenten laufen durch einen gemeinsamen Wrapper, der die Header anhängt. Im OpenAI Python SDK werden Header pro Request über extra_headers übergeben:

raw = await client.chat.completions.with_raw_response.create(
    model=model, messages=messages,
    extra_headers=run.headers(span, agent, parent), **kwargs)
resp = raw.parse()
request_id = raw.headers.get("x-request-id")   # the gateway's id for this call

with_raw_response dient hier nur dazu, die Response-Header auszulesen. Wer die Request-ID nicht braucht, kann einfach create(..., extra_headers=...) verwenden.

Was macht der Demo-Agent?

Er beantwortet eine Kundennachricht in vier Schritten mit fünf Modellaufrufen. Der Ablauf ist so gewählt, dass sich am Trace etwas ablesen lässt. Der Hauptagent nutzt GPT-6 Luna, der Subagent DeepSeek V4.1 Flash. Beide Modelle werden über Umgebungsvariablen festgelegt. Die Aufrufe des Subagenten setzen reasoning_effort="none", weil das Modell für eine Prüfung mit einem Satz Antwort nicht erst einen Reasoning-Schritt braucht. Falls das gewählte Subagenten-Modell diesen Wert ablehnt, muss ANALYST_REASONING geändert oder geleert werden.

SpanWerAblauf
planHauptagentliest die Nachricht und fordert Tools an (Function Calling: Das Modell liefert einen Funktionsnamen und Argumente, die der eigene Code ausführt)
keinereigener Codeführt zwei Tools lokal und parallel aus: eine Bestellabfrage (1.2 s) und eine Suche in Richtlinien (0.6 s)
sentiment, policy-check, riskSubagent analystdrei gleichzeitig gestartete Prüfungen mit jeweils einem Satz als Ergebnis
answerHauptagentformuliert die Antwort aus den Tool-Ergebnissen und den drei Prüfungen

Die Tools sind einfache Python-Funktionen mit einem sleep, die eine Datenbank und einen Suchindex simulieren. Mit --fail fordert ein policy-check-Aufruf zunächst ein nicht existierendes Modell an. Er erhält einen 404 und wird unter dem Span-Namen policy-check.retry wiederholt.

run.py spielt zwei Gespräche mit jeweils zwei Gesprächsrunden durch. Jedes Gespräch bekommt eine Session-ID, jede Runde eine neue Trace-ID:

session_id, history = new_id("support"), []
for text in turns:
    run = Run(session_id, labels={"feature": "support-agent", "release": "v2", "_user": user})
    reply = await handle(run, history, text)

Wie liest man den Wasserfall?

Jede Zeile zeigt einen Modellaufruf oder eine Lücke, in der der eigene Code läuft. Die Zeitachse beginnt, sobald der erste Aufruf des Laufs das Gateway erreicht. Der Screenshot oben zeigt die zweite Runde des ersten Gesprächs. Von oben nach unten:

  • plan dauerte 2.3 s. Der Balken zeigt die Zeit, die das Gateway für diesen Request brauchte, vom Empfang bis zum Abschluss der Response.
  • your app dauerte 1.4 s. In diesem Zeitraum war kein Request offen. Die Zeit entfiel daher auf Arbeit außerhalb des Gateways: den eigenen Code und das Netzwerk zwischen Code und Gateway. Hier sind es die beiden lokalen Tools; das langsamere wartet mit sleep 1.2 s. Bei einem echten Agenten wird in dieser Zeile eine langsame Datenbankabfrage oder Vektorsuche sichtbar.
  • sentiment, risk und policy-check.retry überlappen sich. Der Agent hat die drei Prüfungen mit asyncio.gather gestartet. Die überlappenden Balken zeigen die parallelen Aufrufe. Der Pfeil vor den Namen kennzeichnet Aufrufe eines Subagenten.
  • policy-check ist eine schraffierte 95-ms-Markierung mit 404. Ein fehlgeschlagener Aufruf bleibt mit dem Statuscode im Trace, den der eigene Code erhalten hat.
  • answer dauerte 3.0 s und ist der langsamste Aufruf. Das steht auch in der Zusammenfassung über dem Wasserfall.
  • Die Spalte Output zeigt die Ausgabetokens jedes Aufrufs. Die Ausgabe bestimmt, womit ein Aufruf seine Zeit verbringt. Die Spalte erklärt deshalb meist die Länge des danebenliegenden Balkens: answer erzeugte 101 Tokens, die drei Prüfungen jeweils 14 bis 57. Wenn bei einer größeren Ausgabe die meisten Tokens auf Reasoning entfallen, unterstreicht die Konsole die Anzahl. Das ist hier bei den 101 der Fall.

Die Zusammenfassung teilt die 8.5 s Gesamtdauer in 7.0 s im Gateway und 1.6 s in der Anwendung auf. Damit beantwortet sie die erste Frage der meisten Latenzanalysen: Muss man die Modellaufrufe oder den eigenen Code untersuchen? In diesem Lauf entfielen 82% der Wartezeit auf die Modelle. Die beiden Aufrufe des Hauptagenten, plan und answer, machten davon 5.3 s aus. Ein schnelleres Modell für den Hauptagenten würde daher mehr bringen als schnellere Modelle für die drei Prüfungen.

Das Gateway kann nicht in den eigenen Code hineinsehen. Es leitet die Zeilen your app aus den Lücken zwischen Requests ab. Eine Lücke zeigt also, wie lange der Code lief, aber nicht, was er getan hat. Für Spans eigener Funktionen braucht man ein SDK für OpenTelemetry, den offenen Standard für Tracing. Beide können dieselben IDs verwenden: Wenn kein X-Trace-Id gesendet wird, liest das Gateway traceparent, den Standard-Header mit der Trace-ID.

Was ergänzt eine Session gegenüber einem Trace?

Eine Session fasst die Läufe eines Gesprächs zusammen und bildet ihre Summen. Sie lässt sich aus der Traces-Liste öffnen oder über „Session öffnen“ in einem beliebigen Trace.

Eine Session in der Konsole: 11 Aufrufe mit Kosten von $0.0008 und einem 404, die Labels _user, feature und release, eine Agententabelle mit dem Hauptgespräch auf gpt-6-luna und dem Subagenten analyst auf deepseek-v4.1-flash sowie die beiden Traces der Session mit Dauer, Eingabe, Cache-Treffern und Kosten

Die Session oben zeigt ein Kundengespräch mit zwei Runden: 11 Aufrufe, 1,890 Eingabe- und 750 Ausgabetokens, $0.0008, ein Fehler. Die Seite besteht aus drei Teilen:

  • Agenten dieser Session schlüsselt Aufrufe und Kosten nach Agent auf. Der Hauptagent führte 4 Aufrufe auf GPT-6 Luna für $0.0004 aus. Der Subagent analyst führte 7 Aufrufe für $0.0004 aus: sechs auf DeepSeek V4.1 Flash und einen fehlgeschlagenen. Die Tabelle erscheint, weil die Aufrufe des Subagenten X-Agent-Id: analyst mitgeschickt haben. Aufrufe ohne diesen Header zählen zum Hauptgespräch.
  • Traces dieser Session listet jede Runde mit Fehlern, Dauer, Eingabetokens, Cache-Treffern und Kosten auf. So lässt sich die langsame, fehlgeschlagene oder teure Runde eines langen Gesprächs finden, ohne durch ein Log zu scrollen.
  • Cache-Treffer ist der Anteil der Eingabetokens, die aus dem Prompt-Cache des Anbieters gelesen wurden. Hier liegt er bei 0%, weil die Prompts nur wenige hundert Tokens lang sind. Anbieter cachen Präfixe erst ab einer Mindestlänge (bei den meisten 1,024 Tokens; siehe Prompt-Caching).

Die Session-ID erfüllt eine zweite Aufgabe, sobald die Prompts lang genug für den Cache sind. Jeder Aufruf eines Gesprächs sollte denselben X-Session-Id senden. Das Gateway sorgt damit für konsistente Prompt-Cache-Treffer innerhalb des Gesprächs, sodass die zweite Runde den Cache liest, den die erste geschrieben hat. Bei /v1/chat/completions und /v1/responses lässt sich der Wert auch als session_id im Request-Body übergeben, wenn das Framework das Setzen von Headern erschwert.

Wie lassen sich Kosten nach Feature oder Nutzer aufschlüsseln?

Dazu wird X-Synthorai-Metadata mit einem flachen JSON-Objekt aus Strings gesendet. Anschließend kann man danach gruppieren oder filtern. Die Demo versieht jeden Aufruf mit Labels für Feature, Release und Endnutzer:

labels = {"feature": "support-agent", "release": "v2", "_user": "u_1002"}

Die Labels erscheinen als kleine Tags am Trace und an der Session. Für die Summen öffnet man die Analyse, wählt den Tab für die Aufschlüsselung und dort die Karte „Kosten nach Metadaten“. Nach Auswahl eines Schlüssels zeigt sie jeden zugehörigen Wert mit Requests, Fehlern, Eingabetokens und Kosten:

Die Karte „Kosten nach Metadaten“ mit ausgewähltem Schlüssel _user: u_1001 mit 22 Requests, 2 Fehlern und $0.0018 sowie u_1002 mit 20 Requests, 0 Fehlern und $0.0017

Die Karte oben umfasst eine Stunde, in der die Demo zweimal lief: 22 Requests und $0.0018 für u_1001, dessen Läufe die zwei fehlgeschlagenen Aufrufe enthalten, und 20 Requests und $0.0017 für u_1002. Nach einem Durchlauf stehen dort 11 und 10. Mit feature als Schlüssel sieht man die Kosten je Feature. Mit release lässt sich prüfen, wie sich eine Prompt-Änderung auf die Kosten ausgewirkt hat. Die Karte kann auch zwei Werte eines Schlüssels nebeneinanderstellen. Im selben Tab gibt es eine Karte „Kosten nach Agent“, die jeden X-Agent-Id über alle Sessions hinweg summiert.

Um die Läufe hinter einer Summe zu finden, lassen sich die Labels als Suchkriterien nutzen. metadata._user = u_1002 in das Suchfeld der Traces-Liste eingeben und Enter drücken. Dann bleiben nur die Sessions und Traces dieses Nutzers übrig. Das Filterfeld in den Logs akzeptiert denselben Ausdruck für einzelne Requests.

Für Labels gelten wenige Regeln:

  • Höchstens 10 Labels pro Request. Ein Schlüssel besteht aus 1 bis 32 Zeichen aus a-z, 0-9, _, . und -. Ein Wert ist ein String mit bis zu 128 Bytes.
  • _user kennzeichnet den Endnutzer, für den der Request ausgeführt wurde. Andere Schlüssel mit führendem Unterstrich sind reserviert.
  • Ein Label, das gegen eine Regel verstößt, wird verworfen; der Request läuft trotzdem weiter. Der Response-Header X-Synthorai-Metadata-Dropped nennt die verworfenen Schlüssel. Diesen Header sollte man bei der Einrichtung mitloggen. Wir haben zum Test einen Schlüssel namens Bad?Key gesendet. Der Header kam mit Bad?Key zurück.
  • Prompt-Text, personenbezogene Daten und Geheimnisse gehören nicht in Labels. Für _user die interne Nutzer-ID verwenden, keine E-Mail-Adresse.

Was passiert bei einem fehlgeschlagenen Aufruf?

Er bleibt als schraffierte Markierung mit seinem Statuscode im Trace und kostet nichts. In der Demo hat der fehlgeschlagene policy-check eine nicht existierende Modell-ID angefordert. Ein Klick darauf im Wasserfall öffnet das Panel rechts im Screenshot oben. Es zeigt den Fehler, den der Code erhalten hat: The model "model-that-does-not-exist" does not exist or is not available to this workspace, eine Latenz von 95 ms, Kosten von $0 und den Hinweis, dass nichts berechnet wurde.

Die Demo gibt dem Retry einen eigenen Span-Namen:

try:
    await call(run, span, model=BROKEN_MODEL, messages=messages, agent="analyst")
except APIStatusError:
    span = f"{span}.retry"      # the retry is its own span, so both show in the waterfall
resp = await call(run, span, model=ANALYST_MODEL, messages=messages, agent="analyst", max_tokens=400,
                  **analyst_options())   # reasoning_effort="none" unless ANALYST_REASONING says otherwise

Die unveränderte Trace-ID und der neue Span-Name sorgen dafür, dass Fehler und Retry gemeinsam in einer Ansicht stehen. Wenn die Retry-Logik in einer Bibliothek steckt, die dieselben Header wiederverwendet, erscheinen trotzdem beide Versuche, dann als zwei Zeilen mit demselben Namen.

Fehler werden auch zusammengefasst. Die Trace-Zusammenfassung zeigt „6 Modellaufrufe, 1 fehlgeschlagen“. Die Session listet Fehler nach Statuscode auf (1 × 404 Not found). Die Traces-Liste hat eine sortierbare Fehlerspalte, über die sich problematische Läufe finden lassen.

Wie bekommt man die Kosten jedes Aufrufs im Code?

Über die Request-ID aus der Response lässt sich der Abrechnungsdatensatz beim Gateway abfragen. GET /v1/generation?id=<request id> liefert Kosten, Token-Zahlen, Latenz und Status eines Requests.

async def cost_of(http: httpx.AsyncClient, request_id: str) -> float | None:
    """The billing record of one request. It is written a moment after the call returns."""
    if not request_id:
        return None                                  # a call that got no response has no record
    for _ in range(6):
        try:
            r = await http.get(f"{BASE_URL}/generation", params={"id": request_id})
            if r.status_code == 200:
                return r.json()["data"]["total_cost"]
        except (httpx.HTTPError, KeyError, TypeError, ValueError):
            pass                                     # a dropped connection or a half-written record: try again
        await asyncio.sleep(2)
    return None

Der Datensatz wird kurz nach Abschluss der Response geschrieben. Deshalb wiederholt die Funktion die Abfrage, bis sie einen 200 erhält. Sie bekommt einen httpx.AsyncClient, den der aufrufende Code einmal mit gesetztem Authorization-Header erstellt und für jede Abfrage übergibt. Wer zwanzig Aufrufe über denselben Client abfragt, verwendet einige wenige Verbindungen wieder. Ein neuer Client pro Abfrage öffnet dagegen zwanzig Verbindungen gleichzeitig. Bei unseren Läufen brachen eine oder zwei davon ab. Die Demo gibt pro Aufruf eine Zeile aus, die auf ihren eigenen Zeitmessungen und diesen Datensätzen beruht. Dies ist die erste Runde der Session aus den Screenshots:

trace trace-5258a3328d7d  session support-d8ac3cdd4411
  plan                 main     gpt-6-luna                 200   1878 ms    153 in   22 out  $0.000026
  sentiment            analyst  deepseek-v4.1-flash        200   1431 ms     91 in   28 out  $0.000061
  risk                 analyst  deepseek-v4.1-flash        200   1877 ms     96 in   33 out  $0.000068
  policy-check         analyst  deepseek-v4.1-flash        200   1885 ms     96 in   39 out  $0.000076
  answer               main     gpt-6-luna                 200   4142 ms    350 in  339 out  $0.000205

Diese Zeiten werden im Client gemessen und enthalten die Netzwerkzeit. Sie sind deshalb länger als die Balken in der Konsole, die den Request innerhalb des Gateways messen. In der zweiten Runde, die der Wasserfall oben zeigt, dauerte plan im Client 2,499 ms und im Gateway 2.3 s. Bei answer waren es 3,161 ms und 3.0 s. Bei parallel gesendeten Aufrufen kann der Unterschied größer sein: Der fehlgeschlagene Aufruf dauerte im Client 694 ms und im Gateway 95 ms.

Wer die abgerechneten Kosten jedes Aufrufs in eigenen Logs haben möchte, kann diese Funktion übernehmen. Zum Stoppen eines zu teuren Laufs eignet sie sich nicht: Jeder Datensatz kommt erst kurz nach dem Aufruf, und die Funktion kann mehrere Sekunden darauf warten. Für ein laufendes Budget müssen die Kosten aus usage jeder Response während des Laufs selbst berechnet werden. Die Abrechnungsdatensätze dienen anschließend zur Kontrolle.

Was ist, wenn der Agent bereits Trace-Header sendet?

Dann muss möglicherweise nichts ergänzt werden. Für jede der drei IDs, die im Request fehlt, liest das Gateway den entsprechenden Header, den gängige Tools bereits senden:

Eingesetztes ToolGesendeter HeaderInterpretiert als
OpenTelemetry-HTTP-InstrumentierungtraceparentTrace, mit dessen Parent-ID als Span
LiteLLM SDKX-Litellm-Trace-Id, X-Litellm-Session-IdTrace, Session
Langfuse SDKbaggage: langfuse_trace_id=…, langfuse_session_id=…Trace, Session
Open WebUIX-OpenWebUI-Chat-IdSession
Claude CodeX-Claude-Code-Session-IdSession
OpenAI Codex CLISession-IdSession

Die eigenen Header X-Trace-Id, X-Span-Id und X-Session-Id haben Vorrang, wenn beide Varianten vorhanden sind. Die vollständige Liste steht in der Referenz der Request-Header. Bei Agenten-Frameworks, die keinen dieser Header senden, müssen sie über die jeweilige Einstellung für benutzerdefinierte Header ergänzt werden.

Wofür nutzt man einen Trace?

Meist vergleicht man ihn mit einem anderen. Jede Trace-Seite hat einen Vergleichsbutton. Nach Auswahl eines zweiten Traces zeigt die Konsole beide auf einer Seite: jede Kennzahl als A, B und Differenz, jeden Aufruf zusammen mit dem gleichnamigen Aufruf sowie beide Wasserfälle auf einer gemeinsamen Zeitachse. So lässt sich prüfen, wie sich Änderungen am Prompt, am Modell oder am eigenen Code auf einen Lauf ausgewirkt haben.

Drei weiterführende Anleitungen verwenden denselben Agenten und dieselben Header. Jede reproduziert Probleme, behebt sie und vergleicht das Ergebnis auf diese Weise:

Drei weitere betrachten dasselbe Log aus anderen Blickwinkeln:

Was fehlt noch?

Die Demo deckt Modellaufrufe ab. Im Produktionsbetrieb kommen meist vier Dinge hinzu:

  • Spans für den eigenen Code. Das Gateway zeigt die Lücken zwischen Modellaufrufen, aber nicht, was darin passiert. Tools, Retrieval und Queues mit OpenTelemetry instrumentieren und traceparent durchreichen, damit dieselbe Trace-ID beide Ansichten verbindet.
  • Prompt- und Response-Bodys. Diese Anleitung nutzt die Zeit-, Token- und Kostendaten jedes Aufrufs. Sie behandelt nicht die Prüfung der Inhalte, die an das Modell gesendet wurden.
  • Budgets. cost_of liefert den abgerechneten Betrag im Nachhinein. Um einen Lauf an einem Limit zu stoppen, müssen die Kosten aus usage jeder Response direkt bei deren Eingang berechnet werden. Das sind einige Zeilen in der Agentenschleife, die in der Demo fehlen.
  • Sampling. Jeder Aufruf wird erfasst, weil der Trace aus dem Request-Log entsteht, das das Gateway ohnehin führt. Es gibt nichts zu samplen. Auf Agentenseite entsteht außer einigen Headern kein zusätzlicher Aufwand.

FAQ

Brauche ich OpenTelemetry für das Tracing eines LLM-Agenten?

Nein. Drei Request-Header (X-Trace-Id, X-Span-Id, X-Session-Id) pro Modellaufruf reichen für einen Wasserfall der Modellaufrufe. Die Zeit dazwischen wird der Anwendung zugeordnet. OpenTelemetry wird nützlich, wenn auch eigene Funktionen Spans bekommen sollen.

Was ist der Unterschied zwischen einem Trace und einer Session?

Ein Trace ist ein Agentenlauf: alles, was zur Bearbeitung einer Nutzernachricht oder einer Aufgabe passiert. Eine Session ist ein Gespräch aus mehreren Läufen. In der Demo ist jedes Kundengespräch eine Session mit zwei Traces. Jeder Trace enthält fünf oder sechs Modellaufrufe.

Ändern Trace-Header das Routing, die Abrechnung oder die Daten, die das Modell sieht?

X-Trace-Id und X-Span-Id ändern nichts. X-Session-Id sorgt für konsistente Prompt-Cache-Treffer innerhalb eines Gesprächs. Keiner der Header wird an den Modellanbieter weitergeleitet. Die Abrechnung richtet sich nach der eigenen Request-ID des Gateways.

Wie lange werden Traces aufbewahrt?

Traces und Sessions werden aus dem Request-Log erstellt und decken dieselben 90 Tage ab. Die Traces-Liste zeigt beim Öffnen die letzten 7 Tage.

Kostet ein fehlgeschlagener Aufruf etwas?

Der fehlgeschlagene Aufruf in der Demo, ein 404 für eine unbekannte Modell-ID, wurde mit Kosten von $0 und dem Hinweis erfasst, dass nichts berechnet wurde.

Weiterführende Artikel

← Anwendungsfälle