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、時刻を比較します。
文字が表示されないとき
- "stream": falseで同じRequestを送る。
- ModelがEndpointを支えるか確認する。
- UI変換前のRaw Eventを保存する。
- TextなしのToolまたはReasoning Eventを確認する。
- 中間層のBufferingを除外する。
- ParserがTerminal Eventを扱うか確認する。
- 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イベントを読んでいる可能性があります。モデルを変える前に未加工イベントを取得してください。
途中で切れたストリームを再試行すべきですか?
操作を安全に繰り返せる場合だけです。上流処理やツールは既に実行済みかもしれません。試行回数を制限し、副作用がある場合は冪等性を必須にしてください。