- AI ⚙️
LLM APIのコスト削減方法|キャッシュとバッチの仕組みと計算例
LLM API のコスト削減とは、送る内容と使い方を変えずに「同じ入力を安く読ませる(プロンプトキャッシュ)」「急がない処理をまとめて安く流す(バッチ)」「出力を短くする」の3つを組み合わせて、同じ仕事にかかるトークンの費用を下げることです。 どれを使うかは、前置きが毎回同じかどうかと、返事をどれだけ待てるかで決まります。
この記事では、金額ではなく「通常の入力を1としたときの倍率」で仕組みを説明します。倍率で考えると、モデルや単価が変わっても同じ式で判断できます。キャッシュの書き込み割増を何回で取り戻せるかの式と計算例、Anthropic と OpenAI のキャッシュの違いの対応表、応答の usage から効き具合を確かめるコード、バッチの投入から結果取得までのコード、出力トークンを減らす指定の一覧を載せます。
確認環境:Linux、Python 3.11.15、anthropic 1.11.0、openai 3.24.0(Python SDK)。確認日は 2026-10-06 です。計算例は Python で実行しました。API を呼ぶコードは、各社の応答と同じ形を返す手元の模擬サーバーに SDK を向けて動かしており、出力例のトークン数は模擬サーバーが返した説明用の値です。倍率・上限・有効期間などの仕様は、2026-10-06 に開いた各社の公式ドキュメントの記載です。
この記事の結論
| 条件 | 選ぶもの |
|---|---|
| 同じ前置き(長い指示、資料、ツール定義)を短い間隔で繰り返し送る | プロンプトキャッシュ |
| 返事を急がない処理が大量にある(数時間〜1日待てる) | バッチ |
| 両方に当てはまる(同じ資料に対する大量の質問を、急がず処理する) | 併用 |
| 前置きは毎回違い、すぐ返事が要る | 出力を短くする・送る内容を絞る |
- キャッシュは「書き込みが少し高く、読み取りが大幅に安い」仕組み。 書き込み1.25倍・読み取り0.1倍なら、1回読めば取り戻せます。
- バッチは入力も出力も半分の倍率になる代わりに、結果が後から届く仕組み。
- 併用は、キャッシュが読めたときに最も安くなります。読めるかどうかは保証されないため、効き具合を usage で確かめます。
コスト削減方法の早見表
通常の入力トークンを 1 としたときの倍率です(各社公式ドキュメントの記載、2026-10-06 確認)。
| 方法 | 入力の倍率 | 出力の倍率 | 返事の速さ | 向く使い方 |
|---|---|---|---|---|
| そのまま送る | 1 | 変わらない | すぐ | 前置きが毎回違う、単発の質問 |
| プロンプトキャッシュ(読み取り) | 0.1(キャッシュから読めた部分) | 変わらない | すぐ | 同じ前置きを繰り返し送る対話・検索つき応答 |
| プロンプトキャッシュ(書き込み) | Anthropic:1.25(5分)/2(1時間)。OpenAI:モデルの世代により割増なし、または1.25 | 変わらない | すぐ | 上と同じ(最初の1回にかかる) |
| バッチ | 0.5 | 0.5 | 24時間以内(多くはもっと早い) | 分類・要約・評価などの大量処理 |
| キャッシュ+バッチ | 読めた部分は 0.1 × 0.5 | 0.5 | 24時間以内 | 同じ資料に対する大量の質問 |
| 出力を短くする | 変わらない | トークン数そのものが減る | むしろ速くなる | すべての使い方 |
読み取りの倍率は、一部のモデルで 0.1 より小さい値が案内されています。使うモデルの値は公式の料金ページで確かめてください。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「LLM APIのコスト削減方法|キャッシュとバッチの仕組みと計算例」
プロンプトキャッシュの仕組み
プロンプトキャッシュは、リクエストの先頭から同じ内容が続く部分(接頭辞)を API 側が覚えておき、次に同じ接頭辞が来たら安い倍率で読む仕組みです。答えそのものを保存するわけではないので、同じ前置きに違う質問を続けても、毎回新しい答えが生成されます。
1回目 [ 長い前置き(規程集など) ][ 質問A ] 前置きを「書き込み」(少し高い)
2回目 [ 長い前置き(規程集など) ][ 質問B ] 前置きを「読み取り」(大幅に安い)
3回目 [ 日付 ][ 長い前置き ][ 質問C ] 先頭が変わったので書き込み直し
要点は3つです。
- 先頭から一致している部分だけが対象。 途中に1文字でも違いがあると、そこから後ろは読めません。
- 変わらないものを前、変わるものを後ろに置く。 日付・利用者名・質問文などは末尾に寄せます。
- 有効期間がある。 期間内に次のリクエストが来なければ、次は書き込みからやり直しです。
何回読めば元が取れるか(倍率だけの式)
書き込みの倍率を w、読み取りの倍率を r とします。同じ前置きを1回書き込み、そのあと n 回読むときの費用と、キャッシュを使わずに n+1 回送る費用を比べます。
キャッシュあり: w + r × n
キャッシュなし: 1 + n
w + r × n < 1 + n を n について解くと
n > (w − 1) ÷ (1 − r)
前置きのトークン数は両辺に同じだけ掛かるので、式から消えます。必要な読み取り回数は倍率だけで決まります。
import math
def min_reads(write, read):
"""書き込みの割増分を取り戻すのに必要な読み取り回数(通常入力を1とした倍率で計算)"""
need = (write - 1) / (1 - read)
return need, math.floor(need) + 1
def ratio(write, read, reads):
"""同じ前置きを 1回書き込み + reads回読み取り したときの、キャッシュ無しに対する比"""
return (write + read * reads) / (1 + reads)
print("書き込み 読み取り 必要な読み取り回数")
for write, read in [(1.25, 0.1), (2.0, 0.1), (1.0, 0.1)]:
need, n = min_reads(write, read)
print(f" {write:<5} {read:<5} {need:.3f} を超える回数 -> {n}回")
print()
print("読み取り回数 5分(1.25/0.1) 1時間(2.0/0.1)")
for reads in [0, 1, 2, 3, 5, 10, 50]:
print(f" {reads:>3} {ratio(1.25, 0.1, reads):.3f} {ratio(2.0, 0.1, reads):.3f}")
$ python3 breakeven.py
書き込み 読み取り 必要な読み取り回数
1.25 0.1 0.278 を超える回数 -> 1回
2.0 0.1 1.111 を超える回数 -> 2回
1.0 0.1 0.000 を超える回数 -> 1回
読み取り回数 5分(1.25/0.1) 1時間(2.0/0.1)
0 1.250 2.000
1 0.675 1.050
2 0.483 0.733
3 0.388 0.575
5 0.292 0.417
10 0.205 0.273
50 0.123 0.137
読み方です。
- 書き込み1.25倍・読み取り0.1倍なら、1回読めば取り戻せます。 2回送るだけで、そのまま送る場合の 67.5% になります。
- 書き込み2倍(1時間の有効期間)なら、2回読む必要があります。 1回しか読めないと 1.050 で、わずかに割高です。
- 書き込みに割増が無い場合は、読めた時点から得です。
- 読み取りが増えるほど、比は読み取りの倍率 0.1 に近づきます。50回で 0.123 です。
- 逆に、一度も読まれない前置きにキャッシュを指定すると、割増分だけ高くなります(読み取り0回の行)。
AnthropicとOpenAIのキャッシュの違い(対応表)
2026-10-06 に開いた両社の公式ドキュメントの記載を並べます。値はモデルにより変わるものがあるため、利用するモデルの行を公式ページで確かめてください。
| 項目 | Anthropic(Messages API) | OpenAI(Responses API / Chat Completions API) |
|---|---|---|
| 有効にする方法 | 明示指定。キャッシュしたい範囲の末尾のブロックに cache_control を付ける。リクエスト全体に cache_control を1つ付けて、区切りを自動で置かせる方法もある |
既定で有効。対応モデルでは指定なしで働く |
| 区切りの数 | ブロック単位の指定は1リクエストに最大4つ | 既定では指定不要(先頭からの一致を API 側が判定) |
| 最小トークン数 | モデルにより 512/1,024/2,048/4,096 のいずれか | 1,024 |
| 有効期間 | 既定は5分。"ttl": "1h" で1時間。読まれるたびに追加費用なしで更新される |
モデルの世代により異なる。メモリ上の保持(通常5〜10分、最長1時間)か prompt_cache_retention の "24h"、または prompt_cache_options の ttl(既定30分) |
| 書き込みの倍率 | 5分:1.25、1時間:2 | モデルの世代により、割増なし、または 1.25 |
| 読み取りの倍率 | 0.1(一部のモデルはさらに小さい) | 0.1(一部のモデルはさらに小さい) |
| 対象の順序 | tools → system → messages の順で接頭辞になる |
指示、ツール定義、会話履歴などを含む、組み立て後の入力全体 |
| usage の項目 | cache_creation_input_tokens(書き込み)、cache_read_input_tokens(読み取り)、input_tokens(最後の区切りより後ろ) |
input_tokens_details.cached_tokens(読み取り)、input_tokens_details.cache_write_tokens(書き込み) |
| 振り分けの補助 | なし(接頭辞の一致で判定) | prompt_cache_key で、同じ接頭辞を共有するリクエストをまとめられる |
usage の読み方には違いがあります。Anthropic の input_tokens は「最後の区切りより後ろの、キャッシュ対象外の部分」だけで、入力全体は3つの項目の合計です。OpenAI の input_tokens は入力全体で、cached_tokens はその内数です。
キャッシュが効かなくなる変更
Anthropic のドキュメントには、何を変えるとどの範囲が読めなくなるかの表があります。接頭辞の順序(tools → system → messages)のとおり、前にあるものを変えるほど広い範囲に及びます。
| 変えたもの | tools の範囲 | system の範囲 | messages の範囲 |
|---|---|---|---|
| ツール定義 | 読めなくなる | 読めなくなる | 読めなくなる |
| Web検索・引用・速度設定の切り替え | 読める | 読めなくなる | 読めなくなる |
tool_choice |
読める | 読める | 読めなくなる |
| 画像の追加・削除 | 読める | 読める | 読めなくなる |
| 思考(thinking)の設定 | モデルによる | モデルによる | 読めなくなる |
OpenAI のドキュメントでは、組み立て後の接頭辞全体が一致することが再利用の条件で、区切りより前の内容や関係する設定が変わると、その先は一致しないと説明されています。どちらの API でも、対策は同じです。
| よくある原因 | 直し方 |
|---|---|
| 先頭に日付・時刻・リクエストIDを入れている | 末尾(質問の側)に移す |
| ツール定義の並びが毎回変わる(辞書の順序など) | 名前順などで並びを固定する |
| 前置きが最小トークン数に届いていない | キャッシュの対象にしない(効果が出る長さの前置きにだけ使う) |
| リクエストの間隔が有効期間より長い | 長い有効期間を選ぶ、または処理をまとめて流す |
| 同じ前置きのリクエストを同時に並行して送っている | 最初の1件の応答が始まってから残りを送る(Anthropic のドキュメントに、キャッシュは最初の応答が始まってから使えるようになるとの記載がある) |
| 利用者ごとに前置きの文面が少しずつ違う | 共通部分を前、利用者ごとの部分を後ろに分ける |
usage でキャッシュの効き具合を確かめるコード
設定したつもりでも、読めているかどうかは usage を見るまで分かりません。次のコードは、同じ前置きで質問を変えて送り、書き込み・読み取り・通常入力のトークン数と読み取り率を出します。manual.txt は毎回送る長い前置き(ここでは架空の備品予約システムの利用規程)です。
Anthropic
import anthropic
client = anthropic.Anthropic() # 環境変数 ANTHROPIC_API_KEY を使う
MODEL = "your-model-name" # 利用するモデル名に置き換える
MANUAL = open("manual.txt", encoding="utf-8").read() # 毎回同じ長い前置き
def ask(question, manual=MANUAL):
res = client.messages.create(
model=MODEL,
max_tokens=300,
system=[
{"type": "text", "text": manual,
"cache_control": {"type": "ephemeral"}}, # ここまでをキャッシュ
],
messages=[{"role": "user", "content": question}],
)
u = res.usage
total = u.input_tokens + u.cache_creation_input_tokens + u.cache_read_input_tokens
rate = u.cache_read_input_tokens / total
print(f"書き込み={u.cache_creation_input_tokens:>5} "
f"読み取り={u.cache_read_input_tokens:>5} "
f"通常入力={u.input_tokens:>5} 読み取り率={rate:.1%}")
ask("備品の予約は何日前からできますか") # 1回目:書き込み
ask("予約を取り消せる期限はいつですか") # 2回目:読み取り
ask("延長は何回までできますか") # 3回目:読み取り
ask("会議室Bの定員は", "本日の日付: 10/6\n" + MANUAL) # 先頭を変えると書き込み直し
ask("会議室Bの定員は", MANUAL[:500]) # 最小トークン数に届かない前置き
模擬サーバーに向けて実行した出力です(1文字を1トークンとして数える模擬で、最小トークン数は 1,024 としています)。
$ python3 cache_check_anthropic.py
書き込み= 2650 読み取り= 0 通常入力= 16 読み取り率=0.0%
書き込み= 0 読み取り= 2650 通常入力= 16 読み取り率=99.4%
書き込み= 0 読み取り= 2650 通常入力= 12 読み取り率=99.5%
書き込み= 2662 読み取り= 0 通常入力= 8 読み取り率=0.0%
書き込み= 0 読み取り= 0 通常入力= 508 読み取り率=0.0%
| 行 | 起きたこと | 見るところ |
|---|---|---|
| 1回目 | 前置きを書き込んだ | 書き込みが前置きの大きさ、読み取りは 0 |
| 2・3回目 | 同じ前置きを読んだ | 書き込み 0、読み取りが前置きの大きさ |
| 4回目 | 先頭に日付を足した | 接頭辞が変わり、もう一度書き込みになった |
| 5回目 | 前置きが短い | 書き込みも読み取りも 0。エラーにはならず、通常入力として数えられる |
書き込みも読み取りも 0 のまま、という表示が「効いていない」合図です。5回目のように最小トークン数に届かないときも、4回目のように先頭が毎回変わるときも、リクエスト自体は通常どおり返ってくるので、usage を記録して初めて気づけます。
OpenAI
from openai import OpenAI
client = OpenAI() # 環境変数 OPENAI_API_KEY を使う
MODEL = "your-model-name" # 利用するモデル名に置き換える
MANUAL = open("manual.txt", encoding="utf-8").read() # 毎回同じ長い前置き
def ask(question):
res = client.responses.create(
model=MODEL,
instructions=MANUAL, # 変わらない部分を先頭に置く
input=question, # 変わる部分は後ろに置く
max_output_tokens=300,
)
u = res.usage
cached = u.input_tokens_details.cached_tokens
print(f"入力={u.input_tokens:>5} うちキャッシュ={cached:>5} "
f"読み取り率={cached / u.input_tokens:.1%}")
ask("備品の予約は何日前からできますか")
ask("予約を取り消せる期限はいつですか")
$ python3 cache_check_openai.py
入力= 2668 うちキャッシュ= 0 読み取り率=0.0%
入力= 2668 うちキャッシュ= 2650 読み取り率=99.3%
指定は不要で、2回目に cached_tokens が入ります。日々の運用では、この読み取り率をログに出しておき、下がった日に「前置きを変えたか、間隔が空いたか」を調べる使い方が向いています。
バッチの仕組みと、投入から結果取得までのコード
バッチは、たくさんのリクエストを1つにまとめて預け、あとで結果をまとめて受け取る方式です。すぐには返ってこない代わりに、入力・出力とも倍率が 0.5 になります。
| 項目 | Anthropic(Message Batches) | OpenAI(Batch API) |
|---|---|---|
| 倍率 | 通常の 0.5 | 通常の 0.5 |
| 完了までの時間 | 多くは1時間未満。24時間で完了しない分は期限切れ | 24時間以内(completion_window="24h") |
| 1回の上限 | 10万リクエストまたは 256 MB の早く達したほう | 5万リクエスト、入力ファイル 200 MB |
| 投入の形 | リクエストの配列(custom_id と params) |
JSONL ファイルをアップロード(各行に custom_id・method・url・body) |
| 状態 | processing_status:in_progress → ended(取り消し中は canceling) |
status:validating・in_progress・finalizing・completed・failed・expired・cancelling・cancelled |
| 結果の種類 | 1件ごとに succeeded・errored・canceled・expired |
出力ファイル(output_file_id)と、エラー分のファイル(error_file_id) |
| 結果の保存期間 | 作成から29日間 | ―(公式ページで確かめてください) |
| 結果の並び | 投入順とは限らない。custom_id で対応づける |
投入順とは限らない。custom_id で対応づける |
Anthropic のドキュメントでは、errored・canceled・expired になったリクエストには費用がかからないと説明されています。
Anthropic:Message Batches
import time
import anthropic
client = anthropic.Anthropic()
MODEL = "your-model-name" # 利用するモデル名に置き換える
POLL_SECONDS = 60
questions = {
"q-001": "備品の予約は何日前からできますか",
"q-002": "予約を取り消せる期限はいつですか",
"q-003": "延長は何回までできますか",
}
# 1. 投入:custom_id で結果と突き合わせる
batch = client.messages.batches.create(
requests=[
{
"custom_id": cid,
"params": {
"model": MODEL,
"max_tokens": 300,
"messages": [{"role": "user", "content": q}],
},
}
for cid, q in questions.items()
]
)
print("投入:", batch.id, batch.processing_status)
# 2. 待つ:processing_status が ended になるまで
while True:
batch = client.messages.batches.retrieve(batch.id)
print("確認:", batch.processing_status)
if batch.processing_status == "ended":
break
time.sleep(POLL_SECONDS)
print("件数:", batch.request_counts)
# 3. 結果取得:並びは投入順とは限らないので custom_id で対応づける
answers = {}
for item in client.messages.batches.results(batch.id):
if item.result.type == "succeeded":
answers[item.custom_id] = item.result.message.content[0].text
else:
answers[item.custom_id] = f"[{item.result.type}]"
print("受信:", item.custom_id, item.result.type)
print()
for cid in questions:
print(cid, "->", answers[cid])
模擬サーバーに向け、待ち時間を短くして実行した出力です(模擬サーバーは、結果を逆順で返し、1件をエラーにする設定にしています)。
$ python3 batch_anthropic.py
投入: msgbatch_mock01 in_progress
確認: in_progress
確認: ended
件数: MessageBatchRequestCounts(canceled=0, errored=1, expired=0, processing=0, succeeded=2)
受信: q-003 errored
受信: q-002 succeeded
受信: q-001 succeeded
q-001 -> (q-001 への模擬の応答)
q-002 -> (q-002 への模擬の応答)
q-003 -> [errored]
結果は q-003、q-002、q-001 の順で届きましたが、custom_id をキーに辞書へ入れているので、最後の表示は投入順に戻せています。結果を配列の添字で対応づけると、順序が違ったときにずれるので、必ず custom_id を使います。errored になった q-003 は、custom_id を手がかりに内容を直して次のバッチに入れ直します。
OpenAI:Batch API
import json
import time
from openai import OpenAI
client = OpenAI()
MODEL = "your-model-name" # 利用するモデル名に置き換える
POLL_SECONDS = 60
questions = {
"q-001": "備品の予約は何日前からできますか",
"q-002": "予約を取り消せる期限はいつですか",
"q-003": "延長は何回までできますか",
}
# 1. 入力ファイル(JSONL)を作ってアップロード
with open("batchinput.jsonl", "w", encoding="utf-8") as f:
for cid, q in questions.items():
line = {
"custom_id": cid,
"method": "POST",
"url": "/v1/responses",
"body": {"model": MODEL, "input": q, "max_output_tokens": 300},
}
f.write(json.dumps(line, ensure_ascii=False) + "\n")
input_file = client.files.create(file=open("batchinput.jsonl", "rb"), purpose="batch")
# 2. 投入
batch = client.batches.create(
input_file_id=input_file.id,
endpoint="/v1/responses",
completion_window="24h",
)
print("投入:", batch.id, batch.status)
# 3. 待つ:completed になるまで(failed / expired / cancelled でも終わる)
while batch.status not in ("completed", "failed", "expired", "cancelled"):
time.sleep(POLL_SECONDS)
batch = client.batches.retrieve(batch.id)
print("確認:", batch.status)
# 4. 結果取得:出力ファイルの各行を custom_id で対応づける
answers = {}
for line in client.files.content(batch.output_file_id).text.splitlines():
row = json.loads(line)
answers[row["custom_id"]] = row["response"]["body"]["output"][0]["content"][0]["text"]
print("受信:", row["custom_id"], row["response"]["status_code"])
print()
for cid in questions:
print(cid, "->", answers[cid])
$ python3 batch_openai.py
投入: batch_mock01 validating
確認: in_progress
確認: completed
受信: q-003 200
受信: q-002 200
受信: q-001 200
q-001 -> (q-001 への模擬の応答)
q-002 -> (q-002 への模擬の応答)
q-003 -> (q-003 への模擬の応答)
OpenAI のバッチは、Responses API のほか、Chat Completions、Embeddings などのエンドポイントに対応しています。JSONL の url と、batches.create の endpoint を同じ値にそろえます。
キャッシュとバッチを併用したときの計算
Anthropic のドキュメントには、プロンプトキャッシュとバッチの割引は重ねて適用できること、ただしバッチの中でキャッシュが読めるかどうかは保証されない(リクエストが並行して処理されるため)こと、共通の前置きを持つバッチでは1時間の有効期間の利用が勧められていることが書かれています。
そこで、読めた割合を変えて計算します。単価は説明用の自作の値(通常入力1トークン=1、出力1トークン=5)で、1万トークンの前置き+200トークンの質問+300トークンの出力を100件処理する想定です。
# 説明用の単価:通常入力1トークン = 1、出力1トークン = 5 とした自作の値
IN, OUT = 1.0, 5.0
WRITE, READ = 1.25, 0.1 # キャッシュ書き込み・読み取りの倍率(5分の場合)
BATCH = 0.5 # バッチの倍率
PREFIX = 10_000 # 毎回同じ前置き(トークン)
VAR = 200 # 毎回変わる質問(トークン)
OUTPUT = 300 # 1件あたりの出力(トークン)
N = 100 # 件数
def cost(cache, batch, hit=1.0):
"""hit は、2件目以降のうちキャッシュを読めた割合"""
if cache:
reads = (N - 1) * hit
writes = N - reads # 読めなかった分は書き込みになる
prefix = PREFIX * (WRITE * writes + READ * reads)
else:
prefix = PREFIX * N * IN
total = prefix + VAR * N * IN + OUTPUT * N * OUT
return total * (BATCH if batch else 1.0)
base = cost(False, False)
rows = [
("そのまま送る", cost(False, False)),
("キャッシュのみ", cost(True, False)),
("バッチのみ", cost(False, True)),
("併用(すべて読めた)", cost(True, True, 1.0)),
("併用(半分だけ読めた)", cost(True, True, 0.5)),
("併用(まったく読めない)", cost(True, True, 0.0)),
]
for name, c in rows:
print(f"{name:<14} {c:>12,.0f} そのままの {c / base:.1%}")
print()
print("出力を 300 -> 150 トークンにした場合(そのまま送る)")
OUTPUT = 150
print(f" {cost(False, False):,.0f} そのままの {cost(False, False) / base:.1%}")
$ python3 combo.py
そのまま送る 1,170,000 そのままの 100.0%
キャッシュのみ 281,500 そのままの 24.1%
バッチのみ 585,000 そのままの 50.0%
併用(すべて読めた) 140,750 そのままの 12.0%
併用(半分だけ読めた) 425,375 そのままの 36.4%
併用(まったく読めない) 710,000 そのままの 60.7%
出力を 300 -> 150 トークンにした場合(そのまま送る)
1,095,000 そのままの 93.6%
| 読み取れること | 数字 |
|---|---|
| 前置きが大きい処理では、キャッシュだけで約4分の1になる | 24.1% |
| バッチは内容に関係なく半分 | 50.0% |
| 併用ですべて読めれば、約8分の1 | 12.0% |
| 併用でも、半分しか読めないと、バッチのみとの差は小さくなる | 36.4% |
| 併用でまったく読めないと、書き込みの割増分だけバッチのみより高い | 60.7% |
| 前置きが大きい処理では、出力を半分にしても全体の減りは小さい | 93.6% |
併用の効果は「どれだけ読めたか」で大きく変わります。 バッチの結果にも usage が入るので、読み取りのトークン数を集計して、想定どおりかを確かめます。最後の行が示すように、費用の大半を占めている部分(この例では前置き)から手を付けるのが近道です。出力が長い処理では、逆に出力を短くする効果が大きくなります。
出力トークンを減らす指定の一覧
出力はキャッシュの対象になりません。減らす手段は「上限を決める」「止める場所を決める」「短く書くよう頼む」の3種類です。パラメーター名は、インストールした SDK(anthropic 1.11.0/openai 3.24.0)の引数にあることを確かめたものです。
| 手段 | Anthropic(Messages API) | OpenAI(Responses API) | OpenAI(Chat Completions API) | 効き方 |
|---|---|---|---|---|
| 出力の上限 | max_tokens |
max_output_tokens |
max_completion_tokens |
上限で打ち切る。途中で切れることがあるので、短く書く指示と併せる |
| 止める文字列 | stop_sequences |
― | stop |
指定した文字列が出たところで止める。区切り記号の後ろを書かせない |
| 推論・思考の量 | thinking、output_config の effort |
reasoning の effort |
reasoning_effort |
答えの前に考える量を調整する。簡単な分類などでは小さくできる |
| 文章の詳しさ | ―(プロンプトで指定) | text の verbosity |
verbosity |
文章の長さの傾向を変える |
| 出力の形 | output_config の format |
text の format |
response_format |
決まった形の JSON だけを返させ、前置きや説明文を省く |
プロンプト側でできる指定も並べます。
| 指定 | 書き方の例 |
|---|---|
| 文字数・件数を決める | 「3点以内の箇条書きで、各40字以内」 |
| 前置きと復唱を省く | 「質問の繰り返しや挨拶は書かず、答えから始める」 |
| 選択肢で答えさせる | 「A・B・C のいずれか1文字だけを返す」 |
| 理由は必要なときだけ | 「判定だけを返す。理由は求められたときに書く」 |
| 差分だけ返させる | 「変更した行だけを返す。変更のない部分は出力しない」 |
似た仕組みとの違いと選び方
「キャッシュ」という名前の付く仕組みは他にもあります。
| 仕組み | 何を覚えるか | 安くなる部分 | 答えは毎回生成されるか |
|---|---|---|---|
| プロンプトキャッシュ(この記事) | 入力の接頭辞の処理結果(API 側) | 入力の読み取り | される |
| アプリ側の応答キャッシュ | 質問と答えの組(自分のアプリ側) | 入力も出力も(API を呼ばない) | されない(保存した答えを返す) |
| バッチ | 覚えない(まとめて後で処理する) | 入力と出力の両方 | される |
| 条件 | 選ぶもの |
|---|---|
| まったく同じ質問が何度も来て、同じ答えでよい | アプリ側の応答キャッシュ |
| 前置きは同じで、質問が毎回違う | プロンプトキャッシュ |
| 質問は毎回違い、前置きも短いが、急がない | バッチ |
| 前置きが同じで、急がない大量処理 | プロンプトキャッシュ+バッチ |
トークンの数え方そのもの(文字数との関係、数える手段)は別の主題です。この記事は「数えた後に、どう減らすか」に絞っています。
確かめて分かったこと
計算の実行と、SDK を模擬サーバーに向けた実行、公式ドキュメントの確認で分かった点です。
- 元が取れる読み取り回数は、前置きの長さに関係なく倍率だけで決まる。 書き込み1.25倍なら1回、2倍なら2回でした。2倍で1回しか読めないと 1.050 で、わずかに割高になります。
- 併用でキャッシュが一度も読めないと、バッチのみより高くなる(60.7% と 50.0%)。 併用は「読める見込みがある」ときに選び、usage で確かめるのが前提です。
- Anthropic の
input_tokensは入力全体ではない。 キャッシュを使ったリクエストでは、3つの項目を足して初めて全体になります。input_tokensだけを集計していると、入力が急に減ったように見えます。 - 最小トークン数に届かない前置きに
cache_controlを付けても、エラーにはならない。 書き込みも読み取りも 0 のまま通常どおり返るため、usage を見ないと気づけません(公式ドキュメントの記載どおりの挙動を模擬サーバーで再現して、検出コードが働くことを確かめました)。 - バッチの結果は投入順に並ぶとは限らない、と両社のドキュメントに明記されている。
custom_idをキーにした辞書で受けるコードなら、順序が逆でも対応がずれませんでした。 - OpenAI のキャッシュは、モデルの世代によって有効期間の指定方法と書き込みの扱いが違う。 記事やサンプルの値をそのまま当てはめず、使うモデルの記載を確かめる必要があります。
よくある質問
Q. プロンプトキャッシュとは何ですか? A. リクエストの先頭の同じ部分を API 側が覚えておき、次回から安い倍率で読む仕組みです。答えを保存するのではなく、入力の処理を再利用します。前置きが長く、短い間隔で繰り返し送る使い方で効果が出ます。
Q. プロンプトキャッシュが効かないときは、何を確かめればよいですか? A. usage の読み取りトークン数が 0 のままかを確かめ、次に「先頭に毎回変わる内容が無いか」「前置きが最小トークン数に届いているか」「リクエストの間隔が有効期間内か」を順に見ます。日付やIDを先頭に入れているのが、よくある原因です。
Q. キャッシュとバッチの違いは何ですか? A. キャッシュは同じ入力を安く読む仕組みで、返事はすぐ届きます。バッチはまとめて後から処理する仕組みで、入力も出力も半分の倍率になる代わりに、結果が届くまで待ちます。前置きが同じかどうかと、待てるかどうかで選びます。
Q. キャッシュとバッチは併用できますか? A. 併用できます。Anthropic のドキュメントには割引が重ねて適用されると書かれています。ただしバッチの中でキャッシュが読めるかは保証されないため、usage で読み取り率を確かめます。
Q. バッチの結果はいつ届きますか? A. 両社とも24時間以内が枠です。Anthropic のドキュメントには、多くは1時間未満で終わると書かれています。時間が決まった処理には、余裕を見て前日までに投入する組み方が向いています。
まとめ
- 同じ前置きを繰り返し送るならキャッシュ、急がない大量処理ならバッチ、両方に当てはまれば併用。
- キャッシュは書き込みが少し高く、読み取りが 0.1 倍。元が取れる読み取り回数は (w − 1) ÷ (1 − r) を超える回数で、前置きの長さによらない。
- 変わらないものを前、変わるものを後ろに置く。先頭の日付やIDがキャッシュを止める。
- Anthropic は明示指定(
cache_control)、OpenAI は既定で自動。最小トークン数・有効期間・usage の項目名が違う。 - バッチは入力も出力も 0.5 倍。結果は投入順とは限らないので
custom_idで対応づける。 - 併用の効果はどれだけ読めたかで決まる。読めなければバッチのみより高くなることもある。
- 効き具合は usage を記録して確かめる。費用の大半を占める部分から手を付ける。
参考・出典
- Anthropic「Prompt caching」 https://platform.claude.com/docs/en/build-with-claude/prompt-caching (2026-10-06 確認)
- Anthropic「Batch processing」 https://platform.claude.com/docs/en/build-with-claude/batch-processing (2026-10-06 確認)
- OpenAI「Prompt caching」 https://developers.openai.com/api/docs/guides/prompt-caching (2026-10-06 確認)
- OpenAI「Batch API」 https://developers.openai.com/api/docs/guides/batch (2026-10-06 確認)
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「LLM APIのコスト削減方法|キャッシュとバッチの仕組みと計算例」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。