一覧へ
1 分で読めます

Claude Code の API コストを90%削減するキャッシュ設計

本番環境でキャッシュが壊れた瞬間、コストが10倍に跳ね上がった。同日、Anthropicのエンジニアがその理由を詳しく解説していた。

昨日、急ぎの本番対応をしていた最中に、プロンプトキャッシュが突然壊れた。その1時間のAPI請求額は、前の3日間の合計を上回っていた。

タイミングが皮肉としか言いようがなかった。その同じ晩、Claude Code を Anthropic で開発した Thariq と、同じく Anthropic の Lance Martin がそれぞれプロンプトキャッシュの設計について記事を公開していた。読み進めるうちに、自分のキャッシュが偶然ではなく、設計上の問題から脆弱だったことに気づいた。

以下は、その2本の記事から得た知見を、昨日の痛い経験でフィルタリングしてまとめたものです。

プレフィックスマッチングでは、順序がすべてを決める

Anthropic API のプロンプトキャッシュは、リクエストの先頭からトークン単位でマッチングする仕組みになっている。キャッシュ済みのバージョンと1文字でも異なる箇所が現れた瞬間、それ以降はすべてキャッシュミスになる。部分マッチも、途中からのスキップもない。

Claude Code チームは、プロンプトの順序をインフラとして扱っている。まず静的なシステムプロンプトを先頭に置き、次に CLAUDE.md、そしてセッションコンテキストと続く。会話メッセージは最後だ。ターンごとに変化するからこそ、最後に置く。この順序により、コストのかかる安定したプレフィックスがセッション内の全リクエストにわたってキャッシュされ、再利用される。

キャッシュ済みトークンのコストは、通常の入力トークンの10%に過ぎない。この差があるからこそ、キャッシュが壊れたときに「10倍の値上げ」のような感覚になる。

私のミスは、システムプロンプトにタイムスタンプを埋め込んでいたことだった。リクエストのたびに新しいタイムスタンプが生成されるため、先頭のトークンが毎回異なってしまい、後続のいかなる内容もキャッシュできなかった。デバッグログを1か所間違えただけで、100Kトークン超のリクエストすべてに対してフル料金を払う羽目になっていた。

Claude Code チームはまた、ツール定義の順序が非決定的になるとキャッシュミスが発生するとも報告している。ツール自体が変わっていなくても、リクエスト間でシリアライズの順序が変わるだけで、その時点からキャッシュが壊れる。

コンテキストの変更はメッセージで伝え、システムプロンプトを編集しない

セッション途中でコンテキストが変わった場合、たとえばファイルが更新された、時刻が変わった、モードが切り替わったといったとき、まず思いつくのはシステムプロンプトを更新することだろう。だが、それはやってはいけない。システムプロンプトを編集すると、キャッシュ済みのプレフィックス全体が無効になる。

Claude Code はこれを、最初のリクエスト以降はシステムプロンプトに一切手を加えないことで解決している。変更されたコンテキストは、次のユーザーメッセージに system-reminder タグで包んで追加する。モデルは同じように読み取れるうえ、キャッシュのプレフィックスはそのまま保たれる。

Plan Mode はその好例だ。Plan Mode への切り替えをツール定義の差し替えで実装すると、その時点でキャッシュが壊れる。代わりに Claude Code では EnterPlanMode というツールコールとして実装しており、モデル自身が必要に応じて呼び出せるようにしている。ツールセットは変わらない。モデルが難しい問題を検知したとき、システムプロンプトを変更することなく、自律的に Plan Mode に入れる。

同じ考え方は、モデルの切り替えにも適用される。会話の途中でモデルを変えると、キャッシュが完全に無効になる。Claude Code は、異なるモデルをサブエージェントとして別コンテキストで動かすことでこれを回避し、親会話のキャッシュを維持している。

ツールは削除せず、隠す

MCP サーバーは数十のツールをロードすることがある。すべてをすべてのリクエストに含めるのはコストがかかる。しかし、リクエスト間でツールを取り除くと、ツール定義はキャッシュのプレフィックスの一部であるため、キャッシュが壊れる。

Claude Code チームの解決策は defer_loading だ。完全なツールスキーマを含める代わりに、ツール名と defer_loading: true フラグだけを持つ軽量なスタブを挿入する。スタブは毎回同じ順序で保たれるため、キャッシュのプレフィックスは同一のままだ。モデルが実際にツールの完全なスキーマを必要とした場合は、ToolSearch ツールを呼び出してオンデマンドでロードする。

このパターンは、現在の Anthropic API で利用可能だ。同じスタブ・アンド・サーチのアプローチを、自分のエージェントにも実装できる。

Manus の peakji は、キャッシュヒット率を本番エージェントにおける最も重要な単一指標と呼んでいた。昨日の経験を経て、まったく同意する。

コンテキスト圧縮にはキャッシュの落とし穴がある

会話がコンテキストウィンドウを埋め尽くしたとき、履歴を要約してトリムした形で続けるという圧縮処理が必要になる。素直なアプローチは、要約プロンプトで API を呼び出すことだ。しかし、その呼び出しで別のシステムプロンプトや別のツール定義を使うと、既存のキャッシュと一致しなくなる。コストが最も高くなるまさにその瞬間に、100Kトークン超の会話全体をキャッシュの恩恵なしに処理することになる。

Claude Code はこれを、圧縮呼び出しに親会話のシステムプロンプトとツール定義をそのまま流用することで解決している。変わるのは最後のユーザーメッセージだけで、そこに圧縮指示を入れる。親会話のキャッシュのプレフィックスは引き続き一致するため、新しいメッセージと要約の出力分だけがフル料金の対象となる。

Anthropic はその後、このパターンをコンパクション機能として API に組み込んだ。また、リクエストボディに cache_control を一度設定するだけでキャッシュのブレークポイントを自動処理するオートキャッシュ機能もリリースしている。

キャッシュヒット率を運用指標として監視する

Claude Code チームは、キャッシュヒット率を稼働率と同じように監視している。数値が下がれば、インシデントとして扱う。

この捉え方は、プロンプト設計に対する自分の考え方を変えた。システムプロンプトの編集、ツールの順序変更、セッション途中でのモデル切り替え、いずれも潜在的なインシデントになりうる。最も安いトークンはキャッシュにヒットしたものであり、その反対がいかに高くつくかを、昨日身をもって学んだ。

ニュースレターに登録

最新のプロジェクト、記事、AIとWeb開発の実験に関する情報をお届けします。