1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude API の Prompt Caching を本番投入する前に整理しておくべき 6 つの設計判断

1
Posted at

W21#1 cover-1200x630.png

Claude API の Prompt Caching を本番投入する前に整理しておくべき 6 つの設計判断

個人開発で AI ニュース配信 SaaS「DAINews」を作っている fumi(@fumikun_gengen)です。SES でインフラを兼業しながら、Claude API を採点パイプラインに組み込もうとしています。

採点コストの試算が想定より高く、本番投入前に Anthropic の Prompt Caching の採用を検討し始めました。ただ、公式ドキュメントを読んでいくと「最小トークン数がモデルによって 4 倍違う」「2026-02-05 にキャッシュのスコープが変わっている」「ブレイクポイントは 4 つまで」など、設計フェーズで先に整理しておかないと silent failure するポイントが多く、いったん本番投入を止めて整理ノートを書くことにしました。

この記事は、実プロダクトに Prompt Caching を導入する前に「何を整理して何を決めるか」 を、DAINews の採点パイプラインを題材にまとめたものです。すでに使っている人ではなく、「これから入れようとしている人」向けです。

そもそも Prompt Caching とは何か(前置き)

Anthropic の Prompt Caching は、Claude API リクエストの プロンプト prefix(変わらない部分)を Anthropic 側で一時的に保存しておき、次に同じ prefix で来たリクエストでその部分を再計算せず使い回す機能です。コスト構造は以下のようになっています(2026-05-18 確認)。

  • キャッシュ読み込み: 通常入力トークンの 10% 課金(= 90% 割引)
  • キャッシュ書き込み: 通常入力トークンの +25% 課金(×1.25)
  • TTL: 標準 5 分(直近のヒットで延命)/ Extended TTL は最大 1 時間(書き込み課金 ×2.0)

「採点ルールのような固定部分を 1 回キャッシュに書いて、以降の N 件で読みに行く」と損益分岐するため、prefix が長く同じ構造のリクエストが続くパイプラインで効きます。一方で、最小トークン数を割ったり cache_control の置き場所を間違えると、エラーにならずに 黙って効かない(silent failure)ので、設計フェーズで境界条件を詰めておく必要があります。

出典: Anthropic 公式ドキュメント Prompt caching - Pricing(2026-05-18 確認)

個人的に最初に「Prompt Caching を入れないと回らない」と思った瞬間が、2026-05-15 の dry-run でした。採点 12 サンプルを流したときの実コストは合計 $0.2075(中央値 1 件 $0.0162)で、想定 $7.2 の 2.9% に収まっていました。これは「キャッシュ書き込み 1 回 + 残り 11 件キャッシュ読み込み」が成立した結果です。

ここで一度、キャッシュなし条件の最大値($0.0449/件) で月次に展開してみました。DAINews は 12 件/日 × 30 日 = 360 件/月の採点を想定しているので、$0.0449 × 360 = 約 $16/月。一方で、DAINews フェーズ 1 の月次運用コスト上限はチケット起票時に $12 で確定済みです。

「想定通りキャッシュが効いた状態」と「効かなかった最悪ケース」で 4 ドル弱の差があり、その 4 ドルが運用上限を割るか割らないかの境界にあると気づいた瞬間が、本記事のきっかけです。本番投入の前にキャッシュ前提を「効いてくれたらいいな」ではなく「効かせないとそもそも回らない」レベルまで仕様に組み込み直す必要があると判断しました。

出典: 本記事に登場するコスト実測値($0.2075 / $0.0162 / $0.0449 / $7.2 等)は、DAINews v1 採点プロンプトの 12 サンプル dry-run(2026-05-15 / Claude Sonnet 4.6 使用 / 本番投入前の検証用ワークスペース)の実測値です。本番運用後の数値とは差が出る可能性があります。


TL;DR(3 行)

  • Prompt Caching は「節約テクニック」ではなく prefix / suffix の境界をどう設計するか の設計原則。先に境界を決めてからコードを書く
  • モデル別に最小トークン数が 1,024 と 4,096 に分かれており、Opus 4.7 / Haiku 4.5 では エラーにならず silent failure する
  • DAINews では設計判断 1〜5(配置・最小トークン・ワークスペース・ブレイクポイント・無効化トリガー)は確定済み。残るのは設計判断 6(RSS 本文を Prompt Cache に乗せる是非) で、第三者著作物が絡むため法務確認後に確定させる方針

前提:DAINews の採点パイプラインと使うモデル

DAINews は RSS で集めた IT・金融系のニュースを Claude API で採点・要約して配信する SaaS です。採点パイプラインの大枠は以下のようになっています(設計フェーズで、まだ本番稼働はしていません)。

  • モデル: Claude Sonnet 4.6(採点・要約用)
  • 入力:
    • 採点ルール定義(システムプロンプト・採点スキーマ・ツール定義)
    • 記事本文(RSS から取得した本文 / 数百〜数千トークン)
  • 出力: JSON 形式の採点結果(重要度・カテゴリ・要約 3 行)

採点ルール定義は全記事共通で固定。記事本文だけが毎回変わります。この構造を見たときに「prefix を固定して cache できそう」と感じたのが、今回の整理を始めたきっかけです。

出典: Anthropic 公式ドキュメント Prompt caching(2026-05-18 確認)


設計判断 1:prefix を system に置くか messages[0] に置くか

Prompt Caching の cache_controltoolssystemmessages の階層順序で配置できます(公式ドキュメント)。逆順は不可です。

採点パイプラインのように「採点ルール(固定)+ 記事本文(毎回変わる)」という構造の場合、固定部分の置き場所として以下のどちらが妥当か、設計フェーズで一度立ち止まる必要があります。

配置 利点 欠点
system ブロック 「指示」と「データ」の責務分離が綺麗 / Web 検索・Citations 切り替えで system キャッシュは無効化されるが、その操作を採点パイプラインでは想定しないため影響は小さい システムプロンプトを変えると system キャッシュ全体が失効する。プロンプト・チューニングのイテレーションが遅い段階だとキャッシュ書き込みコスト(×1.25)が回収しにくい
messages[0] user ブロック 採点ルールも「データ」として 1 つの user ブロックにまとめると、role 設計と整合する場合がある tool_choice や画像追加で messages キャッシュ全体が失効する。Tool を使う設計だと無効化トリガーが増える

私は 採点ルールは system に置く 方向で整理しています。理由は、DAINews の採点パイプラインで Web 検索や Citations を切り替えるユースケースが想定にないこと、プロンプト・チューニングが落ち着いてから本番投入する想定なので「system 書き換えで全失効」のコストが現実的に許容できそうな見通しがあること、の 2 つです。

ただし、設計フェーズで Tool 定義の有無がまだ揺れているなら、後述の「キャッシュ無効化トリガー 7 種」をセットで決めてから判断したほうが安全です。

私の最終的な判断は、採点ルール定義は system ブロックに置き、Tool は使わず、採点結果の JSON Schema もシステムプロンプト内に文字列として埋め込む という構成です。

決め手は 2 つあって、1 つは dry-run スクリプト側で採点ルールを「base プロンプト + 分野別モジュール」のファイル連結(テキスト)で組み立てており、Tool 定義に昇格させる手間に対して得られる構造化のメリットが現時点で見えないこと。もう 1 つは、後述する「キャッシュ無効化トリガー 7 種」を見たときに、Tool 定義を 1 フィールド足すだけで tools / system / messages の全キャッシュが無効化されるペナルティが想像以上に強かったことです。

採点スキーマを Tool に昇格させるのは、ツール呼び出しの並行制御や複数ツールの選択分岐が必要になったタイミングまで保留します。現状は「system に固定ルール + JSON 出力指示」「user に記事本文」の 2 ブロック構成です。

コードに落とすと最小構成はこのような形になります(採点パイプラインの dry-run スクリプトから抜粋・整理)。

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const response = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  system: [
    {
      type: "text",
      text: scoringRulePrompt, // 採点ルール + JSON Schema(固定 / 数千トークン)
      cache_control: { type: "ephemeral" },
    },
  ],
  messages: [
    { role: "user", content: articleBody }, // 記事本文(毎回変わる)
  ],
});

console.log({
  cache_creation: response.usage.cache_creation_input_tokens,
  cache_read: response.usage.cache_read_input_tokens,
  input: response.usage.input_tokens,
});

ポイントは system を文字列ではなく 配列で渡し、各ブロックに cache_control を付けることです。文字列で渡すと cache_control を付ける場所がそもそも存在しないので、cache_creation_input_tokens は 0 のまま silent failure します(次節の設計判断 2 と連動します)。


設計判断 2:モデル別「最小トークン数」を境界条件として持つ

ここが個人開発者には地味につらいポイントでした。Prompt Caching には モデル別に「これ以上のトークン数がないとキャッシュされない」最小値 があり、しかも 満たない場合はエラーが返らず cache_creation_input_tokens: 0 で silent failure します。

公式ドキュメントによる現行モデルの最小トークン数(2026-05-18 確認):

  • Claude Opus 4.7 / 4.6 / 4.5: 4,096 トークン
  • Claude Sonnet 4.6 / 4.5: 1,024 トークン
  • Claude Haiku 4.5: 4,096 トークン

DAINews は Sonnet 4.6 を使う想定なので 1,024 トークンの閾値で済みますが、「コストを下げたいから Haiku に切り替える」と判断した瞬間に閾値が 4 倍 になります。採点ルール定義が 2,000 トークンしかなければ、Haiku への切り替えで cache が全く効かなくなる、というシナリオが起こります。

設計判断としては、本番投入前のチェックリストに「採点ルール定義の prefix 部分の総トークン数」を計測し、モデル切替の閾値を 設計ドキュメントに数字で書いておく ことにしました。あわせて、silent failure を運用ログで拾うための最小チェックも採点パイプラインに入れています。

// 注: 公式の最小トークン数は 2026-05-18 時点の値。仕様変更で動くため、
// デプロイ前に Anthropic 公式ドキュメントの cache-limitations セクションで再確認すること。
// 旧 Opus 4.1(1,024)など、startsWith では拾いきれない例外モデルもあるため、
// 本番では明示的なモデル名マップに切り替えるのが安全。
const minimumCacheTokens =
  model.startsWith("claude-opus") || model.startsWith("claude-haiku")
    ? 4096
    : 1024;

if (
  response.usage.cache_creation_input_tokens === 0 &&
  response.usage.cache_read_input_tokens === 0
) {
  console.warn(
    `[CACHE WARN] No cache activity. model=${model}, min=${minimumCacheTokens}. prefix トークン数と cache_control 配置を確認してください。`
  );
}

cache_creationcache_read の両方が 0 なら、「書きにも行っていないし読みにも行っていない」 = 最小トークン数を割っているか、cache_control の配置が間違っているかのどちらかです。エラーにならないので、運用ログに警告を出す側で気づくしかありません。

モデル別の最小トークン数は今後の仕様変更で動く可能性があるため、Anthropic 公式ドキュメントを デプロイ前にもう一度確認する 運用を入れています。旧 Sonnet 3.7 時代の数値(1,024)を引用している記事もまだネット上に多く、参考にする場合は公開日と対象モデルを必ず確認してください。


設計判断 3:2026-02-05 のワークスペース分離をどう吸収するか

これはドキュメントの一番下の Changelog に小さく書かれていて、最初に気づいたとき「これは本番設計を変えなきゃまずいやつでは」となった項目です。

2026-02-05 の仕様変更で、キャッシュのスコープが 組織レベル → ワークスペースレベル に変わりました。同じ Anthropic アカウントでも、API キー(ワークスペース)が違えばキャッシュは共有されません。

DAINews 単独のときは関係ない話に見えますが、私は以下のような構成を考えています。

  • 開発ワークスペース: ローカル dry-run / バッチ採点の試験実行
  • 本番ワークスペース: 本番採点パイプライン
  • 個人検証ワークスペース: 自分の Qiita 記事下書きや個人のプロンプト遊び(既存)

この 3 つで API キーを分けると、開発で温めたキャッシュは本番では使えません。逆に、本番投入後の安定運用のために「キャッシュを温める専用のワークスペースに本番採点も寄せる」設計 が必要になる、ということです。

設計判断としては:

  • 開発ワークスペースで cache 効果を検証 → 本番ワークスペースで「キャッシュを書く側」と「読む側」のリクエストを 同一ワークスペースに閉じる
  • バッチ採点を並列化する場合、最初の 1 件で cache を書ききるまで他のリクエストは cache miss する(公式に明記)ため、バッチ全体を直列の「先頭ウォームアップ + 並列実行」構成にする

設計フェーズで一番悩んだのが、「DAINews 専用の本番 API キーを発行するタイミング」 でした。本記事を書いている時点で、私はまだ DAINews 用の専用 API キーを発行しておらず、個人の検証ワークスペースで dry-run を回しています。

2026-02-05 のスコープ変更で、ワークスペースを分けた瞬間にキャッシュは引き継がれません。つまり「今 dry-run で温めているキャッシュは、本番ワークスペース移行時に全部捨てる」ことを前提に設計を組み立てます。

最終的に倒した方針は以下の 3 点です。

  • 本番ワークスペースは DAINews 専用 API キーで切り分ける(コスト計測の独立性 + 個人検証との誤読防止)
  • dry-run と本番のキャッシュ共有は最初から諦める(捨てる前提)
  • 本番側では、配信バッチの 先頭 1 件で system プロンプトのキャッシュを書き、残り N-1 件をキャッシュ読み込みする直列ウォームアップ構成にする(並列化は cache miss の損益分岐を見てから判断)

このうち専用 API キーの発行は、記事公開予定の 5/21 より少し先(5/24)に動かすので、本番投入前の最後の TODO として手元に残しています。

「先頭ウォームアップ + 並列実行」の構成を擬似コードに落とすと、こんな形になります。

// 採点バッチを「先頭 1 件で cache write、残り N-1 件並列で cache read」する構成
async function scoreBatchWithWarmup(articles: Article[], scoringRulePrompt: string) {
  if (articles.length === 0) return [];

  const systemBlock = [
    {
      type: "text" as const,
      text: scoringRulePrompt,
      cache_control: { type: "ephemeral" as const },
    },
  ];

  // 1. 先頭 1 件で system プロンプトのキャッシュを書く(書き込み課金 ×1.25)
  const firstResult = await client.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 1024,
    system: systemBlock,
    messages: [{ role: "user", content: articles[0].body }],
  });

  // 2. 残り N-1 件は並列実行(cache read = 通常入力の 10%)
  const restResults = await Promise.all(
    articles.slice(1).map((article) =>
      client.messages.create({
        model: "claude-sonnet-4-6",
        max_tokens: 1024,
        system: systemBlock,
        messages: [{ role: "user", content: article.body }],
      })
    )
  );

  return [firstResult, ...restResults];
}

ポイントは、先頭 1 件を await で待ち切ることです。並列で同時起動すると、複数リクエストが同じ system プロンプトに対して全部「cache write」扱いになり、書き込み課金 ×1.25 を N 回払うことになります。1 件だけ書ききってから残りを並列に開けば、書き込みは 1 回 / 読み込みは N-1 回になり、損益分岐が一気に良くなります。


設計判断 4:ブレイクポイント 4 つをどこに置くか

cache_control のブレイクポイント上限は リクエストあたり最大 4 つ(自動キャッシング使用時は 1 スロット消費)です。4 つも置けるなら十分そうに見えますが、採点パイプラインで「分割しすぎ」「分割しなさすぎ」のどちらに倒すかは検討の余地があります。

採点ルール定義を「ツール定義 / システムプロンプト / 採点スキーマ / Few-shot 例」の 4 つに分けて全部にブレイクポイントを置く構成も理屈上は可能ですが、Lookback Window(20 ブロック)の制約や、書き込み×1.25 のコストを 4 回払うことになる損益分岐が悪化するため、「ほぼ確実に変わらない 1〜2 箇所に絞る」 のが現実的だと整理しました。

ここでの「1 ブロック」とは tools 配列の各ツール定義 / system 配列の各テキストブロック / messages 配列の各 content ブロック(テキスト・画像・tool_use・tool_result)を 1 つずつ数えたものです。Lookback Window はブレイクポイント位置から 直前 20 ブロック以内 にしか過去のキャッシュエントリを遡らないため、長い会話・大きなプロンプトでは複数のブレイクポイントを置かないと cache hit がつながらなくなります(公式ドキュメント "Example: Lookback in a growing conversation" 参照)。

DAINews の採点パイプラインでは、最低限以下の 2 つにブレイクポイントを置く構成を初期案にしています。

  1. システムプロンプト + 採点スキーマ(最も変わりにくい)の末尾に 1 つ
  2. (Few-shot 例を入れる場合)Few-shot 例ブロックの末尾に 1 つ

残り 2 つは、本番運用で「Tool 定義を別ブロックで切るべきか」「記事本文の前にもう 1 つ置くべきか」を運用データを見てから決める運用予備にする方針です。


設計判断 5:キャッシュ無効化トリガー 7 種をデプロイチェックリストに入れる

公式ドキュメントの「Cache invalidation」表は、設計フェーズで一度紙に書き写しておく価値があります。要点だけ抜き出すと以下のとおりです。

変更内容 Tools cache System cache Messages cache
Tool 定義変更 無効 無効 無効
Web 検索 / Citations の ON/OFF 維持 無効 無効
tool_choice 変更 維持 維持 無効
画像追加・削除 維持 維持 無効
Thinking 設定変更 維持 維持 無効

DAINews への当てはめで気になっているのは、採点スキーマを Tool 定義として渡すか、システムプロンプト内の JSON スキーマとして渡すか で、キャッシュの壊れ方が大きく変わるという点です。

採点スキーマを Tool として渡す設計だと、「スキーマを 1 フィールド足す」だけで Tool 定義変更扱いになり、tools / system / messages のキャッシュ全部が無効化されます。一方でシステムプロンプト内に JSON スキーマを書く設計なら、その変更は system キャッシュだけの失効で済みます(messages キャッシュは維持される)。

出典: Anthropic 公式ドキュメント Prompt caching - What invalidates the cache(2026-05-18 確認)

設計判断としては、採点スキーマは 当面システムプロンプトに JSON Schema として埋め込む方針 で進めようとしています。Tool 定義に昇格させるのは、ツール呼び出しの並行制御が必要になったタイミングまで保留します。


設計判断 6:第三者著作物(RSS 本文)をキャッシュに乗せるかは法務論点として保留

ここが、技術的判断とは別の軸で残している論点です。

DAINews が扱う記事本文は、各ニュースメディアの著作物です。Prompt Caching の Extended TTL(1 時間)を使う場合、その本文が Anthropic のインフラ上に 最大 1 時間保存 されます。

公式の利用規約とプライバシーポリシーを読んだ範囲では、「キャッシュに乗せる第三者著作物の扱い」について明示的な条項は確認できませんでした(2026-05-18 確認)。著作権法上の整理としては以下のような論点が残ります。

  • 47 条の 5(情報解析の規定)が Prompt Cache の保存を許容する範囲か
  • 47 条の 4(処理目的の一時的複製)の「一時的」が最大 1 時間 TTL を含むか
  • 32 条(引用)の主従関係を採点パイプラインで満たせるか
  • Anthropic 側を「処理委託先」と整理した場合の責任分界

設計パターン候補としては:

  • パターン A: 本文を system prompt に含めて全文キャッシュ(コスト最小・著作権リスク最大)
  • パターン B: 見出し + URL のみキャッシュ、本文は user message として都度送信
  • パターン C: 全文キャッシュなし、毎回フル送信(コスト最大・リスク最小)

技術的にはパターン A が最も合理的ですが、第三者著作物が絡む以上、ここは法務確認なしでは選び切れない論点だと考えています。私自身も自分のサービスに導入する際は、契約・著作権の専門家に相談したうえでパターンを確定する予定です。

本記事では「RSS 本文を Prompt Cache に乗せて問題ない」とは断定しません。同じように第三者著作物を扱う SaaS で Prompt Caching を検討している方は、契約・著作権の論点を技術設計と並走させて整理する ことをおすすめします。


個人開発者なら、ここまで考えなくていい線引き

ここまで 6 つの設計判断を書いてきましたが、個人開発で小規模に検証するだけなら、ここまで全部を詰める必要はありません。読者層ごとに、どこまで踏み込むべきかの目安を置いておきます。

  • プロンプトが最小トークン数を割っている試作段階(Sonnet 系で 1,024 / Opus・Haiku 系で 4,096 未満)→ そもそも Prompt Caching が効かないので、まずは設計判断 2 だけ意識して、それ以外は無視して OK
  • 1 日数件しか叩かないバッチ・個人のチャット用途 → TTL 5 分の標準キャッシュも持続しないので、最初は cache_control 抜きで素直に書いてからコストを実測する方が早い
  • 第三者著作物を扱わない(自分のドキュメント・自分のコードのみキャッシュ)→ 設計判断 6 はスキップして OK

「採点・配信のような 同じ prefix を 1 日数百〜数千回叩くパイプライン」に Prompt Caching を入れる段階で、初めて本記事の 6 つを順番に潰す価値が出てきます。DAINews がちょうどそのライン上にあったので、私は今のタイミングで設計判断を整理しました。


おわりに:設計フェーズの落とし穴チェックリスト

DAINews の本番投入は、上記 6 つの設計判断を以下の順で潰してから着手することにしました。

  1. prefix を system に置く方針で確定(設計判断 1)
  2. 採点ルール定義の prefix トークン数を計測し、モデル切替閾値を設計ドキュメントに記載(設計判断 2)
  3. dry-run と本番のワークスペース分離方針を確定(設計判断 3)
  4. ブレイクポイントは初期 2 つに限定、残り 2 つは運用後に判断(設計判断 4)
  5. 採点スキーマは Tool 定義ではなくシステムプロンプト内 JSON Schema として持つ(設計判断 5)
  6. RSS 本文のキャッシュ可否は法務確認後に確定(設計判断 6)

次の検証課題として残っているのは、

  • 本番投入後の cache hit rate を採点バッチ単位で計測する仕組み(cache_creation_input_tokens / cache_read_input_tokens をログに残す設計)
  • 法務確認後のパターン確定と、それに伴うコスト試算の再計算

の 2 点です。法務確認の結果は、別記事 or 本記事への追記として後日共有する予定です。

「Prompt Caching を導入してコストを下げた」という記事は既に何本も存在します。一方で「設計フェーズで何を決めてから着手するか」のチェックリストはまだ世の中に少ない印象なので、これから着手する方の整理ノートとして使ってもらえれば嬉しいです。


参考リンク

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?