ストリーミングとSSEの切断:重複なく復旧する方法
SSE切断を見分け、途中出力を保持し、副作用を重複させない安全な再試行を行う方法。
StreamingとSSEの切断: 重複なしで復旧する
Streaming APIは、プロトコルが定めるterminal eventを受信して初めて完了です。socketの切断、client timeout、最後のテキスト断片は完了の証拠になりません。切断時には受信済みevent、Request ID、操作状態を保存し、副作用を確認してからretryします。最後のtokenから普遍的に再開できるわけではなく、盲目的なretryはtoolを二度実行します。
正常終了か実際の切断か
SSEは長いHTTP接続でevent列を送ります。
completedは成功を確認するterminal event、failedはstream内のエラー、disconnectedは確認済み終了なしの転送終了です。OpenAI Responsesではresponse.created、output断片、response.completedなどを使います。Anthropic Messagesはmessage_start、content block event、message_delta、message_stopを使います。一つのparserに両方の名前を混ぜません。
短いtoolなしstreamで再現するなら、BetterTokenのアカウントとAPI Keyを作成し、APIリファレンスを確認します。時間、model、statusをDashboardと照合し、terminal eventとpartial outputを確認してから限定retryを加えます。
切断時に保存するデータ
API Key、完全なprompt、tool引数、機微な応答は保存しません。partial textはアプリの規則が許す場合だけ保存します。本番では操作hash、event数、最後の安全なsequence markerが有用です。Request IDはheaderまたはeventから得られるため、stream終了後ではなく早期に保存します。
ネットワークより先にparserを確認する
複数のdata:行、空行、network chunk境界のUTF-8、未知event、HTTP 200後のerror event、最後のtext deltaのないterminal event、分割されたtool引数を処理します。TCP chunkはSSE eventではありません。まず完全なSSE frameを組み立て、それからJSONをparseします。
is_terminal_successとis_terminal_failureはResponses、Chat Completions、Messagesごとに分けます。
各層のtimeoutを確認する
SDK/HTTP client、アプリのidle/read timeout、reverse proxy、load balancer/ingress、社内proxy、モバイルまたは家庭回線、server-side generation limitを確認します。request timeoutとidle timeoutは別です。eventが継続して来るなら短いidle timeoutは発火すべきではありません。proxyがSSE chunkをbufferすると、長時間無表示の後に大きな塊かtimeoutになります。最初のeventまでの時間とevent間隔を、ローカル、proxy背後、本番で測定します。
partial outputを表示、保存、破棄する
途中のtextは有用でも、完了として見せません。状態はstreaming、complete、partial、failed、cancelledに分けます。partialは受信済みtextを新しい生成と別に保存します。二つの生成を自動連結すると、文章の重複、表現の変化、tool呼び出し順の変化を招きます。
リクエストを再実行できる場合
安全性は操作内容で決まります。
外部操作のないテキスト
短いtext requestは試行回数を制限して再実行できます。古い結果をpartial、新しい結果を別生成として表示するか、明示確認後に置換します。
Tool Callとトランザクション
retry前にtoolがすでに実行済みか確認します。command送信後に切断すると、二度目のrequestがissue、メール、支払いを重複作成できます。tool単位のIdempotency Key、独自のOperation ID、完了操作のlogを使います。
長いAgentタスク
「最後のtokenから継続」はプロトコルで確認されないことが多いです。確認済みmessage、tool result、最後に終えたstepを含む保存済みapplication stateから復元します。生のtext fragmentは一貫したagent stateではありません。
backoff付きの限定retry
retry可能かはprotocol error、HTTP status、terminal event、副作用で決めます。401、誤ったModel ID、無効JSONは待っても直りません。429、一時的な5xx、transport failureはprovider headerを考慮し、上限付きなら再試行できる場合があります。
最小テスト
- toolなしの短いstreamを送る。
- event typeを記録しterminal eventを待つ。
- 数event後にclientを人工的に切断する。
- 結果が
partialと表示されることを確認する。 - 限定retryを一度確認する。
- 本番proxy背後でも繰り返す。
- Dashboardで時間、model、statusを照合する。
正確なevent typeはOpenAI Streaming ResponsesとAnthropic Messages Streamingで確認します。
FAQ
最後のtokenからstreamを続けられますか?
普遍的な仕組みはありません。partial resultとapplication stateを保存し、対象APIの機能に従います。新しいrequestはtextを繰り返したり変更したりします。
HTTP statusが200なのにstreamがerrorで終わるのはなぜですか?
headerは生成完了前に届きます。後からprotocol eventまたはtransport切断としてerrorが現れるため、statusだけでは足りません。
切断のたびに再実行すべきですか?
いいえ。terminal event、error、Request ID、副作用を先に確認します。Tool Callのretryにはidempotencyが必要です。
ローカルでは動く場合、どこを確認しますか?
reverse proxy、load balancer、idle timeout、buffering、社内networkを調べ、各層の前後のevent間隔を比べます。
BetterTokenでは何を確認しますか?
Dashboardで時間、model、status、usageを照合します。APIリファレンスで現在のrequest parameterを確認し、完全なAPI Keyや機微なpromptをsupportへ送らないでください。