バッチ処理でプロンプトキャッシュのヒット率を上げる方法
Message Batches APIとプロンプトキャッシュを併用する際のcache_control設計と、ヒット率30〜98%の幅が生まれる理由を実装例つきで解説します。
このTipsでできること
Message Batches APIはリクエストを非同期・並列に処理するため、プロンプトキャッシュのヒットは保証されず「ベストエフォート」扱いになります。公式ガイドは、トラフィックのパターンによってヒット率が30%〜98%まで幅を持つと明記しています。この記事では、ヒット率を高く保つためのcache_controlの書き方と、両機能を併用する理由を実装コードで示します。
やり方
Batch APIは、Messagesリクエストを最大10万件または256MBまで(どちらか先に達したほう)まとめて送信し、非同期で処理する仕組みです。通常のAPI料金の50%で処理でき、多くのバッチは1時間以内に完了します。バッチは24時間以内に処理が終わらないと期限切れになり、結果は作成から29日間ダウンロード可能です。プロンプトキャッシュとの併用は、この料金割引に加えてさらにコストと処理時間を圧縮できる組み合わせです。
ヒット率が保証されない理由は、バッチのリクエストが並列かつ非同期に処理される点にあります。通常の同期APIならリクエストを順番に送るのでキャッシュへの書き込みと読み込みの順序を制御しやすい一方、バッチは複数のリクエストが同時に走るため、あるリクエストがキャッシュへの書き込みを終える前に別のリクエストが同じ内容を処理してしまうことがあります。
公式ガイドが挙げるヒット率を高めるための3点は次のとおりです。
- バッチ内のすべてのMessageリクエストに、同一の
cache_controlブロックを含める - リクエストを途切れさせず一定のペースで送り続ける(キャッシュエントリは5分の有効期限で失効するため)
- リクエスト間でできるだけ多くのキャッシュ対象コンテンツを共有する構成にする
以下は、同じシステムプロンプトと長文コンテキスト(『高慢と偏見』全文)をcache_control付きで2つのリクエストに含め、バッチとして送信する例です。
curl https://api.anthropic.com/v1/messages/batches \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"requests": [
{
"custom_id": "my-first-request",
"params": {
"model": "claude-opus-5",
"max_tokens": 1024,
"system": [
{
"type": "text",
"text": "You are an AI assistant tasked with analyzing literary works.\n"
},
{
"type": "text",
"text": "<the entire contents of Pride and Prejudice>",
"cache_control": {"type": "ephemeral"}
}
],
"messages": [
{"role": "user", "content": "Analyze the major themes in Pride and Prejudice."}
]
}
},
{
"custom_id": "my-second-request",
"params": {
"model": "claude-opus-5",
"max_tokens": 1024,
"system": [
{
"type": "text",
"text": "You are an AI assistant tasked with analyzing literary works.\n"
},
{
"type": "text",
"text": "<the entire contents of Pride and Prejudice>",
"cache_control": {"type": "ephemeral"}
}
],
"messages": [
{"role": "user", "content": "Write a summary of Pride and Prejudice."}
]
}
}
]
}'両方のリクエストでsystem配列の内容とcache_controlブロックが完全に一致している点がポイントです。2番目のリクエストが処理されるタイミングで1番目のキャッシュ書き込みが完了していれば、長文コンテキスト分のトークンはキャッシュから読み込まれ、課金と処理時間の両方が圧縮されます。逆にmessages配列の質問文はリクエストごとに変わってよく、cache_controlの対象をsystem側に限定していれば、質問文の違いはキャッシュのヒット判定に影響しません。
補足
バッチを2段階に分けるとヒット率を安定させやすくなります。公式ガイドが最もコスト効率が良いと案内する方法は次の手順です。
- 共通のプレフィックス(システムプロンプトや長文コンテキストなど)を持つリクエスト群を洗い出す
- その共通プレフィックスと1時間キャッシュのブロックだけを持つリクエスト1件だけを先にバッチとして送信し、プレフィックスを1時間キャッシュへ書き込む
- この単独リクエストの完了をジョブの状態確認で待ってから、残りのリクエストをまとめて送信する
バッチは5分から1時間程度かかることが多いため、5分キャッシュより1時間キャッシュのほうが適している場面が一般的です。この2段階方式なら、通常のバッチ送信のように「並列処理のどこかでキャッシュが書き込まれるのを待つ」のではなく、キャッシュの書き込みを確実に完了させてから本体のリクエストを送れます。
プレフィックス書き込み用リクエストの完了は、バッチのprocessing_statusをポーリングして確認します。
import time
client = anthropic.Anthropic()
message_batch = None
while True:
message_batch = client.messages.batches.retrieve(MESSAGE_BATCH_ID)
if message_batch.processing_status == "ended":
break
print(f"Batch {MESSAGE_BATCH_ID} is still processing...")
time.sleep(60)processing_statusは作成直後はin_progressで、全リクエストの処理が終わるとendedに変わります。プレフィックス書き込み用の1件だけのバッチがendedになったのを確認してから、本体のリクエスト群を送信すれば、1時間キャッシュへの書き込みが完了した状態で本体を処理できます。ステータス確認はコンソール画面からでも可能ですが、自動化するパイプラインではAPI経由のポーリングが基本です。
キャッシュ対象にできないパラメーターに注意が必要です。バッチリクエストではmax_tokens: 0によるキャッシュの事前ウォームアップが使えません。事前ウォームアップは、バッチ処理中に書き込んだ一時キャッシュエントリが後続のリクエストより先に失効してしまう可能性が高いための制限です。同様にstream: trueやspeed(Fast mode)パラメーターもバッチでは使えず、含めるとバリデーションエラーになります。Fast modeは同期処理の応答速度を調整する機能で、非同期処理であるバッチには意味を持たないため、この制限自体は自然な設計です。
プロンプトキャッシュとバッチ処理の料金割引は重ね掛けできます。公式ガイドは、プロンプトキャッシュの各種倍率(5分キャッシュ書き込みは通常入力の1.25倍、1時間キャッシュ書き込みは2倍、キャッシュ読み込みは0.1倍、Claude Fable 5.1とClaude Mythos 5.1では0.025倍)が、バッチAPIの割引やデータレジデンシーといった他の料金修飾子と重ね掛けされると明記しています。バッチの50%引きはベースの入力・出力単価に適用され、その上でキャッシュ読み込み分はさらに0.1倍(または0.025倍)まで下がる計算です。バッチ内で共通のシステムプロンプトや長文リファレンス資料を扱うワークロード(大量ドキュメントの一括分析、大規模評価データセットの処理など)ほど、この組み合わせの効果が大きくなります。
バッチリクエストを共有プレフィックスでグルーピングする
ヒット率を実務で上げる鍵は、1つのバッチに詰め込むリクエストをどうグルーピングするかです。100,000件のリクエストを1つの巨大なバッチに雑多に詰め込むより、同じシステムプロンプト・同じ長文コンテキストを使うリクエストをまとめて別々のバッチに分割するほうが、共有プレフィックスの割合が上がりヒット率も安定します。
バッチにまとめられるリクエストの種類は幅広く、Vision(画像入力)、ツール使用(web検索・web取得・コード実行・MCPコネクタ・advisor・tool searchを含むサーバーツール)、システムメッセージ、マルチターンの会話、拡張思考(extended thinking)、ほとんどのベータ機能が対象です。異なる種類のリクエストを同じバッチ内に混在させることもできますが、cache_controlのヒット率という観点では、リクエストの種類がバラバラでも共有プレフィックスの内容が同一であることが条件になる点は変わりません。
バッチ全体でcache_controlブロックの値だけを揃えても不十分な場合があります。キャッシュはプロンプトの階層(tools → system → messages)を追う仕組みで、ある階層の内容が変わると、その階層以降のキャッシュがまとめて無効になります。バッチ内の各リクエストでツール定義やシステムプロンプトの文言が1文字でも異なると、cache_controlブロック自体が同じでもキャッシュは別エントリとして扱われます。バッチ用にリクエストを組み立てる際は、共有したいsystem配列やツール定義をテンプレート化し、変数展開ではなく完全に同じ文字列を全リクエストへコピーする実装にしておくと事故が減ります。
ヒット率を左右する要因の早見表
| 要因 | ヒット率への影響 | 対策 |
|---|---|---|
cache_controlブロックの内容不一致 | ヒット率への影響キャッシュミスに直結 | 対策全リクエストで完全一致させる |
| リクエストの送信間隔が空く | ヒット率への影響5分キャッシュが失効しやすい | 対策途切れず送り続ける、または1時間キャッシュに切り替える |
| バッチの同時実行数が多い | ヒット率への影響並列処理でキャッシュ書き込み前に他リクエストが到達しやすい | 対策共有コンテンツを増やし、書き込み待ちの影響を相対的に下げる |
| 共有コンテンツの割合が低い | ヒット率への影響キャッシュ対象自体が小さくコスト削減効果も薄い | 対策システムプロンプト・長文コンテキストなど共通部分をcache_controlでくくる |
よくある質問
バッチのキャッシュヒット率が低い場合、リトライすれば改善するか
公式ガイドはリトライによる改善を保証していません。ヒット率は同時実行される他のリクエストのタイミングに左右されるベストエフォートの挙動であるため、同じバッチを送り直しても必ずしも改善するとは限りません。ヒット率を安定させたい場合は、前述の3点(同一cache_control・途切れないリクエスト・共有コンテンツの最大化)を見直すほうが確実です。
バッチ処理ではキャッシュの事前ウォームアップは使えないか
max_tokens: 0によるキャッシュの事前ウォームアップは、バッチリクエストでは対応していません。バッチ処理中に書き込まれた一時キャッシュエントリが、後続の実リクエストより先に失効する可能性が高いためです。バッチ処理でキャッシュを効かせたい場合は、事前ウォームアップに頼らず、リクエスト自体を途切れさせずに送り続ける運用でカバーします。
すべてのモデルでバッチとキャッシュの併用ができるか
Batch APIはすべての稼働中モデルに対応しています。プロンプトキャッシュもMessages APIの標準機能なので、稼働中モデルであれば基本的にバッチとキャッシュの併用が可能です。プロンプトキャッシングの詳しい仕組みと課金の考え方はAnthropic APIのPrompt Cachingを理解するにまとめています。バッチAPI自体の基本的な使い方はClaude Batch APIの使い方を参照してください。
バッチのキャッシュはワークスペースをまたいで共有されるか
プロンプトキャッシュのキャッシュキーは、キャッシュブレークポイントまでのプロンプト内容から生成される暗号学的ハッシュです。同一プロンプトのリクエストだけが特定のキャッシュへアクセスできる仕組みで、キャッシュは組織内であってもワークスペースごとに分離されます。バッチもワークスペース単位でスコープされるため、この分離の粒度は一致しており、別ワークスペースのバッチが同じキャッシュを読みにいくことはありません。