Streaming- und SSE-Abbrüche: Wiederherstellen ohne doppelte Aktionen

Terminale Events von SSE-Abbrüchen unterscheiden, Teilausgaben sichern und sichere Wiederholungen ohne doppelte Nebenwirkungen umsetzen.

Eine Streaming-API-Anfrage ist erst abgeschlossen, wenn das für das jeweilige Protokoll vorgesehene terminale Event eingetroffen ist. Ein geschlossener Socket, ein Client-Timeout oder das letzte Textfragment beweisen keinen erfolgreichen Abschluss. Nach einem Abbruch sollten bereits empfangene Events, Request ID und Operationsstatus erhalten bleiben. Wiederhole die Anfrage erst nach einer Prüfung auf Seiteneffekte: Nicht jedes Protokoll kann einen Stream ab dem letzten Token fortsetzen, und ein blinder Retry kann ein Tool zweimal ausführen.

Reguläres Ende oder echter Abbruch

Server-Sent Events übertragen eine Folge von Events über eine lange HTTP-Verbindung. Der Client liest sie bis zu einem dieser Zustände:

created -> receiving -> completed \-> failed \-> disconnected

completed ist ein bestätigendes, terminales Protokoll-Event. failed ist ein Fehler, der innerhalb des Streams übermittelt wurde. disconnected bedeutet dagegen, dass der Transport ohne bestätigten Abschluss endete. Dieser letzte Fall braucht eine Diagnose.

In der OpenAI Responses API gibt es Events wie response.created, Output-Fragmente und response.completed; außerdem sind Fehler- oder unvollständige Zustände möglich. Anthropic Messages verwendet message_start, Content-Block-Events, message_delta und message_stop. Diese Eventnamen dürfen nicht in einem gemeinsamen Parser vermischt werden.

Möchtest du einen Streaming-Abbruch mit einer kontrollierten Anfrage nachvollziehen? Erstelle dazu ein eigenes BetterToken-Konto mit API Key, öffne die API-Referenz und beginne mit einem kurzen Stream ohne Tool Calls. Vergleiche anschließend Zeit, Modell und Status mit dem Eintrag im Dashboard. Erst wenn terminales Event und Teilausgabe geprüft sind, sollte ein begrenzter Retry hinzukommen.

Welche Daten bei einem Abbruch gespeichert werden sollten

Ein minimales Protokoll hilft, ein Client-Problem von einem Server-Problem zu unterscheiden:

{ "started_at": "2026-08-03T12:00:00Z", "protocol": "openai-responses", "request_id": "req_placeholder", "http_status": 200, "last_event_type": "response.output_text.delta", "events_received": 42, "bytes_received": 8192, "terminal_event_received": false, "client_error": "socket closed" }

Speichere weder API Key noch vollständigen Prompt, Tool-Argumente oder sensible Antwortinhalte. Partiellen Text darfst du nur ablegen, wenn die Richtlinie deiner Anwendung das erlaubt. Für Produktion sind ein Hash der Operation, die Zahl der Events und der letzte sichere Sequenzmarker meist hilfreicher als ein vollständiges Payload-Archiv.

Die Request ID kann aus HTTP-Headern oder einem Event stammen. Speichere sie so früh wie möglich und nicht erst, nachdem der Stream beendet wurde. Damit lassen sich Client-Log, Proxy-Log und die spätere Anfragehistorie zusammenführen.

Vor dem Netzwerk den Parser prüfen

Der Client muss diese Fälle korrekt behandeln:

  • mehrere data:-Zeilen in einem Event;
  • Leerzeilen zwischen Events;
  • UTF-8-Fragmente an Grenzen von Netzwerk-Chunks;
  • unbekannte Eventtypen ohne Prozessabsturz;
  • ein Fehler-Event nach erfolgreichem HTTP-Status;
  • ein terminales Event ohne das erwartete letzte Text-Delta;
  • Tool-Argumente, die auf mehrere Fragmente verteilt sind.

Ein TCP-Chunk ist kein SSE-Event. Ein Event kann in mehreren Teilen eintreffen; mehrere Events können in einem einzigen Lesevorgang ankommen. Baue daher erst einen vollständigen SSE-Frame zusammen und parse danach das JSON.

Pseudocode für den Handler:

state = receiving for event in parse_sse(response_body): log_safe_metadata(event) apply_event_to_partial_result(event) if is_terminal_success(event): state = completed break if is_terminal_failure(event): state = failed break if connection_closed and state == receiving: state = disconnected

is_terminal_success und is_terminal_failure sollten für Responses, Chat Completions und Messages getrennt implementiert werden. Ein Parser, der bei einem unbekannten Event automatisch Erfolg annimmt, verdeckt genau den Zustand, den ein Retry später sicher entscheiden muss.

Timeout auf jeder Ebene prüfen

Eine lange Verbindung durchläuft mehrere Timer:

  1. Timeout des SDK oder HTTP-Clients;
  2. Idle-/Read-Timeout der Anwendung;
  3. Reverse Proxy;
  4. Load Balancer oder Ingress;
  5. Unternehmens-Proxy;
  6. Mobilfunk- oder Heimnetz;
  7. serverseitiges Generierungslimit.

Ein allgemeines Request-Timeout und ein Idle-Timeout sind verschiedene Parameter. Wenn das Modell regelmäßig Events sendet, sollte ein kurzes Idle-Timeout nicht auslösen. Sind längere Pausen zwischen Events zulässig, muss der Wert zur erwarteten Last passen.

Prüfe auch auf Buffering im Proxy. Puffert ein Proxy SSE-Chunks, sieht der Nutzer lange keinen Text und bekommt dann entweder einen großen Block oder ein Timeout. Miss die Zeit bis zum ersten Event und die Abstände zwischen Events in jeder Umgebung: lokal, hinter dem Reverse Proxy und in Produktion. Nur ein lokaler Erfolg beweist nicht, dass der gesamte Pfad lange Streams unverändert durchlässt.

Teilausgabe anzeigen, speichern oder verwerfen

Partieller Text kann in der Oberfläche nützlich sein, sein Status muss jedoch sichtbar bleiben. Eine abgerissene Antwort darf nicht als vollständig angezeigt werden.

Ein praktisches Zustandsmodell ist:

  • streaming – der Text ändert sich noch;
  • complete – ein terminales Erfolgs-Event ist eingetroffen;
  • partial – die Verbindung ging nach mehreren Events verloren;
  • failed – das Protokoll lieferte einen Fehler;
  • cancelled – Nutzer oder Anwendung stoppten die Anfrage.

Bei partial speicherst du den bereits empfangenen Text getrennt von einer neuen Generierung. Zwei Generationen automatisch zusammenzukleben ist riskant: Das Modell kann Text wiederholen, Formulierungen ändern oder Tools in einer anderen Reihenfolge aufrufen. Eine UI kann die alte Ausgabe als unvollständig markieren und eine neue Ausgabe separat zeigen; ein Ersetzen sollte eine ausdrückliche Entscheidung sein.

Wann eine Anfrage wiederholt werden kann

Ob ein Retry sicher ist, hängt von der ausgelösten Aktion ab.

Text ohne externe Aktionen

Eine kurze Textanfrage lässt sich normalerweise mit einer begrenzten Zahl von Versuchen wiederholen. Die Anwendung zeigt das alte Ergebnis als partial und die neue Antwort als eigene Generierung an oder ersetzt sie erst nach ausdrücklicher Bestätigung. Auch hier ist ein bestätigtes Ende besser als die Annahme, der letzte sichtbare Satz sei vollständig.

Tool Calls und Transaktionen

Prüfe vor dem Retry, ob das Tool bereits gelaufen ist. Endet der Stream nach dem Senden eines Befehls, kann eine zweite Anfrage erneut ein Issue, eine E-Mail oder eine Zahlungstransaktion erzeugen. Verwende einen Idempotency Key auf Tool-Ebene, eine eigene Operation ID und ein Log abgeschlossener Aktionen. Der Retry darf nicht allein aus einem geschlossenen Stream abgeleitet werden.

Lange Agent-Aufgabe

Ein automatisches „ab dem letzten Token fortsetzen“ wird selten durch das Protokoll bestätigt. Stelle die Aufgabe besser aus gespeichertem Anwendungszustand wieder her: bestätigten Nachrichten, Tool-Ergebnissen und dem letzten abgeschlossenen Schritt. Rohe Textfragmente sind kein konsistenter Agent-Zustand und sollten nicht als solcher ausgegeben werden.

Begrenzter Retry mit Backoff

Eine Retry-Strategie braucht eine endliche Zahl von Versuchen:

attempts = 0 while attempts < MAX_ATTEMPTS: result = run_request(operation_id) if result.completed: return result if not result.retryable: raise result.error wait(base_delay * 2**attempts + random_jitter) attempts += 1

Ob ein Fehler retrybar ist, ergibt sich aus Protokollfehler, HTTP-Status, vorhandenem terminalem Event und Seiteneffekten. 401, eine falsche Model ID und ungültiges JSON werden durch Warten nicht repariert. 429, ein temporäres 5xx oder ein Transportfehler können manchmal einen Wiederholungsversuch zulassen, aber nur begrenzt und unter Berücksichtigung der Provider-Header.

Backoff verringert die Last bei einer vorübergehenden Störung, ersetzt aber keine Fehlerklassifikation. Verwende dieselbe Operation ID zur Zuordnung, nicht als Behauptung, dass ein bereits ausgeführtes externes Tool sicher folgenlos erneut läuft.

Minimaler Test

  1. Sende eine kurze Streaming-Anfrage ohne Tools.
  2. Notiere alle Eventtypen und warte auf das terminale Event.
  3. Beende den Client nach einigen Events künstlich.
  4. Stelle sicher, dass das Ergebnis als partial markiert wird.
  5. Prüfe genau einen begrenzten Retry.
  6. Wiederhole den Test hinter dem Produktions-Proxy.
  7. Vergleiche beide Anfragen nach Zeit, Modell und Status im Dashboard.

Die exakten Eventtypen stehen in der offiziellen Dokumentation: OpenAI Streaming Responses und Anthropic Messages Streaming.

FAQ

Kann ein Stream ab dem letzten Token fortgesetzt werden?

Dafür gibt es keinen universellen Mechanismus. Speichere Teilergebnis und Anwendungszustand und richte dich danach, was die konkrete API unterstützt. Eine neue Anfrage kann Text wiederholen oder anders formulieren.

Warum endet ein Stream trotz HTTP-Status 200 mit einem Fehler?

Die Header treffen vor der vollständigen Generierung ein. Ein Fehler kann später als Protokoll-Event oder als Transportabbruch auftreten. Der Status allein reicht deshalb nicht für die Beurteilung aus.

Muss nach jeder Unterbrechung wiederholt werden?

Nein. Prüfe zuerst terminales Event, Fehler, Request ID und Seiteneffekte. Bei einem Tool Call setzt ein Retry Idempotenz voraus.

Wo liegt die Ursache, wenn es lokal funktioniert?

Prüfe Reverse Proxy, Load Balancer, Idle-Timeout, Buffering und Unternehmensnetz. Vergleiche die Eventintervalle vor und nach jeder Schicht, statt nur einen lokalen Durchlauf zu betrachten.

Was sollte in BetterToken geprüft werden?

Öffne das Dashboard und gleiche Zeit, Modell, Status und Nutzung ab. Die aktuellen Anfrageparameter stehen in der API-Referenz. Sende dem Support niemals den vollständigen API Key oder einen sensiblen Prompt.

Bereit, Ihren LLM-Workflow zu optimieren?

Verbinden Sie Modelle über eine API, verwalten Sie Schlüssel und behalten Sie KI-Kosten im Blick.