プロンプトキャッシングとは
プロンプトキャッシングとは、APIリクエストで繰り返し送信する長いプロンプト(システムプロンプト・ドキュメント・Few-shot例など)をサーバー側でキャッシュし、次回のリクエストからキャッシュヒット分のトークンコストとレイテンシを大幅に削減する機能だ。Anthropicが2024年8月にClaude 3.5 Sonnet向けに正式リリースし、現在はClaudeの全主要モデルで利用可能となっている。
Anthropicの実装では、キャッシュしたいコンテンツブロックの末尾に "cache_control": {"type": "ephemeral"} を付与することでキャッシュが有効になる。最低1024トークン以上のブロックからキャッシュが適用され、キャッシュの有効期間(TTL)は5分だ。キャッシュヒット時のコストはキャッシュ書き込み時の約10%となり、大幅なコスト削減が可能になる。OpenAIも2024年10月にGPT-4o向けに自動プロンプトキャッシュをリリースしており、1024トークン以上の共通プレフィックスが自動的にキャッシュされる。
なぜ注目されているのか
RAG(検索拡張生成)やエージェントシステムでは、毎回のリクエストに数千〜数万トークンの共通ドキュメントやシステムプロンプトを含める必要がある。プロンプトキャッシングを使えば、この繰り返し部分のコストを最大90%削減できる。Claude 3.5 Sonnetで100ページのドキュメントを10回参照するケースでは、キャッシングなしと比較してAPIコストが7〜8割減になるという計算が成り立つ。
また、レイテンシへの効果も大きい。キャッシュヒット時はサーバー側でのトークン処理が省略されるため、First Token Time(最初のトークンが返るまでの時間)が短縮される。Anthropicの公開データによれば、キャッシュヒット時のレイテンシ削減率は最大80%に達することがある。エージェントのループ処理において、固定のシステムプロンプトやツール定義をキャッシュするだけで全体のコストを大幅に削減できる。
制約と注意点
TTLが5分(Anthropic)という制約に注意が必要だ。5分以上の間隔が空くとキャッシュが無効化されるため、低頻度のリクエストには効果がない。また、キャッシュされるのはプロンプトの「プレフィックス部分」に限られるため、キャッシュ対象コンテンツはリクエストの早い段階(システムプロンプト・長いドキュメント)に配置し、ユーザー入力はその後に続ける必要がある。キャッシュ対象ブロックが毎回変更されるとキャッシュが効かないため、動的な内容はプロンプトの末尾に置くのが原則だ。
ドラゴンボールで例えると
プロンプトキャッシングは**界王様の「コンテキスト記憶」**で例えられる。
- 毎回フルで送るプロンプト(キャッシュなし) = 悟空が界王様に電話するたびに「私はカカロットこと孫悟空です。地球出身のサイヤ人で、今フリーザと戦っていて、超サイヤ人に変身できて…」と毎回最初から全部説明する
- プロンプトキャッシング = 「悟空のことはわかった、次からは用件だけ言え」と界王様が覚えている状態。同じ自己紹介部分(共通プレフィックス)は省略できる
- TTL 5分 = 界王様の「短期記憶保持時間」。5分以上話が途切れると忘れて、また最初から説明が必要になる
- コスト削減90% = 毎回の長い自己紹介をスキップできるので、1回の通話時間(=トークンコスト)が激減
- プレフィックスを変えるとキャッシュが効かない = 「実は今日からベジータと名乗っています」と自己紹介を変えると、界王様の記憶が使えなくなり最初から説明し直し
「毎回同じ長い説明をするな」——AIの世界でも、共通部分は1回だけ覚えてもらえば大幅にコストが下がる。
実装してみる
- 01 キャッシュ対象コンテンツの末尾ブロックに
cache_control: {type: "ephemeral"}を付与する - 02 キャッシュが有効になる最低トークン数は1024(Anthropic)。TTLは5分
- 03 レスポンスの
usage.cache_read_input_tokensでキャッシュヒット数を確認し、コスト削減を測定する
import anthropic
client = anthropic.Anthropic()
long_doc = "詳細なシステム仕様書..." * 300 # 1024トークン以上
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=[
{
"type": "text",
"text": long_doc,
"cache_control": {"type": "ephemeral"},
}
],
messages=[{"role": "user", "content": "この仕様書を要約して"}],
)
usage = response.usage
print(f"キャッシュヒット: {usage.cache_read_input_tokens} tokens")
print(f"キャッシュ書き込み: {usage.cache_creation_input_tokens} tokens")
以下のドキュメントを参照してユーザーの質問に答えてください。 [ここに長いドキュメントを挿入 — このブロックがキャッシュされます] 質問: [ユーザーの質問]