キャッシュ診断
連続するリクエストを比較し、プロンプトのプレフィックスがどこで分岐したかを正確に特定することで、予期しないプロンプトキャッシュミスを診断します。
プロンプトキャッシング(prompt caching)はレイテンシとコストを大幅に削減しますが、それはプロンプトの先頭部分が最近のリクエストとバイト単位で完全に一致している場合に限られます。ツールの順序変更、システムプロンプトに埋め込まれたタイムスタンプ、あるいは以前のメッセージへの編集によって、キャッシュが気付かないうちに無効化されることがあります。キャッシュ診断がなければ、唯一のシグナルは usage.cache_read_input_tokens がゼロに落ちることだけで、何が変わったのかを示す手がかりはありません。
キャッシュ診断(cache diagnostics)はそのギャップを埋めます。前回のレスポンスの id を渡すと、APIが2つのリクエストを比較し、どこで分岐したか(モデル、システムプロンプト、ツール、またはメッセージ履歴)を教えてくれるため、推測ではなく根本原因を修正できます。
キャッシュ診断の仕組み
ベータヘッダーが存在する場合、APIは各リクエストの軽量なフィンガープリントを、レスポンスの id をキーとして保存します。次のリクエストで、その id を diagnostics.previous_message_id として含めてください。APIは新しいリクエストのフィンガープリントを再構築し、保存されているものと比較して、最初の分岐点を記述する diagnostics オブジェクトをレスポンスに付加します。
この比較はリクエストの構造に関するものであり、キャッシュが実際にヒットしたかどうかとは独立しています。diagnostics の結果と usage.cache_read_input_tokens を組み合わせる方法については、usageと併せて診断結果を読むを参照してください。
フィンガープリントにはハッシュとトークン数の推定値のみが含まれ(生のプロンプト内容は一切含まれません)、限られた期間のみ保持され、組織とワークスペースにスコープされ、他の目的には使用されません。
基本的な使い方
毎ターン、ベータヘッダーを送信してください。最初のターンでは、比較対象となる前のメッセージなしでオプトインするために "previous_message_id": null を渡します。以降のターンでは、前回のレスポンスの id を渡します。
client = anthropic.Anthropic()
SYSTEM = "You are an AI assistant analyzing a large document. <document>...</document>"
# ターン1: previous_message_id=None でオプトイン
r1 = client.beta.messages.create(
model="claude-opus-5",
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-5",
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"],
)
diagnostics = r2.diagnostics
if diagnostics is None:
print("No divergence detected.")
elif diagnostics.cache_miss_reason is None:
print("Comparison still pending.")
else:
print(f"cache_miss_reason: {diagnostics.cache_miss_reason.type}")ストリーミング
ストリーミング(streaming)レスポンスでは、diagnostics は message_start イベントに現れます。
# ターン2: 前のレスポンスIDを参照してストリーミング
with client.beta.messages.stream(
model="claude-opus-5",
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"],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
print()
r2 = stream.get_final_message()
diagnostics = r2.diagnostics
if diagnostics is None:
print("No divergence detected.")
elif diagnostics.cache_miss_reason is None:
print("Comparison still pending.")
else:
print(f"cache_miss_reason: {diagnostics.cache_miss_reason.type}")message_start イベントは完全な diagnostics フィールドを含みます。取り得る値についてはレスポンス形式を参照してください。
会話ループを通じて診断を引き継ぐ
マルチターンの会話では、毎ターン最新のレスポンスの id を previous_message_id として引き継ぎます。最初のイテレーションではオプトインのために null を渡し、以降の各イテレーションでは前回のレスポンスの id を渡します。
client = anthropic.Anthropic()
SYSTEM = "You are an AI assistant analyzing a large document. <document>...</document>"
messages = []
prev_id = None
for i, user_message in enumerate(
["Summarize section 1.", "Now section 2.", "Now section 3."]
):
messages.append({"role": "user", "content": user_message})
r = client.beta.messages.create(
model="claude-opus-5",
max_tokens=1024,
cache_control={"type": "ephemeral"},
system=SYSTEM,
messages=messages,
diagnostics={"previous_message_id": prev_id},
betas=["cache-diagnosis-2026-04-07"],
)
if r.diagnostics is not None and r.diagnostics.cache_miss_reason is not None:
print(f"Turn {i + 1} cache_miss_reason: {r.diagnostics.cache_miss_reason.type}")
messages.append({"role": "assistant", "content": r.content})
prev_id = r.idレスポンス形式
レスポンスの Message にある diagnostics フィールドには、4つの取り得る状態があります。
| 値 | 意味 |
|---|---|
| フィールドなし | リクエストに diagnostics が含まれていなかったか、ベータヘッダーが欠けていました。 |
null | previous_message_id が null だった(最初のターンで比較対象がない)か、比較が実行されて分岐が見つからなかったかのいずれかです。 |
{"cache_miss_reason": null} | レスポンスがシリアライズされた時点で比較がまだ実行中でした。これはレスポンスが非常に速く開始された場合に起こり得ます。結論が出ていないものとして扱い、次のターンを確認してください。 |
{"cache_miss_reason": {...}} | cache_miss_reason が付加されています。*_changed タイプの場合、これは最初の分岐点を特定します。previous_message_not_found と unavailable は比較が生成されなかったケースです。 |
cache_miss_reason がnullでない場合、次のようになります。
{
"id": "msg_01Xyz...",
"type": "message",
"role": "assistant",
"content": [{ "type": "text", "text": "..." }],
"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 は type による判別共用体(discriminated union)です。レスポンスは最も早い分岐のみを報告するため、まずそれを修正してください。後続の分岐はその背後に隠れている可能性があります。
| タイプ | 意味 | 変更すべき点 |
|---|---|---|
model_changed | model が前回のリクエストと異なります(例えば、ルーター、A/Bテスト、またはフォールバックが別のモデルを選択した場合)。キャッシュはモデルごとです。 | キャッシュされた会話内ではモデルを一定に保ってください。 |
system_changed | system パラメータが異なります。通常、タイムスタンプ、リクエストID、またはその他のリクエストごとの値がシステムプロンプトに埋め込まれています。 | システムプロンプトをバイト単位で安定した定数にし、動的なデータはキャッシュブレークポイントの後の最初の user メッセージに移動してください。 |
tools_changed | tools 配列が異なります。ターン間でツールが追加、削除、または順序変更されたか、ツールの input_schema JSONが非決定的にシリアライズされました。 | 毎ターン同じツールリストを固定の順序で、決定的にシリアライズされたスキーマ(例えばキーをソート)とともに送信してください。 |
messages_changed | モデル、システム、ツールはすべて一致していますが、messages 内の以前のエントリが追記ではなく変更、順序変更、または削除されました。通常、会話履歴が切り詰められたか編集されたか、あるいはアシスタントのターンや tool_result ブロックが再送時に異なる形でシリアライズされています。 | 履歴を追記専用として扱い、アシスタントの content とツール結果をそのままエコーバックしてください。 |
previous_message_not_found | 指定された previous_message_id に対する保存済みフィンガープリントが存在しません。これはリクエストが変更されたことの証拠ではありません。通常、前回のリクエストにベータヘッダーが付いていなかったか、別のワークスペースから送信されたか、送信から時間が経ちすぎています。 | 毎ターンベータヘッダーを送信し、連続するターンの時間間隔を短く保ってください。 |
unavailable | このリクエストでは診断情報が利用できませんでした。これには、model、system、tools は一致しているが、プロンプトに影響する他のリクエストパラメータ(tool_choice、thinking、context_management、output_config、output_format、または有効な anthropic-beta ヘッダーのセット)が異なるケースや、分岐が比較範囲を超えている非常に長い会話が含まれます。リクエストは通常どおり処理されました。 | キャッシュされた会話の存続期間中、プロンプトに影響するリクエストパラメータを一定に保ってください。継続する場合は、プロンプトキャッシングのページの一般的な問題のトラブルシューティングにある手動チェックを適用してください。 |
usageと併せて診断結果を読む
diagnostics は「リクエストは変わったか?」に答え、usage.cache_read_input_tokens は「キャッシュはヒットしたか?」に答えます。両者を組み合わせることで、どこを調べるべきかがわかります。
このマトリクスは、実際の previous_message_id を渡したターンに適用されます。最初のターン(previous_message_id: null)では、diagnostics は常に null であり、キャッシュは読み取りではなく書き込みが行われているため cache_read_input_tokens は通常ゼロです。トラブルシューティングは不要です。また、cache_miss_reason が null の場合(比較がまだ保留中。次のターンを確認してください)や、その type が previous_message_not_found または unavailable の場合(比較が生成されなかった)にも、このマトリクスは適用されません。
| 診断結果 | キャッシュ読み取りトークン | 解釈 |
|---|---|---|
null | 多い | 期待どおりに動作しています。プレフィックスは安定しており、キャッシュがヒットしました。 |
null | 少ないまたはゼロ | リクエストは一致していますが、キャッシュエントリがもう利用できませんでした。ターン間の間隔を短くするか、1時間のキャッシュTTLの使用を検討してください。 |
cache_miss_reason が *_changed タイプ | 少ないまたはゼロ | あなた側のバグです。リクエストが変更されました。type が示す原因を修正してください。 |
cache_miss_reason が *_changed タイプ | 多い | まれです。プロンプトの後方で変更が発生しましたが、それより前の cache_control ブレークポイントは依然としてヒットしました。修正する価値はありますが、影響は小さいです。 |
制限事項
- ベータ: この機能がベータ版である間、フィールド名とセマンティクスは変更される可能性があります。
- Claude APIのみ: Amazon Bedrock または Google Cloud では利用できません。
- 限定的な保持期間:
previous_message_idの検索用フィンガープリントは短期間で期限切れになります。診断の比較は時間的に近接したリクエスト間で実行してください。 - 同一ワークスペース: 前回のリクエストは同じ組織とワークスペースで実行されている必要があります。確認するには、2つのレスポンスの
anthropic-workspace-idレスポンスヘッダーを比較してください。 - 比較範囲: 唯一の変更がメッセージリストの深い位置にある非常に長い会話では、レスポンスは正確な位置ではなく
unavailableになる場合があります。 - ベストエフォート: 診断がリクエストをブロックしたり失敗させたりすることは決してありません。診断情報が利用できない場合、レスポンスは
unavailableを返し、比較がまだ実行中だった場合はcache_miss_reason: nullを返します。
データ保持
キャッシュ診断はZDR対象(条件付き)です。Anthropic はこの機能のためにプロンプトの生テキストや Claude の出力を保存しません。
各リクエストに対して保存されるフィンガープリントは、暗号学的ハッシュとトークン数の推定値のみで構成され、レスポンスの id をキーとし、組織とワークスペースにスコープされます。フィンガープリントは短期間で期限切れになり、他の目的には使用されません。
すべての機能にわたるZDR対象可否については、APIとデータ保持を参照してください。
関連項目
Compatibility
| Supported platforms |
|
|---|
Was this page helpful?