ストリーミングとSSEの切断:重複なく復旧する方法

SSE切断を見分け、途中出力を保持し、副作用を重複させない安全な再試行を行う方法。

StreamingとSSEの切断: 重複なしで復旧する

Streaming APIは、プロトコルが定めるterminal eventを受信して初めて完了です。socketの切断、client timeout、最後のテキスト断片は完了の証拠になりません。切断時には受信済みevent、Request ID、操作状態を保存し、副作用を確認してからretryします。最後のtokenから普遍的に再開できるわけではなく、盲目的なretryはtoolを二度実行します。

正常終了か実際の切断か

SSEは長いHTTP接続でevent列を送ります。

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

completedは成功を確認するterminal event、failedはstream内のエラー、disconnectedは確認済み終了なしの転送終了です。OpenAI Responsesではresponse.created、output断片、response.completedなどを使います。Anthropic Messagesはmessage_start、content block event、message_deltamessage_stopを使います。一つのparserに両方の名前を混ぜません。

短いtoolなしstreamで再現するなら、BetterTokenのアカウントとAPI Keyを作成し、APIリファレンスを確認します。時間、model、statusをDashboardと照合し、terminal eventとpartial outputを確認してから限定retryを加えます。

切断時に保存するデータ

{"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"}

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します。

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_successis_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は有用でも、完了として見せません。状態はstreamingcompletepartialfailedcancelledに分けます。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

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

retry可能かはprotocol error、HTTP status、terminal event、副作用で決めます。401、誤ったModel ID、無効JSONは待っても直りません。429、一時的な5xx、transport failureはprovider headerを考慮し、上限付きなら再試行できる場合があります。

最小テスト

  1. toolなしの短いstreamを送る。
  2. event typeを記録しterminal eventを待つ。
  3. 数event後にclientを人工的に切断する。
  4. 結果がpartialと表示されることを確認する。
  5. 限定retryを一度確認する。
  6. 本番proxy背後でも繰り返す。
  7. Dashboardで時間、model、statusを照合する。

正確なevent typeはOpenAI Streaming ResponsesAnthropic 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へ送らないでください。

LLM ワークフローを最適化しませんか?

単一 API でモデルを接続し、キーと AI コストを管理できます。