Claude Codeを1日回すとAPI代が数万円。ChatGPTで長い会話を続けると、だんだん応答が遅くなる——。
AI Agentが当たり前になった2026年、LLMの運用コストと応答速度は「プロンプトの書き方」以上に 「同じ計算を何度やらせるか」 で決まるようになりました。20ターンの会話をすると、モデルは前の19ターン分を毎回ゼロから再計算しています。これ、冷静に考えると相当もったいない。
プロンプトキャッシュ(Prompt Caching) は、この「ムダな再計算」を根本から潰す技術です。
この記事では、キャッシュがなぜ安くなるのかの経済ロジックから、Transformer内部のKVキャッシュの挙動、そしてWeb利用・エージェント開発・CLIツールそれぞれでキャッシュを最大限に活かすための具体的な操作方法まで、一気に解剖します。
💡 前回の記事では、DeepSeek V4のキャッシュヒット時の入力単価がClaude Opusの 1786分の1 であることを計算しました。「そもそもキャッシュヒットって何?」という方は、まずそちらの具体的な料金比較を読んでからこの記事に戻ると、理解がさらに深まるはずです。
👉 Claude Opusの1786分の1!? DeepSeek V4のAPI価格がバグってるので全部計算してみた
「キャッシュヒット=90%オフ」の経済ロジック
「なんで読み取るだけで90%も安くなるの?」—— 最初に聞いたとき、正直ちょっと胡散臭いと思いました。でもクラウド側の原価構造を見ると、むしろ妥当な数字です。
GPU計算 vs メモリ読み取り
LLMがテキストを処理する本質は エンコーディング(符号化) です。
- 再計算(キャッシュミス):GPU上で巨大な行列演算をぶん回す。電気代もGPU占有時間もガッツリかかる
- キャッシュ読み取り(キャッシュヒット):計算済みの中間状態をメモリから引っ張ってくるだけ。GPUはほぼ遊んでいる
クラウド事業者にとって最も高い原価は「GPUの稼働時間」。キャッシュヒットはその稼働時間をほぼゼロにできるから、90%ディスカウントしても十分に利益が出る。要するに 「高価な実時間計算を、安価なストレージ読み取りで代替している」 わけです。
再計算のムダを数字で見る
たとえば20ターンの対話を考えてみてください。
| ターン | 従来方式の計算量 | キャッシュ方式の計算量 |
|---|---|---|
| 1 | ターン1を計算 | ターン1を計算 |
| 2 | ターン1+2を再計算 | ターン1はキャッシュ → 2だけ計算 |
| 10 | ターン1〜10を再計算 | ターン1〜9はキャッシュ → 10だけ計算 |
| 20 | ターン1〜20を再計算 | ターン1〜19はキャッシュ → 20だけ計算 |
従来方式だと、ターン1の内容は 20回 計算されます。キャッシュ方式なら 1回 。ターン数が増えるほど、節約効果は加速度的に膨らむ。
KVキャッシュの仕組み:なぜ「旧Token」は再計算不要なのか
ここからが技術的に面白い部分です。新しいTokenが追加されたとき、前のTokenの計算結果は影響を受けないのか?普通に考えると「新メンバーが入ったら全体の関係性が変わるんじゃない?」と思いますよね。
Causal Masking(因果的注意マスク)が守る「不可侵の壁」
生成系Transformerには Causal Masking という仕組みがあります。
ルール:各Tokenは、自分自身と自分より前のTokenだけを参照できる。後ろのTokenは絶対に見えない。
Token A → A だけを見る
Token B → A, B を見る
Token C → A, B, C を見る
Token D(新規追加) → A, B, C, D を見る
このルールがあるおかげで、Token Bの「数学的な状態」(KV値)は、Aの内容とBの内容 だけ で確定します。後ろにDが来ようがXが来ようが、Bの計算結果は永遠に変わらない。これがKVキャッシュを安全に再利用できる根拠です。
増分計算:新Tokenが来たとき何が起きるか
新しいToken Dが追加されたとき、モデル内部では3つのパターンが走ります:
| パターン | 処理 | コスト |
|---|---|---|
| 旧 → 旧(A,B,CがA,B,Cを参照) | 100%スキップ。キャッシュをそのまま使う | ゼロ |
| 新 → 旧(DがA,B,Cを参照) | 必須計算。Dのクエリベクトルを生成し、キャッシュ済みのK/Vと照合 | あり |
| 旧 → 新(A,B,CがDを参照) | Causal Maskにより 物理的に存在しない | — |
この「増分更新」によって、計算の複雑度が $O(N^2)$ から $O(N)$ に落ちる。つまり、プロンプトが長くなっても、新しく追加した部分の計算量しか増えない。これが TTFT(最初のトークンまでの応答時間) を劇的に短縮する理由です。
キャッシュの生命線:前缀マッチングと「連鎖失効」
キャッシュシステムは 前缀マッチング(Prefix Matching) で動いています。これを理解しないと、せっかくのキャッシュが全滅する罠にハマります。
前缀マッチング = 「列車方式」
キャッシュは先頭から順番に照合されます。イメージとしては一列に並んだ列車の車両:
✅ キャッシュヒット
[A] → [B] → [C](キャッシュ済み)→ [D](新規)
A-B-Cが完全一致 → キャッシュヒット。Dだけが新規計算の対象。
❌ 連鎖失効(ウォーターフォール効果)
[A] → [B] → [X(Cを修正)] → [D] → [E]
- A → B:まだ一致しているのでキャッシュヒット ✅
- X:Cと異なる → ここで前缀が途切れる ❌
- D → E:テキストが元のままでも、前方の「数学的な経路」が変わったため 全部再計算 ❌
中間を1文字いじるだけで、それ以降のキャッシュが 全滅する。これがウォーターフォール効果です。
黄金律:「変わらないものは左、変わるものは右」
[固定] システムプロンプト / 業務ロジック / ツール定義
[固定] 長文ドキュメント / Few-shot例
[変動] ユーザーの今回の質問 ← ここだけ毎回変わる
変更頻度が低いものほど先頭(左)に配置する。 これがプロンプトキャッシュの最も重要な設計原則です。
実戦ガイド①:Web利用ユーザー向け
普段ChatGPTやClaudeのWebインターフェースを使っている方向けのテクニックです。
長対話 > 新規ウィンドウ
同じタスクについて聞きたいなら、同じ対話の中で続けて質問する。新しいウィンドウを開くと、バックエンドではそれまでの文脈をゼロからロードし直すことになります。
「追加」する、「編集」しない
以前の質問に間違いがあった場合、多くの人が「編集」ボタンを押して直そうとします。でもこれをやると、編集した箇所以降のキャッシュが全部吹き飛ぶ。
正解は 新しいメッセージとして「さっきの質問、○○の部分を△△に訂正します」と追加する こと。前缀が壊れないので、キャッシュはそのまま生き残ります。
固定月額ユーザー(Pro/Plus)も無関係じゃない
「自分は月額課金だからToken代は関係ない」—— 半分正解ですが、キャッシュが効くことで得られるメリットは料金だけじゃないです:
- 応答速度が段違い:キャッシュヒットすると、モデルが万字単位の過去のコンテキストを「再読」しないので、TTFT(最初のトークンが返ってくるまでの時間)が体感で激速になる
- 使用枠に余裕が出る:サブスク制でも「メッセージ上限」がある。キャッシュ効率が高い使い方はプラットフォーム側の負荷も下がるので、上限に引っかかりにくくなる
- 長対話の安定性:キャッシュ前缀が安定していると、超長文コンテキストでもモデルの理解精度が落ちにくい
実戦ガイド②:エージェント開発者向け(LangChain等)
API経由でエージェントを構築している開発者が押さえるべきポイントは、「コードの書き方」よりも 「プロンプト構造のアーキテクチャ」 です。
静的コンテンツをヘッダー化する
Prompt Templateを組み立てるとき、最も重い(そして最も変わらない)部品を先頭に固定する:
# ✅ キャッシュに優しい構成
prompt = [
SystemMessage(content=STATIC_BUSINESS_LOGIC), # 数千トークンの業務ルール
SystemMessage(content=TOOL_DEFINITIONS), # ツール定義
SystemMessage(content=FEW_SHOT_EXAMPLES), # Few-shot例
HumanMessage(content=f"現在時刻: {now}\n{user_query}") # 変動部分は末尾
]
# ❌ キャッシュが壊れる構成
prompt = [
SystemMessage(content=f"現在時刻は{now}。{STATIC_BUSINESS_LOGIC}"), # 先頭に動的情報
HumanMessage(content=user_query)
]
System Promptに動的情報を入れない
これは本当によく見るアンチパターンです。
# ❌ 毎回タイムスタンプが変わるのでキャッシュ全滅
system = f"あなたはAIアシスタントです。現在時刻: {datetime.now()}"
# ✅ タイムスタンプはユーザーメッセージ側に
system = "あなたはAIアシスタントです。"
user = f"[現在時刻: {datetime.now()}]\n{actual_question}"
1文字でも先頭が変われば前缀マッチングが壊れる。System Promptは「彫刻のように固定する」のが鉄則。
Anthropic SDK:明示的キャッシュブレークポイント
Claude APIを使う場合、cache_control パラメータで手動キャッシュポイントを設定できます:
response = client.messages.create(
model="claude-sonnet-4-6-20260514",
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": massive_document,
"cache_control": {"type": "ephemeral"} # ← ここにキャッシュブレークポイント
},
{
"type": "text",
"text": "このドキュメントを要約してください"
}
]
}
]
)
長大なドキュメントや会話履歴の末尾にこの印をつけると、そこまでの内容がキャッシュ対象として明示的にマークされます。
実戦ガイド③:CLIエージェント(Claude Code等)の深い最適化
Claude Codeのようなエージェントツールは、キャッシュ効率を極限まで絞り出すために非常に精密な内部設計をしています。でも、ユーザー側の操作ひとつでその努力が台無しになることも。ツール内部の仕組みと、それに合わせたユーザーの振る舞いをセットで理解するのが大事です。
プロジェクト冷起動:インデックスキャッシュ
ツール側の最適化:起動時にプロジェクトのディレクトリツリーをスキャンし、巨大なインデックスキャッシュを構築する。
ユーザーがやるべきこと:「1セッション・マルチタスク」で使う。 バグの特定→コード修正→テスト実行を、1回のセッション内で連続してこなす。小さな変更のたびにセッションを閉じて再起動すると、毎回プロジェクトスキャンが走って数千トークン分のキャッシュが無駄になる。
モード切替:非侵入的な状態遷移
ツール側の最適化:「計画モード」と「実行モード」を切り替えるとき、ヘッダーのシステム指令は触らず、会話末尾に「システムメッセージ」を挿入してモデルを誘導する。前缀は壊れない。
ユーザーがやるべきこと:ツール内蔵のコマンド(/compact、モード切替等)を使う。 自分でプロンプトに大量の指示文を手打ちすると、制御できない文字列変化が混入して、ツールが丁寧に維持している前缀チェーンが断裂する。
遅延ロード:ツール定義の最適化
ツール側の最適化:プロンプトのヘッダーを安定させるため、起動時は軽量なツール名プレースホルダーだけを配置し、フル定義は必要時にのみロードする。
ユーザーがやるべきこと:セッション中に大量のカスタムツールやスクリプトを動的に注入しない。 やりたい気持ちはわかるのですが、それをやるとプロンプト前部の構造が強制変更され、積み上げたプロジェクトコンテキストキャッシュが全焼します。
コンテキスト上限時の摘要分岐
ツール側の最適化:コンテキストが上限に達すると、ミラー前缀リクエストを構築して進捗スナップショットを生成する。
ユーザーがやるべきこと:/compact を使って自動要約させる。 手動で対話内容をコピペして新しいウィンドウに持ち込むと、前缀(システム指令、プロジェクトインデックス)が以前と一致しないため、旧キャッシュは一切再利用できず、高額な再エンコード費用が発生する。
まとめ:キャッシュは「節約テクニック」ではなく「設計原則」
| 観点 | 従来の発想 | キャッシュ思考 |
|---|---|---|
| メッセージ修正 | 間違えたら直す | 追加で訂正、前缀を守る |
| 対話戦略 | 用事があるたびに新規ウィンドウ | 1つの対話で深掘りする |
| コスト削減 | テキストを短く圧縮する | 構造を安定させ、再利用を最大化する |
| System Prompt | 便利だから動的情報も入れる | 彫刻のように固定する |
| エージェント起動 | タスクごとに再起動 | 1セッションで連続処理する |
プロンプトキャッシュを理解して設計に組み込むと、コストは下がり、応答は速くなり、長文コンテキストでの精度も安定する。「ちょっとした操作の癖」が月額のAPI代を桁で変えることもあります。
この記事の裏側にある料金の具体的な数字が気になった方は、ぜひこちらもどうぞ:
👉 Claude Opusの1786分の1!? DeepSeek V4のAPI価格がバグってるので全部計算してみた
💬 議論テーマ
みなさんはプロンプトキャッシュを意識した設計、すでに実践していますか? 「この方法でキャッシュヒット率が上がった」「逆にこれでキャッシュが全滅した」みたいな実体験があれば、コメント欄で共有してもらえると嬉しいです!
