プロンプトキャッシュを解説:コストを削減できる場合とできない場合
ある月曜日の午前9:12時、私たちのサポートエージェントのひとつで、コストが想定より4x高くなり始めました。
モデルがハルシネーションを起こしていたわけでも、トラフィックのグラフが間違っていたわけでも、誰かが誤ってHaikuからOpusに切り替えたわけでもありません。原因はもっと単純でした。すべてのリクエストに同じ42,000トークンのポリシーマニュアルが含まれていたのに、その前に毎回新しいタイムスタンプとユーザー固有のメタデータを付けていたため、プロンプトキャッシュのヒット率が事実上ゼロになっていたのです。
同じ内容。同じモデル。同じアプリケーション。それでもキャッシュによる節約はゼロ。
プロンプトキャッシュは、実際に請求額を下げようとするまでは、自動で機能するように思える機能のひとつです。実際には、多くのリクエストで再利用される、大きくて変化しないプレフィックスという、非常に特定のプロンプト構造が効果を発揮します。この一文を理解すれば、キャッシュがコストを削減するのか、それとも何の効果もないままなのかを、たいてい予測できます。
プロンプトキャッシュが実際にキャッシュするもの
プロンプトキャッシュは意味を記憶する仕組みではありません。プロバイダーが「これは以前送られたものに似ていますね」と判断しているわけではありません。実際の仕組みは、次のようなものに近いです。
「このリクエストの先頭部分は、このモデルとキャッシュポリシーに照らして、以前のトークン列と十分に一致しているため、そのプレフィックスの内部計算を再利用できます。」
この違いは重要です。
プロンプトキャッシュは、一般に、次のような同じ長いプレフィックスを繰り返し送信する場合に役立ちます。
- システム指示
- ツールのスキーマ
- 大量のドキュメントをまとめたもの
- 製品カタログ
- コードベースのコンテキスト
- 法務・ポリシーマニュアル
- Few-shotの例
- 多くのリクエストで再利用される静的な検索結果
リクエストの先頭が毎回異なる場合、通常はあまり効果がありません。
- 先頭にある動的なタイムスタンプ
- 共通コンテンツの前にあるユーザー固有のデータ
- ランダム化された例の順序
- キャッシュ可能なセクション内にあるリクエストごとのUUID
- 順序や書式が変わる検索チャンク
- 固定の指示の前に配置されたチャット履歴
実装上の要点は、変化せず処理コストの高いコンテキストを先頭に置き、変化しやすいコンテキストを後ろに置くことです。
AnthropicとOpenAI:目標は似ていても、制御方法は異なる
AnthropicとOpenAIはどちらもプロンプトキャッシュをサポートしていますが、開発者にとっての使い勝手は異なります。
Anthropicは、プロンプトキャッシュをより明示的に扱えるようにしています。コンテンツブロックにキャッシュ制御を設定し、その後、次のような使用量フィールドを確認できます。 cache_creation_input_tokens と cache_read_input_tokens。これにより、キャッシュエントリを作成しているのか、それとも読み込んでいるのかを把握しやすくなります。
OpenAIのプロンプトキャッシュは、対応モデルではより自動的に動作します。プロンプトのプレフィックスが再利用されると、キャッシュ済みの入力トークンには異なる料金が適用され、使用量メタデータに表示される場合があります。通常、同じように明示的なキャッシュブレークポイントを設定することはありません。代わりに、再利用するプレフィックスがバイト列・トークン列として変わらないようにリクエストを構成します。
実用上の違いは、制御のしやすさと手軽さです。
| 比較項目 | Anthropic方式の明示的なキャッシュ | OpenAI方式の自動キャッシュ |
|---|---|---|
| 開発者による制御 | 高い。キャッシュ可能なブロックを指定できる | 低い。プロバイダーが再利用可能なプレフィックスを検出する |
| デバッグのしやすさ | キャッシュの作成・読み取りカウンターにより、通常は把握しやすい | モデル/APIが公開する使用量メタデータに依存する |
| プロンプト構造の重要性 | 非常に高い | 非常に高い |
| 適した用途 | 大きな静的ブロック、ツール、ドキュメント、例 | 繰り返しの呼び出しで再利用される長いプレフィックス |
| よくある失敗パターン | 動的コンテンツの後にキャッシュブレークポイントを配置する | キャッシュが一致する前にプレフィックスが変わる |
| 考え方のモデル | 「名前付きに近いプレフィックスを書き込み、読み取っている」 | 「プロバイダーが、この繰り返し使われるプレフィックスを認識している」 |
実装の詳細はモデルやプロバイダーによって異なり、今も進化し続けています。ただし、アーキテクチャ上の原則は変わりません。キャッシュが対象とするのは、似たプロンプトではなく、繰り返されるプレフィックスです。
料金モデル:作成と読み取り
プロンプトキャッシュを理解するには、入力トークンを次の種類に分けて考えると役立ちます。
- 通常の入力トークン:通常の入力料金で課金されます。
- キャッシュ作成トークン:キャッシュエントリの作成に使われるトークンで、多くの場合、割増料金または専用の書き込み料金が適用されます。
- キャッシュ読み取りトークン:既存のキャッシュエントリから読み取られるトークンで、通常は割引料金が適用されます。
Anthropicでは、次のような使用量フィールドで、この区別を直接確認できます。
{
"usage": {
"input_tokens": 812,
"cache_creation_input_tokens": 42000,
"cache_read_input_tokens": 0,
"output_tokens": 684
}
}
後続のリクエストでプレフィックスがキャッシュにヒットした場合は、次のような値になっていることが望まれます。
{
"usage": {
"input_tokens": 936,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 42000,
"output_tokens": 711
}
}
最初の呼び出しでキャッシュを作成し、後続の呼び出しでそこから読み取ります。
コスト面での効果は、プロバイダーの現在の料金、キャッシュのTTL、モデルによって変わります。料金は変わるため、私は設計ドキュメントに数値を固定で書くよりも、コード内の変数でモデル化するようにしています。
あるルートにキャッシュを実装する価値があるか判断するときに、私が使っている小さなPython関数を紹介します。
def cache_savings(
cached_tokens: int,
normal_input_per_mtok: float,
cache_write_per_mtok: float,
cache_read_per_mtok: float,
requests: int,
) -> dict:
mtok = cached_tokens / 1_000_000
no_cache = requests * mtok * normal_input_per_mtok
with_cache = (
mtok * cache_write_per_mtok +
(requests - 1) * mtok * cache_read_per_mtok
)
return {
"no_cache": round(no_cache, 6),
"with_cache": round(with_cache, 6),
"savings": round(no_cache - with_cache, 6),
"savings_pct": round((1 - with_cache / no_cache) * 100, 2),
}
print(cache_savings(
cached_tokens=42_000,
normal_input_per_mtok=3.00,
cache_write_per_mtok=3.75,
cache_read_per_mtok=0.30,
requests=100,
))
ここで使っている料金は例示用です。重要なのは計算の構造です。
42,000トークンの再利用可能なプレフィックスと100件のリクエストがある場合、キャッシュは通常、非常に効果的です。同じプレフィックスでもリクエストが一度しかない場合、書き込み料金が通常の入力料金より高ければ、キャッシュを使うほうが高くなることがあります。
これが最初の大きな落とし穴です。プロンプトキャッシュは、繰り返し使うことでコストを償却する仕組みです。キャッシュしたプレフィックスを再利用しなければ、メリットは得られません。
損益分岐点の計算式
損益分岐点は、次の式で推定できます。
no_cache_cost = N * T * normal_price
cache_cost = T * write_price + (N - 1) * T * read_price
各変数の意味:
Nは、同じプレフィックスを使用するリクエスト数ですTは、キャッシュ可能なトークン数ですnormal_priceは、通常の入力トークン料金ですwrite_priceは、キャッシュ作成料金ですread_priceは、キャッシュ読み取り料金です
次の条件を満たすと、キャッシュによってコストを削減できます。
T * write_price + (N - 1) * T * read_price
<
N * T * normal_price
この T は、純粋なコストの損益分岐点を求める際には相殺されるため、判断を左右するのは再利用回数と料金の比率です。ただし、運用上はトークン数も重要です。500トークンで80%を節約しても微々たる差ですが、80,000トークンで同じ割合を節約すれば、費用項目として意味のある額になります。
概算に役立つ式:
break_even_requests =
(write_price - read_price) / (normal_price - read_price)
キャッシュへの書き込みが通常の入力よりわずかに高いだけで、読み取りが大幅に安ければ、損益分岐点はかなり低くなることがあります。書き込みが高額だったり、プレフィックスがほとんど再利用されなかったりすると、キャッシュの効果は不安定になります。
変化しないプレフィックスを作るためのプロンプト構成
プロンプトキャッシュの最適化で最も効果的なのは、APIフラグではありません。プロンプトの配置です。
悪い配置:
Request ID: req_8f19a
Current time: 2026-02-14T09:12:33Z
User: customer_4812
Locale: en-US
SYSTEM INSTRUCTIONS:
...
POLICY MANUAL:
[42,000 tokens of stable text]
USER QUESTION:
Can I get a refund after 45 days?
この配置ではプロンプトが先頭から変わるため、プレフィックスのキャッシュが機能しません。
より良い配置:
SYSTEM INSTRUCTIONS:
...
POLICY MANUAL:
[42,000 tokens of stable text]
DYNAMIC REQUEST METADATA:
Request ID: req_8f19a
Current time: 2026-02-14T09:12:33Z
User: customer_4812
Locale: en-US
USER QUESTION:
Can I get a refund after 45 days?
変化しないブロックを先頭に置き、動的なブロックをその後に置きます。
Anthropic方式の明示的なキャッシュでは、概念的には次のような構造になります。
{
"model": "claude-sonnet-4-6",
"max_tokens": 800,
"system": [
{
"type": "text",
"text": "You are a support policy assistant..."
},
{
"type": "text",
"text": "Refund policy manual v2026-02-01:\n\n...",
"cache_control": {
"type": "ephemeral"
}
}
],
"messages": [
{
"role": "user",
"content": "Can I get a refund after 45 days?"
}
]
}
正確なリクエストスキーマはAPIのバージョンやSDKによって異なるため、これはコピーしてそのまま使うコードではなく、構造を示すものとして捉えてください。重要なのは、大きくて変化しないブロックを明示的にキャッシュ可能にし、呼び出しの間で変更しないことです。
OpenAI方式の自動キャッシュでも、ブロックを手動で指定しない場合であっても、プロンプト構造は同じくらい重要です。
{
"model": "gpt-5.5",
"messages": [
{
"role": "system",
"content": "You are a support policy assistant...\n\nRefund policy manual v2026-02-01:\n\n..."
},
{
"role": "user",
"content": "Can I get a refund after 45 days?"
}
]
}
システムメッセージがリクエストごとに変わる場合、安定したキャッシュ読み取りは期待できません。
実践的なアーキテクチャパターン
本番システムでは、私はキャッシュ対象のプロンプトプレフィックスをアプリケーションコード内で明示的に扱うようにしています。
シンプルなパターンの例:
prompt_prefix/
support_policy_v17.txt
tool_schema_v4.json
examples_v9.md
runtime_request/
user_question
user_profile
retrieved_recent_orders
そのうえで、次のようにリクエストを組み立てます。
from pathlib import Path
POLICY_PREFIX = Path("prompt_prefix/support_policy_v17.txt").read_text()
TOOL_SCHEMA = Path("prompt_prefix/tool_schema_v4.json").read_text()
EXAMPLES = Path("prompt_prefix/examples_v9.md").read_text()
def build_prompt(user_question: str, user_profile: dict) -> str:
stable_prefix = f"""
You are a support policy assistant.
TOOLS:
{TOOL_SCHEMA}
POLICY:
{POLICY_PREFIX}
EXAMPLES:
{EXAMPLES}
""".strip()
dynamic_suffix = f"""
USER PROFILE:
{user_profile}
USER QUESTION:
{user_question}
""".strip()
return stable_prefix + "\n\n--- DYNAMIC REQUEST ---\n\n" + dynamic_suffix
実際には、プレフィックスのバージョン管理も行います。ポリシーマニュアルが変わったときには、意図的なキャッシュ無効化として扱いたいからです。
sha256sum prompt_prefix/support_policy_v17.txt
そのハッシュは、モデル名、キャッシュ作成トークン数、キャッシュ読み取りトークン数、リクエストのルートとともにログに記録します。このような観測可能性がなければ、キャッシュのバグへの対処は厄介です。レスポンス自体は正常に返り、ただコストだけが増えるからです。
役に立つログ行の例:
{
"route": "support.answer",
"model": "claude-sonnet-4-6",
"prefix_hash": "9d3a4a...",
"input_tokens": 932,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 42000,
"output_tokens": 688,
"latency_ms": 1840
}
請求額だけを見て最適化しないでください。ルートごとにキャッシュの挙動をログに記録しましょう。
レイテンシ:改善することは多いが、魔法ではない
プロンプトキャッシュは、モデルプロバイダーがキャッシュ済みプレフィックスの計算を再利用できるため、レイテンシを削減できることがあります。ただし、自分のワークロードで測定していない限り、一定のレイテンシ削減を前提にSLAを設計することはありません。
本番環境で実際に起きることは、もっと複雑です。
- 短いプロンプトでは、レイテンシに有意な変化が見られない場合があります。
- キャッシュされた長いプレフィックスは、最初のトークンが返るまでの時間を短縮することがよくあります。
- 多くのリクエストでは、依然として出力の長さが所要時間を大きく左右します。
- ツール呼び出し、ネットワークのレイテンシ、キュー待ちによって、キャッシュの効果が見えにくくなることがあります。
- 書き込みが読み取りより遅い場合、キャッシュミスによってテールレイテンシが急増します。
実際の利用状況に即したベンチマークの構成はシンプルです。
# warm cache
python run_eval.py --case support_refund --repeat 1
# measure cache reads
python run_eval.py --case support_refund --repeat 50 --jsonl out.jsonl
# inspect p50/p95 by cache_read_input_tokens > 0
python analyze_latency.py out.jsonl
キャッシュヒットしたリクエストとキャッシュミスしたリクエストを別々に測定してください。両者を混ぜると、どちらの挙動も説明できない数値になります。
実際のシステムで見かける落とし穴
1. 安定したコンテンツより前にある動的コンテンツ
これは最もよくある失敗です。タイムスタンプ、リクエストID、機能フラグのダンプ、ユーザーのロケール、実験の割り当て情報などが、システムプロンプトの先頭に置かれています。
それらを後ろに移してください。
2. 「同等」のJSONが同一とは限らない
次のスニペットは意味的には同一ですが、キャッシュ上も同一とは限りません。
{"name":"refund_lookup","timeout":3,"strict":true}
{
"strict": true,
"timeout": 3,
"name": "refund_lookup"
}
キャッシュするプレフィックスにツールスキーマや設定を含める場合は、決定的な方法でシリアライズしてください。
import json
def stable_json(data: dict) -> str:
return json.dumps(data, sort_keys=True, separators=(",", ":"))
これにより、キーの順序や空白の変更による意図しないキャッシュミスを防げます。
3. 検索がキャッシュ可能性を損なうことがある
RAGシステムでは、次のようにプロンプトを組み立てることがよくあります。
Retrieved documents:
[doc A]
[doc B]
[doc C]
Instructions:
...
検索結果がクエリごとに異なれば、プレフィックスもクエリごとに異なります。検索で取得したコンテキストが実際に動的なものであれば問題ありませんが、安定した指示をその後ろに置かないでください。
よりよい構成:
Instructions:
...
Long-lived product documentation:
[stable docs]
Query-specific retrieved documents:
[doc A]
[doc B]
[doc C]
大量のリクエストを処理するシステムでは、コンテキストを次のように分割することを検討してください。
- 全体で共通の安定した指示
- テナント内で安定したドキュメント
- ユーザーやクエリに固有の検索結果
各レイヤーでキャッシュの挙動は異なります。
4. キャッシュのTTLが重要
キャッシュエントリには有効期限がある場合があります。トラフィックが集中するタイプであれば、あるプレフィックスが十分間にわたって頻繁に再利用された後、使われなくなることもあります。キャッシュの有効期間を延ばすと役立つ場合がありますが、プロバイダーやモデルによって料金や利用可否が異なることがあります。
ここではリクエストの分布が重要です。「一日に10,000件のリクエストがある」という情報よりも、「午前9時から午前10時の間、この完全に同じプレフィックスに対して毎分200件のリクエストがある」という情報のほうが有用です。
5. モデルの変更で前提が崩れる
あるモデルでキャッシュされたプレフィックスを、別のモデルでも再利用できると考えるべきではありません。Claude Opus 4.8、Sonnet 4.6、GPT-5.5、Gemini 3、あるいはLlama、Qwen、DeepSeek、MiniMax、Kimiなどのオープンモデル間でルーティングする場合は、モデルごとにキャッシュの挙動を追跡してください。
複数モデルへのルーティングは便利ですが、コストの償却は複雑になります。トラフィックを五つに分散すると、各モデルで同じプレフィックスが繰り返される回数が減る可能性があります。
6. プロンプトのパーソナライズが逆効果になることがある
チームはシステムプロンプトをパーソナライズすることがよくあります。
You are helping {{customer_name}}, who is on plan {{plan_name}}.
無害に思えますが、それがキャッシュされるコンテンツより前にあると、顧客ごとに異なるプレフィックスになります。安定したブロックに対するモデルの解釈を実際に変える場合を除き、パーソナライズする内容は後ろに置いてください。
プロンプトキャッシュでコストを削減できる場合
通常、次の条件ではキャッシュがよく適しています。
- キャッシュ可能なプレフィックスが大きく、数千から数万トークンに及ぶことが多い。
- 完全に同じプレフィックスが何度も再利用される。
- プレフィックスがリクエスト間で安定している。
- キャッシュの有効期間内にトラフィックが発生する。
- キャッシュ作成時とキャッシュ読み取り時のトークン数を確認できる。
- ルートごとのプロンプト構成が予測可能である。
具体例:
- コーディングアシスタントが、同じリポジトリマップとスタイルガイドを繰り返し含める。
- サポートボットが、返金に関するすべての質問に同じポリシーマニュアルを含める。
- エージェントが、すべてのリクエストで大きな静的ツールスキーマを使用する。
- 社内のリサーチアシスタントが、チーム向けに同じコーパスの概要を含める。
こうしたワークロードでは、コンテキストの品質を下げる必要がないため、プロンプトキャッシュは最も効果の高いコスト最適化の一つになり得ます。長いプロンプトはそのままに、毎回入力の通常料金を全額支払わずに済むようになります。
プロンプトキャッシュが役立たない場合
次の場合、キャッシュの効果は小さいか、逆効果になります。
- プロンプトが短い。
- プレフィックスがほとんど再利用されない。
- コンテキストの大部分がクエリ固有の検索結果である。
- 動的データが安定したデータより前にある。
- 実験中に指示を頻繁に変更している。
- 多くのモデル、テナント、プロンプトのバリエーションにトラフィックが薄く分散している。
- キャッシュの書き込みコストが高く、読み取りが少なすぎてそのコストを償却できない。
評価でよくある落とし穴として、ノートブックでは各ケースを一度しか実行しないため、プロンプトキャッシュの効果が悪く見えることがあります。その後、本番環境では数千人のユーザーが同じ安定したプレフィックスを共有するため、非常に効果的に見えます。逆のことも起こります。デモでは同じプロンプトを100回繰り返して大幅な節約を示していても、本番環境ではテナントごとのカスタマイズによって再利用できないことがあります。
実際のリクエスト分布でテストしてください。
シンプルなキャッシュ準備チェックリスト
プロンプトキャッシュを有効にする前、またはそれを前提にする前に、私は次の点を確認します。
- 数千トークンを超える安定したプレフィックスが少なくとも一つあるか?
- それはすべての動的コンテンツより前にあるか?
- シリアライズは決定的か?
- プレフィックスはバージョン管理され、ハッシュ化されているか?
- キャッシュ作成時とキャッシュ読み取り時のトークン数をログに記録できるか?
- キャッシュの有効期間内に予想される再利用回数を把握しているか?
- キャッシュヒットとキャッシュミスのレイテンシを別々に測定したか?
三つ以上の項目で答えが「いいえ」であれば、キャッシュが機能する可能性はあっても、推測に頼っている状態です。
実践上のポイント
プロンプトキャッシュは魔法の圧縮ではありません。安定したトークンプレフィックスを再利用する仕組みであり、作成コストを一度支払い、その後は低い読み取りコストを何度も支払うことで経済的なメリットが生まれます。
エンジニアリング上の進め方は明快です。
- 大きく安定したコンテンツを先頭に置く:指示、ツール、ドキュメント、例。
- タイムスタンプ、ユーザーメタデータ、検索結果、リクエストIDは、キャッシュ可能なプレフィックスの後ろに移す。
- 追跡する
cache_creation対cache_readルート別・モデル別のトークン数。 - JSONのツールスキーマと設定には、決定的なシリアライズを使用する。
- 実際のプロバイダー料金とトラフィック分布を使って損益分岐点をモデル化する。
- 複数モデルへのルーティング、テナントごとのカスタマイズ、短いプロンプトには注意する。
- 平均値を信頼するのではなく、ヒットとミスのレイテンシを別々に測定する。
私が見た中で最も優れたプロンプトキャッシュの最適化は、凝ったものではまったくありませんでした。42,000個の安定したトークンを、12個の動的トークンより前に移しただけです。モデルの挙動は変わりませんでした。請求額は変わりました。
Claude Opus 5.5、Sonnet 5、Haiku 4.5、Fable 5.1に加え、GPT-6のモデルを、単一のAPIキーで利用できます。従量課金制で、サブスクリプションは不要です。
APIキーを取得 →