71 lines
3.3 KiB
Markdown
71 lines
3.3 KiB
Markdown
# llm-worker: HTTP transient リトライ
|
||
|
||
## 背景
|
||
|
||
`crates/llm-worker/src/llm_client/transport.rs` はリトライを持たず、
|
||
upstream が一時的に不調だったときのエラーがそのまま `WorkerError` に
|
||
伝播して Run が中断する。実セッションでも以下が観測されている:
|
||
|
||
- セッション `019de419-6f8b-71a0-9e56-f0b9a6c7098b.jsonl:85` で
|
||
`API error (status: 503): upstream connect error ... Connection refused`。
|
||
これは `transport.rs:194-216` で `response.status().is_success()` 前に
|
||
返される pre-stream の経路。リクエストはまだ消費されていない。
|
||
- Anthropic の `overloaded_error` (529) や Codex backend の 503 も
|
||
同経路で観測される transient な事象。
|
||
|
||
これらは「ヘッダが返る前の段階」で出るため、SSE を読み始めて
|
||
出力トークンを発生させる前であり、素朴な再送でべき等に復旧できる典型ケース。
|
||
ストリームが途中で切れた場合のリカバリは別の話(→ `llm-worker-stream-continuation`)。
|
||
|
||
## 方針
|
||
|
||
`transport.rs` の HTTP 送信層に transient エラー向けの再送を追加する。
|
||
SSE 読み出し開始後 (`response.bytes_stream()` 以降) のエラーは対象外で、
|
||
従来どおり `ClientError::Sse` として上に流す。
|
||
|
||
scheme(OpenAI / Anthropic / Responses 等)に依存しない共通処理として、
|
||
すべての client から同じ振る舞いで使える形にする。
|
||
|
||
## 要件
|
||
|
||
### リトライ対象
|
||
|
||
- HTTP ステータス: 408 / 425 / 429 / 500 / 502 / 503 / 504 / 529
|
||
- `reqwest::Error::is_connect()` / `is_timeout()` 由来の送信失敗
|
||
- それ以外の `ClientError::Api { status }` および `ClientError::Json`、
|
||
ストリーム開始後の `ClientError::Sse` は対象外
|
||
|
||
判定は `is_retryable(&ClientError) -> bool` を `error.rs` に置いて一箇所に集約する。
|
||
|
||
### バックオフ
|
||
|
||
- フルジッタ付き指数(base/cap は実装時に妥当な値で固定。後で manifest 化したくなったら別ticket)
|
||
- `Retry-After` ヘッダがあれば指数バックオフを上書きしてその時間待つ
|
||
- 上限: 最大試行回数 + 累積タイムアウトの両方を持つ
|
||
|
||
### ログ
|
||
|
||
- リトライ発火ごとに `warn!`(ステータス、attempt 番号、次の wait)
|
||
|
||
### 既存挙動の温存
|
||
|
||
- ストリーム途中で切れた場合の挙動には手を入れない
|
||
(`transport.rs:231` の `ClientError::Sse` 経路はそのまま)
|
||
- 成功時のレイテンシに観測可能なオーバヘッドを足さない
|
||
|
||
## 完了条件
|
||
|
||
- `is_retryable` のテーブル駆動 unit test
|
||
- 503 / 529 / connect refused をモックした unit test が、
|
||
規定回数までリトライして「最終的に成功」「上限到達でエラー」の両ケースを通る
|
||
- `Retry-After: 5` を返すモックでは指数を上書きして 5s 待っている
|
||
(仮想時間で検証)
|
||
- mid-stream で `ClientError::Sse` を起こすモックでリトライが発火しない
|
||
- `cargo check` / `cargo test` が `llm-worker` で通る
|
||
|
||
## 範囲外
|
||
|
||
- mid-stream(SSE 読み中)の継続再開 → `llm-worker-stream-continuation`
|
||
- プロバイダ別の細かい retry policy(共通既定で十分)
|
||
- リトライ上限値の manifest からの上書き(必要になったら別ticket)
|