長いシステムプロンプト — RAG コンテキスト、ツールカタログ、エージェントの ルール、例 — を送っているなら、おそらく呼び出しのたびに全額の入力料金を 払っています。Anthropic のプロンプトキャッシュは、キャッシュされた部分に ついてそれを レートの 10% まで下げます。OpenAI も暗黙的に同じことを やっています。多くのチームが 30 分の作業に見合うと判断するのは、入力 コストの行を確実に 60〜90% 削れるからです。
Brievio は両方の流儀を改変せず、本物のモデルに対して素通しします。 Anthropic スタイルの cache_control は Messages API で動き、OpenAI スタイルの 自動キャッシュは Chat Completions API で動きます。この記事では両方、 キャッシュを静かに無効化する落とし穴、そしてヒット率の検証方法を順に 説明します。
ビフォー・アフター
同じ 18K トークンのシステムプロンプトを 10 回送る素朴なループ:
# 多くの人が最初に書く形:呼び出しごとにプロンプト全体を再課金している。
import anthropic
client = anthropic.Anthropic(
api_key="sk-brievio-...",
base_url="https://api.brievio.com",
)
SYSTEM = open("system-prompt.md").read() # 18,000 トークンのルール + 例
# 1 セッションでユーザーから 10 個の質問。毎回 18K トークンのシステムブロックを送る。
for question in questions:
resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=600,
system=SYSTEM,
messages=[{"role": "user", "content": question}],
)
# 1 回あたりのコスト(入力のみ): 18,000 × $3 / 1M = $0.054
# 10 回: 入力だけで $0.54。では、システムブロックをキャッシュ可能と印付けする — フィールドを 1 つ足すだけ:
# 修正方法:静的なプレフィックスをキャッシュ可能と印付けする。最初の呼び出しの後、
# 約 5 分以内の後続呼び出しは、キャッシュされた部分について入力レートの 10% を払う。
# 同じ答え、89% 安い。
resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=600,
system=[
{
"type": "text",
"text": SYSTEM,
"cache_control": {"type": "ephemeral"}, # キャッシュ可能と印付け
}
],
messages=[{"role": "user", "content": question}],
)
# 1 回目(キャッシュ書き込み): 18,000 × $3 / 1M = $0.054
# 2〜10 回目(キャッシュヒット): 18,000 × $0.30 / 1M = 各 $0.0054
# 合計: $0.054 + 9 × $0.0054 = $0.103 (以前は $0.54 — 81% 節約)これでこのセッションの入力コストを 81% 節約しました。最初の呼び出しは 実は素朴な版よりわずかに高くつきます(キャッシュを 書き込むため入力レートの約 1.25 倍)。2〜10 回目はレートの 10% です。 損益分岐点は 2 回目 — 3 回目には先行し、10 回目には圧勝です。
OpenAI スタイル:何もする必要なし
OpenAI SDK を使っているなら、Chat Completions は 1,024 トークンを超える プロンプトのプレフィックスを自動的にキャッシュします。フラグも 設定も不要です。Brievio の背後にある同じモデルは、Anthropic ルートを 使っても OpenAI ルートを使っても割引を受けます:
# OpenAI Chat Completions スタイル — 1024 トークン以上のプロンプトはキャッシュが自動。
# 設定するフラグはない。"usage" オブジェクトが何がキャッシュされたかを教えてくれる。
from openai import OpenAI
client = OpenAI(
api_key="sk-brievio-...",
base_url="https://api.brievio.com/v1",
)
resp = client.chat.completions.create(
model="claude-sonnet-4-6",
messages=[
{"role": "system", "content": LONG_SYSTEM_PROMPT}, # >1024 トークン
{"role": "user", "content": "Latest question…"},
],
)
# レスポンスの中で:
# resp.usage.prompt_tokens_details.cached_tokens → 17,800
# resp.usage.prompt_tokens → 18,200
# 17.8K/18.2K = 入力の 98% がキャッシュ由来。請求はそれを自動で反映する。usage.prompt_tokens_details.cached_tokens を読めば、どれだけが キャッシュから提供されたか分かります。固定プレフィックスが大きいほど 節約も大きい。経験則:システムプロンプトが可変のユーザーコンテンツより 短いなら、キャッシュの効果はあまりありません。静的な部分が大きく、 先頭に来るようにプロンプトを組み直しましょう。
多層キャッシュ — 最大 4 ブレークポイント
一部のレイヤーが他より速く変わるエージェントループでは、複数の cache_control ブレークポイントを設定します。それぞれが そこまでのすべてのスナップショットです:
# Anthropic はリクエストごとに最大 4 つのキャッシュブレークポイントをサポートする —
# 後半のレイヤーが変わってもキャッシュを温かいまま保つために使おう。
client.messages.create(
model="claude-sonnet-4-6",
max_tokens=600,
system=[
{"type": "text",
"text": ROLE_AND_RULES, # 約 3,000 トークン
"cache_control": {"type": "ephemeral"}}, # ブレークポイント 1
{"type": "text",
"text": LARGE_KNOWLEDGE_BASE, # 約 15,000 トークン、めったに変わらない
"cache_control": {"type": "ephemeral"}}, # ブレークポイント 2
],
messages=[
{"role": "user",
"content": [
{"type": "text",
"text": CONVERSATION_HISTORY, # ターンごとに増えていく
"cache_control": {"type": "ephemeral"}}, # ブレークポイント 3
{"type": "text", "text": new_question},
]},
],
)
# CONVERSATION_HISTORY が変わっても、ブレークポイント 1+2 はキャッシュにヒットする。
# 全額の入力レートを払うのはブレークポイント 3 + 新しい質問だけ。キャッシュキーはプレフィックス全体です。位置 N にトークンを 1 つ足すと、 N 以降のすべてのブレークポイントが無効になります。順序が重要です: 最も安定したコンテンツを先頭に。ルールのレイヤーはめったに 変わらないはず。ナレッジベースは毎週更新され、会話はターンごとに 増えていきます。
キャッシュを静かに壊すよくあるやり方
- プロンプトに現在の日付や request_id を入れる。 毎回が新しいプレフィックスになり、キャッシュヒット率は 0% です。 プロンプトの入力をハッシュ化し、呼び出し間で比較しましょう。
- 非決定的なシステムプロンプトの組み立て。 システムを dict から組み立てている場合、一部の Python バージョンでは dict の 反復順序が影響します。キーを明示的にソートしましょう。
- キャッシュの寿命は約 5 分。 まばらなトラフィック パターン(10 分に 1 回の呼び出し)ではヒットがゼロになります。 呼び出しをまとめるか、その損失を受け入れるかです。
- 1,024 トークンの最小値。 1K トークン未満では OpenAI スタイルのキャッシュは作動しません。小さな静的フラグメントを 1 つの 長いプレフィックスにまとめましょう。
- ツール / 関数定義はプレフィックスの一部。 カタログに新しいツールを追加すると、全員のキャッシュが無効になります。 ツールカタログを安定させ、バージョン管理しましょう。
ヒット率を検証する
見えないキャッシュはエンジニアリングではありません — ただの願望です。 呼び出しごとに usage をログに記録しましょう:
# 必ず usage を読む。ヒットを期待したのに cached_tokens が 0 なら、
# どこかがおかしい — たいていは非決定的なプレフィックスが原因。
resp = client.messages.create(...)
u = resp.usage
print({
"input_uncached": u.input_tokens,
"input_cache_read": u.cache_read_input_tokens,
"input_cache_write": u.cache_creation_input_tokens,
"output": u.output_tokens,
})
# よくある落とし穴:システムプロンプトに今日の日付や request_id を入れると、
# 静かにキャッシュが無効化される。入力をハッシュ化し、2 回目の同一呼び出しで
# cache_read_input_tokens が 0 でないことを検証しよう。Brievio のダッシュボードでは、 使用状況ページがモデルごと・日ごとの キャッシュ内訳を表示します。cache_read_input_tokens が 総入力に占める割合として増えていれば、キャッシュは機能しています。 0 のまま、あるいは激しく変動するなら、上の落とし穴リストを順に 確認してください。
Brievio では実際いくらかかるのか
各モデルのキャッシュレートは 料金ページに掲載されています:
- Anthropic モデル:キャッシュ読み取りは入力レートの 10%。キャッシュ書き込みは入力レートで課金され、上流の上乗せは ありません。
- OpenAI / Gemini モデル:キャッシュ読み取りは入力 レートの 20%(各プロバイダーが公表している比率)です。
- すべてのキャッシュ料金は Brievio の定価 — プロバイダーの 公式レートより約 15% 安い — を反映します。 したがって Brievio での Sonnet 4.6 のキャッシュ読み取りは
$3 × 0.95 × 0.10 = $0.285 per 1M tokensです。 キャッシュなしの入力レートの約 10 分の 1 です。
キャッシュが答えではないとき
作業に見合わないケースがいくつかあります:
- 短いプロンプト(合計 <1K トークン)。オーバーヘッドが 支配的になるので、わざわざやる必要はありません。
- 繰り返しトラフィックのない単発タスク。 最初の 呼び出しはわずかに高くつき、キャッシュは 2 回目以降でしか元が 取れません。
- 出力が多く入力が少ないタスク(創作、コード生成)。 入力はすでに請求のごく一部です。代わりに出力予算の上限に注力 しましょう。
それ以外のすべて — RAG チャットボット、固定ツールカタログを持つ エージェント、静的なルーブリックで動く分類器、一貫した few-shot を持つ 構造化抽出パイプライン — では、キャッシュは 1 つの午後で出荷できる 最も ROI の高い最適化です。これを コスト最適化プレイブックの他の 4 つの手法 と組み合わせれば、出力品質を一切犠牲にせず 70% のコスト削減が 現実的になります。
すでに Brievio をお使いですか? /app/usage を開いて、最大のモデルの キャッシュ列を確認してください。ゼロなら、お金をテーブルに置き去りに しています。完全ガイド: /docs/caching。