LLMストリーミングのtimeoutとretryを単位から調べる

OpenCodeのようなエージェントからローカルvLLMや遅い推論APIを呼ぶと、最初のchunkまで長く待つことがあります。
ここで、ストリーミングをtokenごとの独立したリクエストだと考えると、timeoutとretryの意味を誤ります。
実際には、1つのHTTPリクエストが接続を開き、レスポンスボディを複数のchunkで受け取ります。
ストリームの通信単位を確認する
LLMのストリーミングは、概ね次の流れです。
client
│ POST /v1/chat/completions
▼
API server
│ response headers
│ data: token chunk
│ data: token chunk
│ data: [DONE]
▼
client
受信したchunkは、同じHTTP接続を流れるレスポンスの一部です。
途中で接続が切れても、サーバーへ「N tokenの続き」を要求できるとは限りません。
再開位置、生成状態、リクエストの同一性を扱うプロトコルを、サーバーとクライアントの両方が実装している必要があります。
その仕組みがない場合、クライアントは元のリクエストを最初から再実行するか、失敗として扱います。
timeoutを全体とchunk間隔へ分ける
OpenCodeの現行設定資料には、リクエスト全体のtimeoutと、chunk間隔を監視するchunkTimeoutがあります。
公式資料ではtimeoutの既定値を300,000ミリ秒とし、falseで無効化できると説明しています。
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"local-vllm": {
"options": {
"timeout": 600000,
"chunkTimeout": 120000
}
}
}
}
2つの値を同じにする必要はありません。
初回chunkが遅いモデルでは、全体のtimeoutだけでなく、初回chunkまでのchunkTimeoutも十分に長くします。
OpenCodeのSSE処理は、現行ソースでは各readをchunkTimeoutで監視します。
そのため、最初のchunkを待つreadも対象になります。
例のchunkTimeoutが120秒なら、全体のtimeoutを600秒にしても、初回chunkが120秒を超えた時点で中断されます。
生成中に一時停止するモデルでは、chunk間隔を許容する値が必要です。
ただし、timeoutを大きくすると通信障害の検出も遅くなります。
chunkTimeoutを小さくすると、正常な計算中でも接続を切ります。
設定値は大きければよいのではなく、初回chunkまでの時間と生成中のchunk間隔の最大値を測って決めます。
通信再試行と意味的再試行を分ける
接続断の再試行可否は、失敗した時点で変わります。
レスポンスを1度も観測していない場合は、サーバーが処理を始めたか判断できません。
途中までレスポンスを受け取った場合も、サーバーが生成を終えたのか最後のchunkだけ失ったのか分かりません。
同じリクエストを再送すると、処理が二重になる可能性があります。
テキスト生成だけなら、主な影響は計算時間と料金です。
tool callや外部APIへの書き込みを含む処理では、副作用も二重になります。
最低限、次の2種類を分けます。
- transport retry: 接続前または応答観測前の一時的な失敗を同じ入力で再送します。
- semantic retry: 応答後にJSON形式や内容が不正だったため、指示を変えて再生成します。
OpenCodeのLLMパッケージ設計資料では、一時的な転送エラーやrate limitを再試行候補として扱います。
一方、観測可能な出力やtoolの副作用が発生した後は、暗黙に再試行しない方針を説明しています。
この挙動はハーネス、バージョン、provider SDKで変わり得ます。
利用中の設定とログで、実際のretry境界を確認します。
ローカルvLLMを直接呼んで層を分ける
OpenCodeからローカルvLLMを呼ぶ場合は、最初にAPIサーバーへ直接リクエストを送ります。
curl http://localhost:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "MODEL_ID",
"messages": [{"role": "user", "content": "短い応答を返してください。"}],
"stream": true,
"max_tokens": 32
}'
直接のストリームが安定するなら、次にOpenCodeのprovider設定と起動ログを確認します。
問題は次の4つへ分けます。
- 初回chunkまでが遅い
- 生成中のchunk間隔が長い
- 一定時間後に接続自体が切れる
- 応答後のJSON検証やtool実行が失敗する
1と2はtimeout調整の候補です。
3はproxy、HTTP client、サーバー、ネットワークのどこが切断したかを調べます。
4はストリーミングではなく、出力検証とsemantic retryの問題です。
retryを識別子で追跡する
同じ論理処理を再実行するクライアントでは、少なくとも3つのIDをログへ残します。
run_id=20260819-001
operation_id=12
transport_attempt=2
semantic_attempt=1
これで、接続断による再送と、内容不正による再生成を同じretryとして混同しません。
外部APIへ書き込む場合は、サーバー側でoperation_idを冪等性キーとして扱う設計も検討します。
ログへaccess key、token、プロンプトに含まれる個人情報を無条件に書きません。
設定値を測定から決める
最初から極端に大きなtimeoutを設定せず、実際の応答を測ります。
- 短い非ストリーミングリクエストを送る
- streamingを有効にして初回chunkまでを測る
- 生成中の最大chunk間隔を測る
- 全体処理時間へ余裕を足して
timeoutを決める - 最大chunk間隔へ余裕を足して
chunkTimeoutを決める - connection drop、429、5xx、出力形式エラーを別々に確認する
- retry回数と境界をログで確認する
切断を減らすには、timeoutだけでなく入力長、最大出力長、並列数も調整します。
ハーネス設定はサーバーの計算を速くしません。
正常に計算している時間と接続停止を区別し、失敗後に再実行する単位を決める境界です。
