4.5 KiB
llm-worker: ストリーム途中失敗時の継続
背景
LLM 応答の SSE ストリームを読んでいる途中で upstream が切れると、
crates/llm-worker/src/llm_client/transport.rs:231 で
ClientError::Sse(...)(中身は eventsource_stream::Error::Transport、
さらに reqwest の error decoding response body)として上に投げられ、
worker.rs:933 stream_response が WorkerError に変換して Run 全体が中断する。
実例: セッション 019de419-6f8b-71a0-9e56-f0b9a6c7098b.jsonl:236。
直前まで text / tool_use を Timeline に積み続けていたが、
最後の SSE フレームが届く前に接続が切れ、Run が落ちた。
ここで重要な点:
- 出力トークンは upstream 側で既に発生しており、課金済み。 単純に同じ request を再送すると二重出力 + 二重課金。
- Anthropic / OpenAI のいずれの API も、途切れた SSE を resume するエンドポイントは持たない。継続したい場合は 「assistant turn として部分出力を history に置いた状態で再リクエスト」 という形を自作する必要がある。
- 部分出力の質は内容依存:
- 完成した text ブロックは原則そのまま history に置ける
- tool_use の
input_jsonが途中で切れたブロックは破損 JSON で、そのままは置けない - reasoning / thinking ブロックも provider 依存の扱いが要る
このため、これは「リトライ」ではなく「継続 (continuation)」。
history を編集する責務であり、transport.rs には収まらず、
worker.rs 層(または上位)の機能になる。
feedback_llm_worker_scope.md の方針(llm-worker は低レベル基盤に留める)にも合致する。
なお worker.rs:973 付近で部分 flush_usage() だけは既に行っており、
半分くらいは継続を意識した作りになっている。あとは
「壊れていないブロックの確定」と「次ターンの起動条件」を足す形。
詰めたい論点(実装前に決める)
このチケットは仕様議論を含む。以下を確定させてから実装に入る。
-
部分ブロックの取り扱い基準
- 完成した text ブロックは確定して history に push するか
- 未完の tool_use は捨てるのが妥当か、暫定 stop で残すか
- reasoning / thinking ブロックの扱い(provider 別)
-
継続の起動方式
- 自動的に次ターンを回す(= retry-like 挙動)/ Pause で上位に判断委譲 / manifest で切替、のいずれか
- デフォルトは何か(自動継続は意図しない出費を生む可能性あり)
-
ループ防止
- 同種の transport エラーが連続 N 回起きたら諦める閾値
- 「ストリーム開始後ほぼ即座に切れる」が連続するパターンの検知
-
他の中断要因との優先度
Cancelled/Aborted/ interceptor のYieldが同時に起きたときの順序
-
可観測性
- 「途中で切れて継続した」事実をセッションログに残す形
ClientError::Sse(String)をSse { kind: Parse | Transport, msg }に 分割するかどうか(診断容易性のため)
要件(暫定。論点確定後に再記述)
- ストリーム途中で transport 由来のエラーが出た場合、
worker.rsがそれを catch し、Timeline に積まれた完成ブロックだけを assistant items として確定する。 - 未完ブロック(特に壊れた tool_use)は破棄するか、 破棄したことを示す形で履歴に残す(決定は論点 1)。
- 継続するか中断するかの判定が、論点 2 の決定に従って分岐する。
- 連続失敗時に止まる(論点 3)。
- 既存の
Cancelled/Abortedパスが優先される(論点 4)。
完了条件
- 上記論点が決まり、ドキュメント or チケット本文に反映されている。
- ストリーム途中で切れるモックを使った integration test が、 決まった仕様どおりに継続 or 中断する。
- 課金重複が起きないこと(自動継続でも、過去ターンの再生成は発生しない)が test または手動手順で確認されている。
cargo check/cargo testがllm-workerで通る。
範囲外
- pre-stream の transient リトライ →
llm-worker-transient-retry - ストリーム resume API の実装(プロバイダ側に存在しないので不可能)
- 課金額の自動上限制御