ADR-0081: Pipeline LLM 呼び出し耐障害性の確立 — retry/Promise.allSettled/JSON parse 安全化
- Status: Accepted
- Mode: Standard
- Kruchten Type: Property/Executive
- Scope: platform
- Implementation Status: Done
- 起案者: [email protected]
- 起案日時 (JST): 2026-05-28 05:27
- 承認日時 (JST): 2026-06-21
- Deciders: [email protected] (単独)
コンテキスト
1.1 背景
2026-05-28 の Decision Pipeline 運用分析で、LLM API 呼び出しに関する 3 つの耐障害性問題を確認した。
1.2 現状
(1) Gate 3 parallel_review の 3 モデル並列実行 (Gemini/Claude/o3) は Promise.all を使用しており、1 モデルの失敗が Gate 3 全体を中断する (parallel_review.ts:125)。他のノード (socratic.ts:179, scoring.ts:149) は既に Promise.allSettled で部分成功を許容している。
(2) 全ノードの LLM API 呼び出しに retry/backoff ロジックがなく、一時的な 429 (rate limit) / 503 (service unavailable) でパイプライン全体が即座に abort する (gateway.ts の createLlm は maxRetries 未設定)。LangChain ChatOpenAI は maxRetries オプションを提供しているが未使用。過去 2 週間の LangSmith trace で transient failure による abort を 3 件確認 (5/15 Gate 3 Gemini 503, 5/21 Gate 4 Claude 429, 5/26 Gate 2 GraphQL timeout)。
(3) consistency.ts:164, policy_alignment.ts:123, triage.ts:85 で LLM 応答の JSON.parse を try-catch なしで呼び出しており、LLM が不正な JSON を返した場合にゲートがクラッシュする。5/18 に triage.ts で markdown code fence 混入による parse error を 1 件確認。
1.3 課題
(1) Gate 3 は 3 つの独立した視点 (ビジネス/ロジック/技術) のレビューであり、1 モデル失敗時も残り 2 モデルの結果は有効。現状は 1 モデルの transient failure で有効な 2 モデル分のレビューも破棄される。abort 率: 過去 30 runs 中 3 件 = 10%。
(2) LiteLLM 側の retry はあるが、Workers → LiteLLM 間のネットワーク障害や LiteLLM 自体の過負荷には対応できない。
(3) parse failure は現状 abort 率 ~3% (30 runs 中 1 件) だが、o3 モデルの応答は特に不安定。
1.4 制約・要件
- Cloudflare Workers の 30s wall time (async queue consumer は 15 分、ADR-0066)
- LangChain ChatOpenAI の maxRetries / retry 設定を活用 (maxRetries=3 で exponential backoff、初期 1s → 2s → 4s)
- 既存の scoring.ts は N>1 時に既に Promise.allSettled を使用 (参考パターン)
- retry は idempotent な read-only LLM 呼び出しのみ (webhook/PR 作成には適用しない)
- ADR-0056 の temperature/sampling 戦略と整合 (retry は同一 temperature で再試行)
- retry による追加 LLM コスト: 最悪ケース (全 10 ノード × 3 retry) で 1 run あたり +$4.89 (通常 $1.63 の 3 倍)。実測では retry 発生率 ~10% のため期待追加コスト ~$0.16/run。月 15 runs で ~$2.4/月。
1.5 目標
(A) Gate 3 を Promise.allSettled に変更し、成功したレビューのみを集約する (最低 1 モデル成功で通過)。 (B) gateway.ts の createLlm/createOSeriesLlm/createLlmWithEffort に maxRetries=3 を追加。 (C) safeParseLlmJson ヘルパーを新設し、JSON.parse を全ノードで try-catch + structured error に統一する。
1.6 ステークホルダー
- 運用者 (代表取締役): abort 時の手動再実行負荷が軽減
- 将来 Jr (2026-10 入社予定): pipeline の abort 率が高いと onboarding で混乱
- Pipeline 利用者 (CI trigger, ADR-0064): CI 経由の自動 trigger で abort → workflow failure → 手動介入が必要
1.7 過去 ADR との関係
| ADR | 関係 | 内容 |
|---|---|---|
| ADR-0019 (LangGraph 基盤) | Refines | LangGraph TS の node 実行パターンに retry/partial-success を追加 |
| ADR-0056 (temperature 戦略) | Confirms | retry は同一 temperature で再試行、sampling 戦略に影響なし |
| ADR-0066 (async Queues+DO) | Confirms | async consumer の 15 分 budget 内で retry 3 回は十分余裕 |
| ADR-0071 (盲点検出 DAG) | Confirms | socratic.ts の Promise.allSettled パターンを parallel_review に横展開 |
| ADR-0064 (CI trigger) | Confirms | CI 経由 trigger の abort 率低下で自動化の信頼性向上 |
| ADR-0042 (prompt lifecycle) | Neutral | prompt 管理とは直交、影響なし |
決定
LLM 呼び出しの耐障害性を 3 層防御で確立する: (1) gateway.ts の createLlm/createOSeriesLlm/createLlmWithEffort に LangChain ChatOpenAI の maxRetries=3 (exponential backoff 1s/2s/4s) を設定し HTTP 層 transient failure に対応、(2) parallel_review.ts を Promise.all から Promise.allSettled に変更し最低 1 モデル成功で通過、(3) 新規 json_utils.ts に safeParseLlmJson
判断基準 (Decision Drivers)
3.1 評価軸 (Q42 9 タグから選定)
| # | 軸 | 重要度 (係数) | 案件特有の解釈 |
|---|---|---|---|
| 1 | #reliable | [Must] (×2.0) | abort 率 10% → 数% への低減、ただし silent corruption / partial success の品質劣化を導入しないこと |
| 2 | #operable | [Must] (×2.0) | LangSmith trace の可観測性維持、retry 発生率・partial success 率の監視可能性 |
| 3 | #maintainable | [High] (×1.0) | gateway 層での一括対応、各ノードのコード重複回避 |
| 4 | #efficient | [Medium] (×0.5) | retry 追加コスト ~$2.4/月 (期待値) / 最悪 $244/月 (thundering herd) の許容範囲 |
| 5 | #suitable | [High] (×1.0) | ADR-0064 CI 自動 trigger の信頼性、ADR-0071 partial success 集約との整合 |
K.O. criterion: Must 軸 (#reliable, #operable) の score < 3 は不採用。
3.2 評価軸 × 案スコア表
| 軸 | 係数 | 採択案 A (3 層防御) | 案 B (LiteLLM retry のみ) | 案 C (個別 retry) | 案 D (現状維持) |
|---|---|---|---|---|---|
| #reliable | ×2.0 | 4 | 2 | 4 | 1 |
| #operable | ×2.0 | 3 | 4 | 3 | 5 |
| #maintainable | ×1.0 | 4 | 5 | 2 | 5 |
| #efficient | ×0.5 | 3 | 5 | 3 | 5 |
| #suitable | ×1.0 | 4 | 2 | 3 | 1 |
| 加重和 (正規化) | 0.713 | 0.613 | 0.600 | 0.575 | |
| K.O. 通過 (Must ≥3) | ✓ | ❌ (#reliable=2) | ✓ | ❌ (#reliable=1) |
加重和: 案 A = (4×2 + 3×2 + 4×1 + 3×0.5 + 4×1) / (5×6.5) = 23.5/32.5 = 0.723 → 監視・partial success リスクで -0.01 補正 = 0.713。案 A が K.O. 通過かつ最高加重和で採択。
検討した代替案 (Alternatives Considered)
- 案 A 【採用】: 3 層防御 — (1) ChatOpenAI maxRetries=3 + exponential backoff 1s/2s/4s (2) parallel_review を Promise.allSettled + 最低 1 モデル成功で通過 (3) safeParseLlmJson
ヘルパー新設 (code fence strip + try-catch + structured error)。影響ファイル: gateway.ts, parallel_review.ts, consistency.ts, policy_alignment.ts, triage.ts, 新規 json_utils.ts。工数: 実装 4h + テスト 4h + レビュー 2h = 10h。LLM 追加コスト: ~$2.4/月 (retry 期待値)。 - 案 B: LiteLLM 側の retry 強化のみ (Workers 側変更なし) — 不採用理由: Workers↔LiteLLM 間のネットワーク障害に対応不可。Promise.all/JSON.parse 問題は未解決。#reliable=2 で K.O. 不通過。
- 案 C: 個別ノード毎に retry ロジック実装 — 不採用理由: コード重複が 7 ノード × retry ロジックで保守負荷増大。gateway 層での一括対応が適切。#maintainable=2 で劣後。
- 案 D: 現状維持 (Do Nothing) — 不採用理由: abort 率 10% が継続、CI 自動化 (ADR-0064) の信頼性阻害、Jr onboarding に悪影響。#reliable=1, #suitable=1 で K.O. 不通過。
影響 (Consequences)
5.1 正の影響 (Good)
- abort 率 10% → 数% (推定 1-2%) への低減、CI 自動 trigger (ADR-0064) の信頼性向上
- 運用者 (代表取締役) の手動再実行負荷軽減、将来 Jr の onboarding 混乱回避
- safeParseLlmJson ヘルパーで JSON parse の例外処理が全ノードで一貫化し、コード品質向上
- gateway 層での一括 retry 設定により、新規ノード追加時も retry が自動適用される
5.2 負の影響 (Bad)
- [盲点 #1, #11] partial success による品質保証の暗黙的 SLA 破壊: Gate 3 の 3 視点 (ビジネス/ロジック/技術) のうち特定モデル (例: ビジネス視点担当) のみが落ちた場合、ビジネス整合性チェック欠落のままパイプラインが通過するリスク。scoring.ts の同質スコア平均と異なり、異質視点の欠落は集約結果の意味が変質する。
- [盲点 #6] retry storm / thundering herd リスク: maxRetries=3 + LangChain デフォルト backoff (jitter 未設定) を全 10 ノードに一括適用、かつ CI trigger 経由で複数 run 同時実行時に、429 発生瞬間に全 run が同一タイミングで retry を試みる。LiteLLM 過負荷 → 503 → 更に retry の正帰還ループ。
- [盲点 #2] Retry-After ヘッダ無視のリスク: LangChain ChatOpenAI が 429 レスポンスの Retry-After ヘッダを尊重するかソースコード未確認。無視する場合は短い待機で再試行し rate limit を悪化させる。5/21 Claude 429 がこのパターンの可能性。
- [盲点 #7] silent corruption リスク: safeParseLlmJson が parse 成功でも必須フィールド欠落・型不一致を許容すると、undefined アクセスや NaN 集計で Gate 判定が誤った結果を返す。abort より発見が困難。
- [盲点 #4] GraphQL timeout (5/26) は本 ADR で解決されない: maxRetries は HTTP 層 (LiteLLM へのリクエスト) のみ対象。GraphQL timeout / LiteLLM 過負荷 / Workers↔LiteLLM ネットワーク障害は本 ADR の対象外であり、別途対策が必要。
- [盲点 #10] コスト上限超過リスク: CI trigger 本格稼働で run 数増加 + retry 発生率上昇 + thundering herd の組み合わせで、最悪 $244/月 (50 runs × $4.89) のシナリオがあり予算承認外。
- [盲点 #8] LangSmith trace 肥大化: parallel_review で 3 モデル × 4 試行 = 最大 12 span/Gate、trace 数最大 4 倍、従量課金プランでのコスト増と debug 可読性低下。
- [盲点 #12] サンクコスト効果: retry で abort が見えなくなることで LiteLLM キャパシティ・Gemini 503 ・o3 JSON 不安定性の根本原因調査が後回しになるバイアスリスク。
5.3 中立・トレードオフ (Neutral / Trade-offs)
- [盲点 #2] async queue consumer 15 分 budget の検証: 単一ノードの累積 backoff 7s は十分余裕だが、Gate 3 の 3 モデル並列 retry が同時に同一プロバイダにヒットする最悪ケースの累積時間を数値で確認する必要 (実装時に計測)。
- [盲点 #3] jsonrepair 採用の先送り: 本 ADR では jsonrepair を導入せず code fence strip + try-catch のみで対応。o3 の不安定応答に対する寛容な修復は Review After 2026-11-28 で再評価。
- [盲点 #5] retry 成功応答の非決定性: temperature 同一でも LLM 応答は確率的に変化、特に o3 の chain-of-thought では retry 時に異なる推論経路の可能性。Gate 3 partial success 集約における一貫性は実測で検証。
- [盲点 #9] partial success の監視追従: abort 率が 0% に近づく一方、慢性的な Gemini degraded を見逃すリスク。participating_models / failed_models メタデータ記録 + 週次 partial success 率アラートで対応 (§6.5 参照)。
- [盲点 #13] json_utils.ts のスコープクリープ: 将来 jsonrepair / schema validation / cache / token 計測等の機能追加圧力。public API を ADR で明示し追加は別 ADR 必須とする。
- retry によるレイテンシ増加 (最悪 7s/呼び出し) は async queue consumer の 15 分 budget 内で許容範囲。
撤退条件 (Rollback Plan)
以下のいずれかが発生した場合、対象の変更を段階的に rollback する:
| 撤退トリガー | 対象 | 具体手順 | 影響範囲 |
|---|---|---|---|
| 月次 LLM コストが $50 を超過 (盲点 #10) | maxRetries 設定 | gateway.ts の maxRetries=3 を 1 に引き下げ、または 0 に戻す | LLM コスト即時低減、abort 率上昇 |
| LiteLLM 503 率が retry 導入後に 2 倍以上に悪化 (盲点 #6 retry storm) | parallel_review の retry | parallel_review のみ maxRetries=0 に変更、p-limit 導入を別 ADR で検討 | Gate 3 abort 率上昇、他ノードは継続 |
| partial success 率が週次 20% を超過 (盲点 #1, #9, #11) | Promise.allSettled | 最低成功モデル数を 1 → 2 に引き上げ、または Promise.all に戻す | Gate 3 abort 率上昇、品質保証 SLA 回復 |
| silent corruption による Gate 誤判定が 2 件以上検出 (盲点 #7) | safeParseLlmJson | zod スキーマ検証追加を緊急実装、または parse error 時に abort へ戻す | Gate abort 率上昇、品質保証回復 |
| LangSmith trace コストが月次 $20 超 (盲点 #8) | retry trace 記録 | retry span retention 短縮、または retry 回数を 3 → 1 に引き下げ | debug 可観測性低下、コスト即時低減 |
- 判定指標: 2026-08-28 (3 ヶ月後) — retry 発生率・abort 率・partial success 率・追加 LLM コスト実測の定量レポート (盲点 #12 の根本原因調査義務化基準を含む)。2026-11-28 (6 ヶ月後) — jsonrepair / zod 依存リスク再評価。
- 代替案: rollback 後は案 C (個別ノード retry + 重要ノード優先) または案 B (LiteLLM 側強化) を別 ADR で検討。
Confirmation (準拠確認 / Fitness Function)
| 検証項目 | 検証手段 | 実行頻度 | 違反時の対応 |
|---|---|---|---|
| gateway.ts の createLlm/createOSeriesLlm/createLlmWithEffort に maxRetries=3 が設定されているか | ESLint カスタムルール または ts-grep スクリプトで new ChatOpenAI( 呼び出しに maxRetries が含まれることを検証 | PR ごとに CI (lint stage) | CI fail、PR マージブロック |
| parallel_review.ts が Promise.allSettled を使用しているか | scripts/adr-lint.mjs に追加し parallel_review.ts に Promise.all( の禁止パターン検出 | PR ごとに CI | CI fail、PR マージブロック |
| consistency.ts / policy_alignment.ts / triage.ts で JSON.parse が safeParseLlmJson 経由か | ts-grep で JSON.parse( 直接呼び出しを禁止 (json_utils.ts 除く) | PR ごとに CI | CI fail、PR マージブロック |
| retry 発生率・partial success 率の可観測性 | LangSmith trace に is_retry / attempt_number / participating_models / failed_models メタデータが記録されているか手動 QA で確認 (盲点 #8, #9) | 週次運用レビュー | LangSmith dashboard 整備、メタデータ追加 PR を 2 週間以内に起票 |
| 月次 LLM コスト上限 ($50) 監視 (盲点 #10) | billing dashboard アラート (Slack 通知) | 日次自動 | 撤退条件発動、maxRetries 引き下げ |
| partial success 率の週次閾値 (20%) 監視 (盲点 #1, #9, #11) | LangSmith trace 集計 + Slack/PagerDuty アラート | 週次自動 | 撤退条件発動、最低成功モデル数を 2 に引き上げ |
| json_utils.ts のスコープクリープ防止 (盲点 #13) | public API を ADR に明示、新規 export 追加時は別 ADR 必須を README に明記 + コードレビューチェックリスト | PR レビュー時 | 別 ADR 起票要求、マージブロック |
参照 (References)
- 関連 ADR: ADR-0019 (LangGraph 基盤, Refines), ADR-0056 (temperature 戦略, Confirms), ADR-0066 (async Queues+DO, Confirms), ADR-0071 (盲点検出 DAG, Confirms), ADR-0064 (CI trigger, Confirms), ADR-0042 (prompt lifecycle, Neutral)
- 関連 PR/Issue: (要追記)
- 外部資料:
- LangChain ChatOpenAI maxRetries オプション仕様 (要追記 URL)
- LangSmith trace 仕様
- 過去 abort 事例: 5/15 Gate 3 Gemini 503, 5/18 triage.ts JSON parse error, 5/21 Gate 4 Claude 429, 5/26 Gate 2 GraphQL timeout
- Review After: 2026-08-28 (3 ヶ月後) — retry 発生率・abort 率・partial success 率・追加 LLM コスト実測 / 2026-11-28 (6 ヶ月後) — jsonrepair 依存リスク再評価