RQ-084: Anthropic prompt caching の実効性検証 (bizlp 使用パターン下)
起案日: 2026-05-29
起案者: [email protected]
ステータス: active (調査未着手)
背景: ADR 起案テキスト tmp/adr-pipeline-prompt-caching-draft.json が Pipeline Cross-Validation Gate (ADR-0076) で差し戻し。critical 盲点「5m TTL 下で cache hit 率実質ゼロ + docs-search-worker の read ≒ 0 観察 → コスト試算崩壊」を解消するため、調査タスクに格下げ。
0. 経緯
2026-05-28 に Anthropic Console (bizlp ワークスペース) で「プロンプトキャッシュ未使用」警告 + 月 USD 202.54 のコスト データを確認。drp の 4 Opus 4.7 ノードを caller と特定し、cache_control 適用を ADR 起案 (sessionId: d62fcf0a-64e9-46b7-930a-4b9ac637a551, draft id adr-pipeline-prompt-caching) として Pipeline に投入。
結果 (2026-05-28 終了):
| 項目 | 値 |
|---|---|
| Score | 47/50 (Standard 閾値 40 超え) |
| Cross-Validation Gate | rejected (Critical blind-spot が #efficient Must 軸を毀損) |
| Blind-spot findings | 8 件 (critical 1 / high 3 / medium 3 / low 1) |
| 差戻し理由要約 | コスト試算の根拠未検証、docs-search-worker の実測値 (read≒0) と矛盾 |
Pipeline の指摘は妥当。検証データ不足のまま本番投入提案だったため、ADR 起案は中止し本 RQ に格下げ。
1. 調査設問
1.1 P1 設問 (実装可否判断に必須)
docs-search-worker で write が発生しつつ read ≒ 0 なのはなぜか?
- 候補仮説 A: 5m TTL 内に同一 prefix が再呼び出しされていない (検索クエリの prefix が毎回異なる)
- 候補仮説 B: cache_control の付与位置が誤っている
- 候補仮説 C: Anthropic Console の集計が write/read を別画面で表示しており、実は read もある (今回見たグラフは Opus 4.7 のみ、Sonnet 4.6 は別途確認要)
- 候補仮説 D: Cloudflare Workers の cold start で SDK が新規 cache key を生成している
drp の Pipeline 呼び出し頻度の実測
- 5/25-26 のスパイク日: 1 時間あたり何本の Pipeline run が走ったか
- 散発日: 1 日 1-2 本程度か、ゼロか
- データ source: Cloudflare Worker logs / D1 (ADR-0077 telemetry) / Anthropic Console グラフ
各 Opus 4.7 ノードの systemPrompt token サイズ計測
- body_generation / scoring / consistency / reviewClaude の各 systemPrompt が Opus 4.5+ の最小 cacheable トークン 4,096 を超えているか
- 計測方法:
prompts/production/<id>/prompt.meta.yamlから系統的に集計、または実 KV から取得して tokenizer 実行 - 4K 未満のノードは cache_control 付与しても効かないため対象外
1.2 P2 設問 (実装最適化のため)
LiteLLM v1.40+ の cache_control passthrough 仕様
- 公式ドキュメントでの passthrough 保証範囲
messages[].content[].cache_control形式とextra_body形式のどちらが推奨か- bizlp の LiteLLM version (
litellm/config.prod.yaml参照) で対応済か
TTL 戦略の最適化
- 5m vs 1h の break-even hit rate (write 1.25x vs 2x のコスト構造)
- bizlp の集中投入パターン (連続 4-8 Opus 呼び出し/起案) でどちらが有利か
- 複数 cache_control breakpoint (tools / system / messages) を組み合わせる必要があるか
代替案: モデル選択最適化との比較
- Pipeline の Opus 4.7 ノード (body/scoring/consistency/reviewClaude) の一部を Sonnet 4.6 にダウングレードした場合のコスト削減効果
- 品質劣化リスク (ADR-0033 で Opus にアップグレードした経緯あり)
- cache_control 適用と比較して、どちらが ROI が高いか
1.3 P3 設問 (中長期最適化)
複数 breakpoint 戦略
- tools / system / 会話履歴 の階層キャッシュ (Anthropic docs §"Multiple cache breakpoints")
- 4 breakpoint まで使えるが、Pipeline 構造に適合するか
Pre-warming の有効性
max_tokens: 0でのキャッシュ事前ロード (Anthropic docs §"Pre-warming the cache")- Pipeline 起動前 (CI / 定期 trigger) で warming する設計
2. 検証手順 (推奨実行順序)
Step 1: docs-search-worker の cache 実測 (P1-Q1)
# Anthropic Console で Sonnet 4.6 のグラフを切り替え、read/write 比率確認
# wrangler tail で実時間のリクエスト patterns 観察 (1-2 日)
npx wrangler tail docs-search-worker --format pretty
期待アウトプット: read ≒ 0 の原因仮説 (A-D) のうちどれが当てはまるか確定。
Step 2: Pipeline 呼び出し頻度の集計 (P1-Q2)
# D1 telemetry から起動間隔を集計
npx wrangler d1 execute pipeline-telemetry \
--command "SELECT DATE(created_at) AS day, COUNT(*) AS runs, MIN(created_at) AS first, MAX(created_at) AS last FROM runs GROUP BY day ORDER BY day DESC LIMIT 30"
期待アウトプット: 同一日内の連続実行間隔ヒストグラム。5m 以内に次の Pipeline が走る確率を算出。
Step 3: systemPrompt token サイズ計測 (P1-Q3)
# 各ノードの prompt.meta.yaml + system prompt から tokenizer で計測
# 簡易計算: 日本語は 1 token ≒ 1.5 chars 想定
# 精密: @anthropic-ai/sdk の count_tokens API
期待アウトプット: 4 ノードのうち何個が 4K tokens 以上か (caching 対象候補数)。
Step 4: LiteLLM passthrough の検証 (P2-Q4)
# dev 環境で 1 Pipeline run を仕掛けて、Anthropic Console で
# cache_creation_input_tokens > 0 が出るか確認
# 出なければ LiteLLM 側で stripping している
Step 5: 代替案比較 (P2-Q6)
各ノードを Sonnet 4.6 に変えた場合のコスト試算 (Opus $5/MTok → Sonnet $3/MTok = 40% 削減 vs caching の 90% 削減)。
3. 完了条件 (Definition of Done)
- P1-Q1: read ≒ 0 の原因が特定され、ADR 案で対処可能か判定
- P1-Q2: Pipeline 呼び出しパターンが定量化され、5m hit 確率が算出
- P1-Q3: 各 Opus ノードの systemPrompt token サイズ確定、4K 超のノード数判明 (2026-05-29 計測完了、§5 参照)
- 上記 3 設問の結果から ADR 再起案の可否を判断
- 「効果あり」と判定された場合のみ ADR 再起案、結果は本 RQ にも追記
4. 関連リソース
- 元 ADR 起案テキスト:
tmp/adr-pipeline-prompt-caching-draft.json(KV からは削除済、ローカルのみ保持) - Pipeline session:
d62fcf0a-64e9-46b7-930a-4b9ac637a551(2026-05-28 rejected) - TODO バックログ:
docs/_internal/02_project/TODO_future.mdDOC-OPS-12 (ADR 路線から RQ 路線に格下げ要更新) - 主要 caller:
drp/src/nodes/{body_generation,scoring,consistency,parallel_review}.ts - 参照実装 (read≒0 の調査対象):
docs-search-worker/src/anthropic.ts:91-96 - 関連 ADR: ADR-0033 (multi-provider 抽象を LiteLLM Gateway に集約)、ADR-0076 (Cross-Validation Gate)、ADR-0077 (TelemetryRecord D1)
- Anthropic 公式ドキュメント: https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
- LiteLLM Anthropic caching: https://docs.litellm.ai/docs/providers/anthropic
5. P1-Q3 計測結果 (2026-05-29)
5.1 計測対象と方法
- 対象:
prompts/production/{body-generation,gate2-consistency,gate3-parallel-review,gate4-scoring}/prompt.md(ファイル実体) - 計測方法: Anthropic 公式
POST /v1/messages/count_tokensAPI、modelclaude-opus-4-5指定 (4.5 / 4.6 / 4.7 は同一 4,096 minimum) - 計測スクリプト:
for node in ...; do curl POST /v1/messages/count_tokens; done(本 RQ commit に詳細) - 注: KV (PROMPTS_KV) で上書きされている可能性は残るが、
prompt.meta.yamlの semver と運用慣行 (file 起点で KV push) からファイル実体と大きく乖離する可能性は低い
5.2 結果
| Node | model_pin (meta) | gateway.ts での実モデル | system tokens | vs 4,096 minimum | キャッシュ可否 |
|---|---|---|---|---|---|
| body-generation | claude-opus-4-7 | claude-opus | 3,599 | -497 (87.8%) | ❌ |
| gate2-consistency | claude-sonnet-4-6 (meta) ※ | claude-opus | 690 | -3,406 (16.8%) | ❌ |
| gate3-parallel-review (claude) | claude-sonnet-4-6 (meta) ※ | claude-opus | 323 | -3,773 (7.9%) | ❌ |
| gate4-scoring | claude-sonnet-4-6 (meta) ※ | claude-opus | 775 | -3,321 (18.9%) | ❌ |
※ prompt.meta.yaml の model_pin と drp/src/llm/gateway.ts:96-101 の MODELS が乖離している。本番呼び出しは gateway.ts の claude-opus (LiteLLM alias → claude-opus-4-7) が優先。meta 側の model_pin はテスト用または更新漏れと思われる (別途確認推奨)。
結論: 全 4 ノードが Opus 4.5+ の 4,096 token 最小キャッシュ可能要件を下回る。cache_control: { type: 'ephemeral' } を system に付けても キャッシュは発生せず、警告も返らない (Anthropic 仕様)。元 ADR 起案の前提が構造的に成立しないことが確定。
5.3 implications (帰結)
- 元 ADR 起案 (DOC-OPS-12) は技術的に無効 — LiteLLM passthrough の検証以前の問題として、Opus 4.7 の最小要件で弾かれる
- Pipeline Cross-Validation Gate の差戻しは更に強い根拠で正当化 — 「未検証」どころか「検証したら明確に NG」
- docs-search-worker の read ≒ 0 は同じ理由の可能性 — Sonnet 4.6 系も最小要件は 1,024 tokens (Opus 4.5+ より低い) だが、もし system が 1K 未満なら同じ罠
- コスト削減 ($30-70/月) のシナリオは完全に消滅 — caching write 課金もそもそも発生しないため、純粋にゼロ効果
5.4 次の検討方向 (caching に固執する場合)
| 案 | 内容 | 必要作業 | 評価 |
|---|---|---|---|
| α | systemPrompt を膨らませて 4K 超過 | 用語集 / ADR スタイルガイド / few-shot example を system に追加 (各 +1-3K tokens) | 質改善も期待できるが「caching のために膨らませる」は本末転倒 |
| β | message 構造を変えて adrBody を user 先頭 → cache_control 配置 | scoring/consistency/reviewClaude で共通の adrBody (2-5K tokens) を user の prefix に固定、cache_control をその後ろに | 1 Pipeline run 内の 3 ノードで cache hit 可能、ただし system は依然 cache 外 |
| γ | モデルダウングレード (Opus → Sonnet) | gateway.ts の MODELS で claude-sonnet に変更 | input $5→$3 で 40% 削減、品質劣化リスクあり (ADR-0033 で逆方向の決定済) |
| δ | caching 案を完全放棄、ノード別 prompt slim down | 現在の prompt 内の冗長部分を削減、トークン使用量を絶対量で削減 | output token も同時削減可能、最も保守的 |
仮の推奨: γ (モデルダウングレード) または δ (slim down) を P1-Q1/Q2 完了後に検討。α/β は変更コストが大きく ROI 低い。
5.5 P1-Q3 の波及効果
- P1-Q1 (docs-search-worker の read ≒ 0) は Sonnet 4.6 系で最小 1,024 tokens なので「systemPrompt が 1K 未満」が原因の可能性を加える
- P1-Q2 (Pipeline 呼び出し頻度) は本 caching 案では不要に。代わりに γ/δ 案のコスト試算根拠として活用
- 本 RQ の DoD §3 の P1-Q3 は ✅ 完了。P1-Q1/Q2 の続行可否は γ/δ 案に組み替えるべきか要判断
5.6 案 δ 詳細: body-generation prompt slim down 提案 (2026-05-29)
§5.4 で δ (prompt slim down) を採用方針として確定。優先対象は body-generation 一択 (system 3,599 tokens で他 3 ノード合計の約 3 倍、output も最大)。本節で具体的削減候補を特定。
5.6.1 削減対象ブロック (現状 3,599 tokens → 目標 ~2,500 tokens, -30%)
| # | 場所 (L 番号) | 現状 (推定) | 提案 (推定) | 節約 | 種別 |
|---|---|---|---|---|---|
| 1 | L1-2 役割定義 | ~50 | ~50 | 0 | 維持 |
| 2 | L4-13 テンプレート placeholder 列 | ~150 | ~100 | -50 | 軽量化 |
| 3 | L15-16 Implementation Status 説明 | ~300 | ~80 | -220 | 大幅圧縮 |
| 4 | L18-24 Kruchten Type 説明 | ~400 | ~150 | -250 | 大幅圧縮 |
| 5 | L26-48 コンテキスト §1.1-1.5 説明 | ~600 | ~430 | -170 | 軽量化 |
| 6 | L50-51 決定 | ~30 | ~30 | 0 | 維持 |
| 7 | L53-82 判断基準 (Q42/WSM/K.O.) | ~700 | ~530 | -170 | 軽量化 |
| 8 | L84-86 検討した代替案 | ~60 | ~60 | 0 | 維持 |
| 9 | L88-104 影響 (Good/Bad/Neutral) | ~300 | ~220 | -80 | 軽量化 |
| 10 | L106-112 撤退条件 / 参照 | ~80 | ~80 | 0 | 維持 |
| 11 | L114-139 生成ルール 1-10 | ~900 | ~700 | -200 | 圧縮 |
| 12 | L117-120 Mode 別構造 | ~150 | ~70 | -80 | 表化 |
| 合計 | ~3,720 ※実測 3,599 | ~2,500 | -1,220 (-32.7%) |
5.6.2 主要削減ブロックの置換案
ブロック #4 Kruchten Type 説明 (250 tokens 節約)
現状 (L18-24, 400 tokens):
[Kruchten Type 必須化 (ADR-0030 で採用)]
本フィールドには Existence / Property / Executive のうち該当する 1-3 個を `/` 区切りで列挙する (例: `Existence` または `Existence/Property` または `Existence/Property/Executive`)。判定基準:
- **Existence**: 新しい要素・コンポーネント・データ構造を導入する決定
- **Property**: システム全体に適用される横断ルール・規約・制約を定める決定
- **Executive**: 技術・プラットフォーム・プロセス・組織選定の決定
複数該当する場合は §5.1 (Existence 影響) / §5.2 (Property 影響) / §5.3 (Executive 影響) で分解記述する。判定の詳細は `docs/adr/README.md` の「Kruchten 3 分類ガイド」を参照。
裁定優先順位: ① 起案者明示があればそれを採用 → ② 過半数一致 (Parallel Review 2/3 以上) → ③ 過半数不一致時は起案者裁定 (Pipeline は INFO 通過、ラベル候補列挙)。
提案 (150 tokens):
[Kruchten Type] Existence / Property / Executive のうち該当を `/` 区切りで列挙 (例: `Existence/Property`)。判定基準と裁定ルールは docs/adr/README.md の「Kruchten 3 分類ガイド」を参照。複数該当時は §5.1/§5.2/§5.3 で分解。
根拠: Existence/Property/Executive の定義は docs/adr/README.md に正本がある。Opus 4.7 ならこのサイズの分類タスクで 1 文の指示で十分判定可能 (few-shot 不要)。裁定優先順位は Pipeline の Parallel Review 合議ロジックと被るため prompt 側で説明不要。
ブロック #3 Implementation Status 説明 (220 tokens 節約)
現状 (L15-16, 300 tokens) → 提案 (80 tokens):
[Implementation Status] 新規 ADR は `Not Started` 固定で起案する。値の定義は docs/adr/README.md の「Implementation Status ガイド」を参照。
根拠: 「マージ時に Done に手動更新」等は起案 prompt の責務外 (実装後の運用ルール)。生成時には不要。
ブロック #11 生成ルール 1-10 のうち Rule 4 Confirmation (170 tokens 節約)
現状 (L122-123, 300 tokens):
[Confirmation 必須化 (ADR-0036 で採用)]
§6.5 Confirmation (準拠確認 / Fitness Function) セクションには「決定が実装で守られているかを継続的に検証する fitness function」を 3 要素 (検証手段 / 実行頻度 / 違反時の対応) で記述する。既存検証メカニズムを最大活用 (scripts/adr-lint.mjs / scripts/4_review_specs_by_gemini.js / Pipeline Gate 2 / 手動 QA など)。自動検証不可なら手動レビューチェックリストを明示。Light Mode + 純ドキュメント ADR は N/A 許容 (理由必須)、Standard / Critical Mode は N/A 不可。
提案 (130 tokens):
4. **Confirmation セクション** (§6.5): fitness function を 3 要素 (検証手段 / 実行頻度 / 違反時対応) で記述。既存メカニズム (adr-lint / Gate 2 / 手動 QA) を活用。Light + ドキュメント ADR のみ N/A 許容、それ以外は N/A 不可。詳細: docs/adr/README.md。
ブロック #12 Mode 別構造 (80 tokens 節約)
現状 (L117-120, 150 tokens) の箇条書きを表化:
提案 (70 tokens):
3. **Mode 別構造**:
| Mode | §1.x サブ見出し | §3 判断基準 | §5.x サブ見出し |
|------|----------------|------------|----------------|
| Light | 単一節 | 省略可 (推奨記載) | 単一節 |
| Standard | 推奨 | 必須 | 推奨 |
| Critical | 必須 | 必須 | 必須 |
5.6.3 削減してはいけない部分
以下は 絶対に残す:
| 行 | 内容 | 残す理由 |
|---|---|---|
| L126-132 (Rule 7) | 「数値・日付・評価マトリクス等は原文転記」 | これがないと Pipeline 出力で起案者の数値が消失する (実害大) |
| L133-138 (Rule 8) | 「Markdown 表・コードブロック・引用はそのまま転記」 | 同上 |
| L139 (Rule 10) | 「セクション帰属が曖昧な箇所も削除しない」 | 起案テキストの欠落防止 |
| L88-104 §5 構造 | Light vs Standard/Critical の影響分割 | Fairy Tale アンチパターン回避 (Bad を必ず書く) |
5.6.4 期待効果
| 指標 | 現状 | slim 後 (見込) | 改善 |
|---|---|---|---|
| system tokens | 3,599 | ~2,500 | -1,099 (-30.6%) |
| input コスト/call | $0.018 | $0.0125 | -$0.0055 |
| output tokens (副次効果) | 5,000-8,000 | 4,500-7,200 (10% 減仮定) | -500-800 |
| output コスト/call (副次) | $0.125-0.20 | $0.113-0.18 | -$0.012-0.025 |
| 月次 (30 call) | $4.3-6.5 | $3.3-5.1 | -$1-1.5/月 |
| TTFT (体感) | baseline | ~30% 短縮 | 体感差あり |
| 品質 | baseline | promptfoo eval で検証 | 不変 ~ 改善 (冗長性除去で安定性向上) |
5.6.5 実装段取り (main ワークスペース)
- 本 RQ §5.6 を slim 化仕様として参照 → main で
prompts/production/body-generation/prompt.mdを編集 - semver bump (
prompt.meta.yaml1.1.0 → 1.2.0) - promptfoo eval 実行 (
prompts/production/body-generation/promptfoo.yaml) で品質回帰チェック - eval 通過 →
scripts/prompt-kv-push.mjsで KV 投入 + active pointer 切替 - 1-2 週間運用観測:
- Telemetry (ADR-0077, per-node token/cost) で input/output 削減実績確認
- Cross-Validation Gate / Gate 4 採点で品質劣化兆候の有無確認
- 効果確認後、他 3 ノード (gate2-consistency / gate3-parallel-review / gate4-scoring) の slim 化要否を判断
- gate4-scoring は Self-Consistency
samplingN > 1設定なら優先度上昇 - 他 2 ノードは system が元から小さく ROI 低い可能性大
- gate4-scoring は Self-Consistency
5.6.6 リスク
- 過剰削減で品質劣化: Rule 4 (Confirmation) 圧縮で N/A 判定が雑になる可能性 → promptfoo eval で検出
- docs/adr/README.md 参照に依存: 「詳細は README 参照」と書くが Opus は README を読めない。判定に必要な最小情報は prompt 内に残す (本提案は最小情報を保持)
- template placeholder 維持: L4-13 の
{{タイトル}}等は body_generation.ts で substitution されないが、テンプレ構造の「形」を示すために残す。削除すると model が出力構造を想像で書く
5.6.7 完了条件
- main 側で
prompts/production/body-generation/prompt.mdslim 化 PR 提出 - promptfoo eval 全件 pass
- semver bump + KV active 切替
- 1-2 週間 Telemetry 観測、input 削減 25%+ / output 削減 5%+ 確認
- 確認結果を本 RQ に追記、他 3 ノード slim 化の要否を判断
6. Pipeline 検出の 8 盲点 (転記、調査時の参考)
| # | 重要度 | 内容 | 対応 RQ 設問 |
|---|---|---|---|
| 1 | critical | 5m TTL 下で cache hit 率実質ゼロ + docs-search-worker の read≒0 | Q1, Q2, Q5 |
| 2 | high | LiteLLM passthrough 未検証 + ADR-0033 との暗黙的矛盾 | Q4 |
| 3 | high | 4K トークン最小要件未計測 → 対象ノード数の不確実性 | Q3 |
| 4 | high | 撤退条件 (1 週間観測) の誤判定リスク | (RQ 完了後の ADR で再設計) |
| 5 | medium | コスト削減試算の根拠不透明 | Q1-Q3 (実測ベース) |
| 6 | medium | KV systemPrompt 更新時の invalidation 不可視 | (運用設計時) |
| 7 | medium | Cloudflare Workers での LiteLLM デプロイ形態の cache_control 消失リスク | Q4 |
| 8 | low | モデル選択最適化など根本対策の検討阻害 | Q6 |