yoi/tickets/llm-worker-stream-continuation.md

88 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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()` だけは既に行っており、
半分くらいは継続を意識した作りになっている。あとは
「壊れていないブロックの確定」と「次ターンの起動条件」を足す形。
## 詰めたい論点(実装前に決める)
このチケットは仕様議論を含む。以下を確定させてから実装に入る。
1. **部分ブロックの取り扱い基準**
- 完成した text ブロックは確定して history に push するか
- 未完の tool_use は捨てるのが妥当か、暫定 stop で残すか
- reasoning / thinking ブロックの扱いprovider 別)
2. **継続の起動方式**
- 自動的に次ターンを回す(= retry-like 挙動)/ Pause で上位に判断委譲 /
manifest で切替、のいずれか
- デフォルトは何か(自動継続は意図しない出費を生む可能性あり)
3. **ループ防止**
- 同種の transport エラーが連続 N 回起きたら諦める閾値
- 「ストリーム開始後ほぼ即座に切れる」が連続するパターンの検知
4. **他の中断要因との優先度**
- `Cancelled` / `Aborted` / interceptor の `Yield` が同時に起きたときの順序
5. **可観測性**
- 「途中で切れて継続した」事実をセッションログに残す形
- `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 の実装(プロバイダ側に存在しないので不可能)
- 課金額の自動上限制御