Warum hat diese Antwort fünf Sekunden gedauert?
Eine KI-Funktion nachzuverfolgen heißt, jeden Modell- und Werkzeugaufruf einer Anfrage zu messen. Ein Support-Assistent plant, ruft zwei Werkzeuge auf, fragt erneut ein Modell und prüft seine Antwort. Eine Nutzerin wartet fünf Sekunden, und die Rechnung ist höher als geplant. Welcher Schritt war langsam, und welcher hat das Geld gekostet?
Modul 69 verfolgte einen einzelnen Aufruf. Diese Lektion geht weiter: ein ganzer Lauf eines Agenten, mit Token, Kosten und Fehlern an jedem Schritt.
Am Ende dieser Lektion
Notiz
Ich kann eine Anfrage durch eine KI-Funktion nachverfolgen und im Trace finden, was langsam, teuer oder fehlerhaft war.
Schlüsselwörter
| Begriff | Was er bedeutet | Beispiel |
|---|---|---|
| Span-ID | die Kennung eines einzelnen Spans | span_id |
| Eltern-ID | die Kennung des Spans, der diesen enthält | parent_span_id |
| Status | ob ein Span gut oder mit einem Fehler endete | fehler |
| Werkzeug-Span | ein Span für ein Werkzeug, das ein Agent aufrief | |
| Token-Zahl | wie viele Token hinein- oder herausgingen | |
| Offener Standard | ein gemeinsamer Entwurf, den alle nutzen dürfen |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.
Ein Span hält Kennungen, Zeiten und Angaben
Jeder Span hat eine Span-ID und die Trace-ID seiner Anfrage. Sein Elternteil nennt er mit einer Eltern-ID, die bei der Wurzel leer ist.
Er hat einen Namen, einen Start, ein Ende und einen Status: ok oder fehler. Angaben stehen in seinen Attributen. OpenTelemetry zählt die Zeit in Nanosekunden seit 1970. Dieses Labor zählt Millisekunden ab dem Start, damit die Zahlen kurz bleiben.
Durchgerechnetes Beispiel: der Span eines Modellaufrufs
| Feld | Wert |
|---|---|
| trace_id | 7a3f0c1d00000000000000000000aa01 |
| span_id | 0000000000000005 |
| parent_span_id | 0000000000000001 |
| name | Modell: Antwort |
| start und ende | 3100 und 4900 |
| status | ok |
| attribute | modell m-small-2026-08, token_ein 2100, token_aus 300, ausgewichen true |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.
Was der Span eines Modellaufrufs aufzeichnet
Zeichnen Sie das Modell und seine Version auf, denn eine neue Version kann Tempo und Kosten ändern. Zeichnen Sie die Token für Eingabe und Ausgabe und die Dauer auf. Halten Sie fest, ob der Aufruf wiederholt wurde oder auf ein anderes Modell auswich.
Legen Sie nie personenbezogene Daten im Klartext an einen Span. Modul 86 zeigt in der Lektion „Sicher protokollieren in einer KI-App“, wie Sie sie fernhalten.
Ein Agent erhält einen Span für jedes Werkzeug
Ein Agent tut mehr, als ein Modell aufzurufen. Er plant, ruft Werkzeuge auf und fragt dann wieder das Modell. Geben Sie jedem Werkzeugaufruf einen eigenen Werkzeug-Span, ein Kind des Spans der Anfrage.
Ein Werkzeug-Span zeichnet den Namen des Werkzeugs, seine Dauer und seinen Status auf. Ein Werkzeug, das scheitert, erscheint als Fehler in einer Anfrage, die trotzdem gut enden kann.
Die Modellhilfe schreiben, Teil 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const basis = process.env.AI_URL || \"http://ai:4000\";' '// Die Token einer Antwort oder null, wenn sie keine nennt.' 'function zaehlen(daten, name) {' ' return daten.usage && typeof daten.usage[name] === \"number\" ? daten.usage[name] : null;' '}' > model.js"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Die Modellhilfe schreiben, Teil 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function fragen(nachrichten, probe, max) {' ' try {' ' const res = await fetch(basis + \"/v1/chat/completions\", {' ' method: \"POST\",' ' headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' ' body: JSON.stringify({ model: \"lab\", messages: nachrichten, max_tokens: max || 100 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"Status \" + res.status);' ' const d = await res.json();' ' return { text: d.choices[0].message.content, eingabe: zaehlen(d, \"prompt_tokens\"), ausgabe: zaehlen(d, \"completion_tokens\"), aufgezeichnet: false };' ' } catch (fehler) {' ' console.log(\"Aufgezeichnete Probe: Kein Modell hat geantwortet (\" + fehler.message + \").\");' ' return { text: probe, eingabe: null, ausgabe: null, aufgezeichnet: true };' ' }' '}' 'module.exports = { fragen };' >> model.js"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Den Trace des Agenten schreiben, Teil 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { fragen } = require(\"./model\");' 'const TRACE = \"7a3f0c1d00000000000000000000aa01\";' 'const PREISE = { \"m-large-2026-09\": [3, 15], \"m-small-2026-08\": [1, 4] };' 'const spans = [];' 'function sid(n) { return String(n).padStart(16, \"0\"); }' 'function span(n, eltern, name, start, ende, status, attribute) {' ' spans.push({ trace_id: TRACE, span_id: sid(n), parent_span_id: eltern ? sid(eltern) : \"\",' ' name, start, ende, status, attribute });' '}' > trace2.js"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Den Trace des Agenten schreiben, Teil 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'span(1, 0, \"Anfrage des Agenten\", 0, 5000, \"ok\", {});' 'span(2, 1, \"Modell: Plan\", 0, 800, \"ok\", { modell: \"m-large-2026-09\", token_ein: 900, token_aus: 60 });' 'span(3, 1, \"Werkzeug: Bestellung finden\", 800, 1100, \"ok\", { werkzeug: \"bestellung_finden\" });' 'span(4, 1, \"Werkzeug: Liefertermin\", 1100, 3100, \"fehler\", { werkzeug: \"liefertermin\" });' 'span(5, 1, \"Modell: Antwort\", 3100, 4900, \"ok\", { modell: \"m-small-2026-08\", token_ein: 2100, token_aus: 300, ausgewichen: true });' 'span(6, 1, \"Antwort prüfen\", 4900, 5000, \"ok\", {});' 'function kinder(id) { return spans.filter(function (s) { return s.parent_span_id === id; }); }' 'function dauer(s) { return s.ende - s.start; }' >> trace2.js"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Den Trace des Agenten schreiben, Teil 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function kosten(a) {' ' const p = PREISE[a.modell];' ' return p ? a.token_ein * p[0] + a.token_aus * p[1] : 0;' '}' 'function zeigen(id, tiefe) {' ' for (const s of kinder(id)) {' ' const marke = s.attribute.ausgewichen ? \", ausgewichen\" : \"\";' ' console.log(\" \".repeat(tiefe) + s.name + \" \" + dauer(s) + \" ms \" + s.status + marke);' ' zeigen(s.span_id, tiefe + 1);' ' }' '}' >> trace2.js"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Den Trace des Agenten schreiben, Teil 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' console.log(\"Trace \" + TRACE);' ' zeigen(\"\", 0);' ' const blaetter = spans.filter(function (s) { return kinder(s.span_id).length === 0; });' ' blaetter.sort(function (a, b) { return dauer(b) - dauer(a); });' ' console.log(\"langsamster Schritt: \" + blaetter[0].name + \", \" + dauer(blaetter[0]) + \" ms\");' ' const bezahlt = spans.filter(function (s) { return kosten(s.attribute) > 0; });' ' bezahlt.sort(function (a, b) { return kosten(b.attribute) - kosten(a.attribute); });' ' console.log(\"teuerster Schritt: \" + bezahlt[0].name + \", \" + kosten(bezahlt[0].attribute) + \" Einheiten\");' ' for (const s of spans.filter(function (x) { return x.status === \"fehler\"; })) {' ' const oben = spans.find(function (x) { return x.span_id === s.parent_span_id; });' ' console.log(\"fehlgeschlagen: \" + s.name + \", in \" + (oben ? oben.name : \"nichts\"));' ' }' >> trace2.js"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Den Trace des Agenten schreiben, Teil 5
docker exec -w /home/node/project agent sh -c "printf '%s\n' ' const live = { trace_id: \"7a3f0c1d00000000000000000000aa02\", span_id: sid(7), parent_span_id: \"\",' ' name: \"Modell: Live-Prüfung\", start: 0, ende: 600, status: \"ok\", attribute: {} };' ' const nachrichten = [{ role: \"user\", content: \"Was zeichnet ein Span auf? Antworten Sie in einem kurzen Satz.\" }];' ' const a = await fragen(nachrichten, \"Ein Span zeichnet einen gemessenen Schritt einer Anfrage auf.\", 40);' ' live.attribute = { modell: \"lab\", token_ein: a.eingabe, token_aus: a.ausgabe, aufgezeichnet: a.aufgezeichnet };' ' console.log(\"Live-Span: \" + JSON.stringify(live));' ' console.log(\"Das Modell sagte: \" + a.text);' '}' 'main();' >> trace2.js"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Den Trace ausführen und lesen
docker exec -w /home/node/project agent node trace2.jsHier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Was der Run ausgibt
| Zeile | Was sie zeigt |
|---|---|
| Trace 7a3f0c1d00000000000000000000aa01 | Die Trace-ID, die alle Spans teilen |
| Werkzeug: Liefertermin 2000 ms fehler | Ein Werkzeug-Span, der fehlschlug |
| Modell: Antwort 1800 ms ok, ausgewichen | Ein Modell-Span, der ein anderes Modell nutzte |
| langsamster Schritt: Werkzeug: Liefertermin, 2000 ms | Der längste Schritt ohne Kinder |
| teuerster Schritt: Modell: Plan, 3600 Einheiten | Token mal Preis seines Modells |
| Live-Span: {...} | Ein echter Aufruf als Span. Seine Token sind null, wenn eine Aufgezeichnete Probe antwortet |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.
Einen Trace auf drei Arten lesen
Für das Tempo suchen Sie den Schritt ohne Kinder, der am längsten dauerte. Für die Kosten multiplizieren Sie die Token jedes Modell-Spans mit den Preisen seines Modells.
Für die Fehler listen Sie jeden Span mit dem Status fehler und den Span, in dem er steckt. Ein fehlgeschlagenes Kind in einem Span, der gut endet, heißt: Der Agent hat sich erholt.
Ein häufiger Fehler
Warnung
Viele denken, der langsamste Schritt sei auch der teuerste. Hier ist das Werkzeug für den Liefertermin am langsamsten, aber der Plan-Aufruf kostet am meisten. Warten macht die Zeit, Token machen die Kosten. Prüfen Sie also beides, bevor Sie etwas ändern.
OpenTelemetry ist ein offener Standard für Traces
OpenTelemetry ist ein offener Standard für Traces, Metriken und Logs. Viele Werkzeuge lesen seine Form eines Spans, sodass eine Aufzeichnung mit mehr als einem Speicher funktioniert.
Er hat auch vorläufige Namen für die Attribute von KI-Aufrufen. Diese Entwürfe ändern sich noch, nehmen Sie die Namen unten also als Beispiel. Prüfen Sie die aktuelle Dokumentation, bevor Sie einen verwenden.
Vorläufige Namen, in der aktuellen Dokumentation prüfen
| Unser Schlüssel | Ein vorläufiger Name | Was er enthält |
|---|---|---|
| modell | gen_ai.request.model | Das angefragte Modell |
| token_ein | gen_ai.usage.input_tokens | Die gesendeten Token |
| token_aus | gen_ai.usage.output_tokens | Die erhaltenen Token |
| werkzeug | gen_ai.tool.name | Das aufgerufene Werkzeug |
| hier keiner | gen_ai.operation.name | Die Art des Aufrufs, etwa chat |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.