プロンプトキャッシュはClaude APIのレイテンシとコストを大きく下げます。ただし効くのは、プロンプトの先頭(プレフィックス)が前回とバイト単位で一致しているときだけ。1か所変わると、それ以降のキャッシュがまとめて無効になります。
問題は、「効いていない」ことは分かっても「なぜ」が分からないこと。唯一の手がかりは usage.cache_read_input_tokens が0に落ちること——どこが原因かは教えてくれません。
この記事では、その原因を一点で特定できるbeta機能 cache diagnostics(cache_miss_reason)を、実例と6種類の原因タイプ、そして usage との読み合わせまで解説します。
キャッシュは「プレフィックス一致」でしか効かない
キャッシュキーは、各 cache_control 区切りまでのレンダリング済みプロンプトのバイト列から作られます。レンダリング順は tools → system → messages。
- ツールの並びが変わった
- systemプロンプトにタイムスタンプを埋め込んだ
- 過去メッセージを編集した
——こうした変化が起きると、その地点以降のキャッシュが静かに無効化されます。エラーは出ません。
cache diagnostics の仕組み
betaヘッダ cache-diagnosis-2026-04-07 を付けると、APIはリクエストごとに軽量なフィンガープリント(生のプロンプト本文ではなく、ハッシュとトークン数推定値のみ)を、レスポンスの id をキーに保存します。
次のリクエストでその id を diagnostics.previous_message_id として渡すと、APIが新旧を比較し、最初に分岐した地点を diagnostics として返します。
比較対象は「リクエストの構造」であって、キャッシュが実際にヒットしたかとは独立です。だから後述のとおり
usage.cache_read_input_tokensと組み合わせて読みます。
使い方(Python)
毎ターンbetaヘッダを付けます。初回は previous_message_id に None を渡してopt-in、次ターン以降は前レスポンスの id を渡します。
import anthropic
client = anthropic.Anthropic()
SYSTEM = "You are an AI assistant analyzing a large document. <document>...</document>"
# ターン1: キャッシュを作りつつ診断を有効化
r1 = client.beta.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
cache_control={"type": "ephemeral"},
system=SYSTEM,
messages=[{"role": "user", "content": "Summarize section 1."}],
diagnostics={"previous_message_id": None},
betas=["cache-diagnosis-2026-04-07"],
)
# ターン2: 前ターンのidを渡して比較させる
r2 = client.beta.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
cache_control={"type": "ephemeral"},
system=SYSTEM,
messages=[
{"role": "user", "content": "Summarize section 1."},
{"role": "assistant", "content": r1.content},
{"role": "user", "content": "Now summarize section 2."},
],
diagnostics={"previous_message_id": r1.id},
betas=["cache-diagnosis-2026-04-07"],
)
d = r2.diagnostics
if d is None:
print("分岐なし(キャッシュ的にはOK)")
elif d.cache_miss_reason is None:
print("比較がまだ走り切っていない(次ターンで確認)")
else:
print(f"cache_miss_reason: {d.cache_miss_reason.type}")
client.beta.messages.create(...)+betas=["cache-diagnosis-2026-04-07"]- リクエスト側は
diagnostics={"previous_message_id": ...}、結果はresponse.diagnostics - ストリーミング時は
message_startイベントにdiagnosticsが乗ります(.get_final_message()でも取得可)
diagnostics の4状態
| 値 | 意味 |
|---|---|
| フィールド自体が無い | diagnostics を渡していない or betaヘッダ無し |
null |
previous_message_id が null(初回)だった、または比較した結果分岐なし |
{"cache_miss_reason": null} |
比較がまだ走り切る前にレスポンスが返った。判定不能扱いで次ターンを見る |
{"cache_miss_reason": {...}} |
原因が付いた(*_changed は最初の分岐点) |
非nullのときの実例:
{
"usage": {
"input_tokens": 42,
"cache_read_input_tokens": 0,
"cache_creation_input_tokens": 41850,
"output_tokens": 210
},
"diagnostics": {
"cache_miss_reason": {
"type": "system_changed",
"cache_missed_input_tokens": 41850
}
}
}
cache_miss_reason の6タイプと対処
cache_miss_reason は type による判別共用体です。報告されるのは“最初の”分岐点だけなので、まずそこを直します(後ろの分岐はそれに隠れている可能性があります)。
| type | 何が起きたか | 直し方 |
|---|---|---|
model_changed |
model が前回と違う(ルーター/ABテスト/フォールバック)。キャッシュはモデル単位 |
1つのキャッシュ会話内でモデルを固定 |
system_changed |
system が違う。典型はタイムスタンプ・リクエストIDの埋め込み |
systemはバイト不変の定数に。動的値はキャッシュ境界より後の最初のuserへ |
tools_changed |
tools が違う(追加・削除・並べ替え、input_schema の非決定的シリアライズ) |
毎ターン同じツールを同じ順で。スキーマはキーをsort等で決定的に |
messages_changed |
model/system/toolsは一致するのに、過去のmessagesが追記でなく改変・並べ替え・削除された |
履歴はappend-onlyに。assistantのcontentとtool結果はそのままecho |
previous_message_not_found |
渡したprevious_message_idのフィンガープリントが無い。自分が変えた証拠ではない(前回betaヘッダ無し/別ワークスペース/時間経過) |
毎ターンbetaヘッダを付け、連続ターンを時間的に近づける |
unavailable |
診断不可。model/system/toolsは一致だが他のプロンプト影響パラメータ(tool_choice/thinking/context_management/output_config/beta集合)が違う等。リクエストは正常処理 |
キャッシュ会話の間はこれらも固定 |
*_changed系はcache_missed_input_tokensも持ちます。分岐点より後ろに何トークン分のキャッシュ可能プレフィックスが失われたかの概算(バイト長由来の規模感の指標で、課金値ではありません)。
diagnostics × usage の読み合わせ
diagnostics は「リクエストが変わったか」、usage.cache_read_input_tokens は「キャッシュがヒットしたか」を答えます。組み合わせると原因の在り処が分かります。
| diagnostics | cache_read | 解釈 |
|---|---|---|
null |
高い | 期待どおり。プレフィックス安定+ヒット |
null |
低い/ゼロ | リクエストは一致だがキャッシュが消えていた。ターン間隔を詰める or 1時間TTLを検討 |
*_changed |
低い/ゼロ | 典型的な自分のバグ。type が示す原因を直す |
*_changed |
高い | 稀。後方で変化したが前のcache_control境界はヒット。影響小 |
データ保持と制限
- ZDR適格。保存されるのは暗号学的ハッシュとトークン数推定のみで、プロンプト本文・出力は保存されません。組織/ワークスペース単位、短期で失効。
- beta: フィールド名・意味はGAまでに変わり得ます。
- Claude API専用: Amazon Bedrock / Vertex AI では使えません。
- 保持は短期:
previous_message_id引きは短時間で失効。連続リクエストは時間的に近づけて比較を。 - 同一ワークスペース: 前リクエストは同じ組織・ワークスペースのキーで。
まとめ
- 「
cache_read_input_tokensが0」で止まっていた調査を、cache_miss_reason.typeで原因の一点に特定できます。 - 付けるのはbetaヘッダ
cache-diagnosis-2026-04-07とdiagnostics.previous_message_idだけ。 - 実務で一番効くのは
system_changed(タイムスタンプ埋め込み)とmessages_changed(履歴の非append改変)。まずここを疑う。 diagnostics(変わったか)×usage(ヒットしたか)で、「自分のバグ」か「キャッシュ失効」かを切り分けます。
関連記事
– 会話途中の指示変更でキャッシュを壊さない(Claude Opus 4.8)
– compaction / context editing で長時間エージェントを回す
– advisor toolで安いモデル中心にコストを抑える
本記事はClaude API公式ドキュメント「Cache diagnostics」に基づきます。betaのため仕様は変わり得ます。利用前に最新の公式ドキュメントをご確認ください。本記事はAPI仕様の解説・検証を目的とした技術記事です。


コメント