AI API Streaming:SSEとTimeout

ChatとResponsesのEvent、SSE Parsing、最初の有効出力、段階別Timeout、499 Cancellationを解説します。

AI APIのStreamingは、完全なResponse Bodyを待たず、モデル生成中にEventを順次届けます。体感応答は改善しますが、モデル自体のLatencyが必ず短くなるわけではなく、クライアントはEndpointに合うProtocolを正しく解析する必要があります。

Chat CompletionsはCompletion Chunk、Responsesは型付きResponse Eventを送ります。期待する形式が違えば、HTTP 200でも画面に何も出ないことがあります。

Bufferingなしで確認する

curl -N -sS https://modelflare.dev/v1/responses \
  -H "Authorization: Bearer $MODELFLARE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"YOUR_RESPONSES_MODEL","input":"Explain SSE.","stream":true}'

同じRequestを最初にStreamingなしで試すと、入力検証とStream Parserの問題を分離できます。モデルIDはモデルと料金から選びます。

SSEをProtocolとして扱う

Server-Sent Eventsは境界を持つRecordであり、任意のJSON断片ではありません。本番ClientではHTTP Library、Proxy、UIのBufferingを避け、分割Readを完全なEventまで蓄積し、Text、Reasoning、Tool、Completion、Errorを処理します。Cancellationと最終Usageを保持し、Terminal Event後は接続を閉じます。

遅延を段階別に測る

指標 意味
接続と認証 Gatewayへ到達してKeyを検証するまで
Upstream Header 選択経路が応答を開始するまで
最初の有効出力 最初の有用なText、Reasoning、Tool Event
最初の可視Text ユーザーが実際に見られる最初の文字
総応答時間 完了、失敗、Cancellationまで

Tool Callは可視Textより前に有効な出力となる場合があります。運用には最初の有効出力、UXには最初の可視Textも使います。

Timeoutを段階別に設計する

接続、HeaderまたはFirst Output、Stream Idle、全体Deadlineを分けます。ReasoningやToolのRequestは可視Textまで長く待つことがあります。短い全体Timeoutを一律適用せず、実測したWorkloadから設定します。

Client、Browser、Proxyが先に接続を閉じると、Modelflareは499を記録することがあります。これはDownstream Cancellationの証拠であり、ModelやChannelの失敗を単独では証明しません。Abort、Proxy Timeout、First Output、Model、Group、時刻を比較します。

文字が表示されないとき

  1. "stream": falseで同じRequestを送る。
  2. ModelがEndpointを支えるか確認する。
  3. UI変換前のRaw Eventを保存する。
  4. TextなしのToolまたはReasoning Eventを確認する。
  5. 中間層のBufferingを除外する。
  6. ParserがTerminal Eventを扱うか確認する。
  7. Status、Timing、Cancellationを記録で比較する。

非Streamingが成功しRaw Eventも届くなら、原因はParserかRenderingにあることが多いです。Event自体がなければAI APIエラーガイドへ進みます。形式選択はResponses APIとChat Completionsを参照してください。

両方のエンドポイントをバッファリングなしで比較する

先のResponsesの例に加え、Chat Completionsも固有の形式で試します。

curl -N -sS https://modelflare.dev/v1/chat/completions \
  -H "Authorization: Bearer $MODELFLARE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "YOUR_CHAT_MODEL",
    "messages": [{"role": "user", "content": "SSEを3点で説明してください。"}],
    "stream": true
  }'

パーサーには次の処理が必要です。

  • HTTPライブラリ、プロキシ、画面で応答をバッファリングしない。
  • 部分的な読み取りを完全なイベントになるまで連結する。
  • 選択したエンドポイントのイベント種別を解釈する。
  • テキスト、推論、ツール、完了、エラーを区別する。
  • 可視テキストがなくても有効な応答を扱う。
  • キャンセル状態と最終使用量を保持する。
  • 終端イベントを受け取ったらストリームを閉じる。

本番ストリーミングの確認項目

  • パーサーをChat CompletionsかResponsesのどちらかに明示的に対応させる。
  • テキスト、推論、ツールのみ、エラー、正常終了を試す。
  • 最初のイベント、有効出力、可視テキストを別々に測る。
  • 接続、最初の出力、アイドル、処理全体に別々の期限を設ける。
  • 意図が明確なキャンセルシグナルを使う。
  • 安全な冪等性がない限り、再試行は新しいリクエストとして扱う。
  • 診断用にID、ステータス、モデル、グループ、時間を残す。
  • 中間層がストリームを再びバッファリングしていないことを確認する。

よくある質問

ストリーミングでモデルの生成自体が速くなりますか?

必ずしも速くなりません。出力を早く見せる仕組みであり、最初のイベントまでの時間やその後の速度は、モデル、ルート、コンテキスト、推論、ツールに左右されます。

curlでは動くのにアプリケーションで何も表示されないのはなぜですか?

アプリケーションや中間層が応答をバッファリングしているか、Chat Completions用パーサーでResponsesイベントを読んでいる可能性があります。モデルを変える前に未加工イベントを取得してください。

途中で切れたストリームを再試行すべきですか?

操作を安全に繰り返せる場合だけです。上流処理やツールは既に実行済みかもしれません。試行回数を制限し、副作用がある場合は冪等性を必須にしてください。