一家餐廳,午餐時間固定賣三種定食。每天中午一到,廚師不是從零開始切菜——白飯已經煮好、味噌湯底已經熬好、配菜已經備好。客人點餐的時候,廚師只需要處理主菜那一道,30 秒出餐。

如果每一份定食都從洗米煮飯開始做,一份要 15 分鐘。

Prompt Caching 就是 API 呼叫的備料。你的 system prompt、工具定義、前幾輪對話——這些每次都一樣的東西,處理一次就好,之後直接用。不用每次都重新「煮」。

省多少?**90%**。不是寫錯,是九成。


問題出在哪:每次呼叫都在重複做同一件事

用 Claude API 寫一個 chatbot,通常會這樣設定:

1
2
3
4
5
6
7
8
9
10
11
12
13
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
system="你是一個專業的客服助理,以下是公司政策文件...(8000 tokens)",
tools=[...], # 工具定義,2000 tokens
messages=[
# 前 10 輪對話,5000 tokens
{"role": "user", "content": "前面的問題..."},
{"role": "assistant", "content": "前面的回答..."},
# ...
{"role": "user", "content": "我的新問題"} # 100 tokens
]
)

每次使用者發一條新訊息,你送出的 request 裡有 15,100 tokens。其中 15,000 tokens 跟上一次一模一樣——system prompt、工具定義、歷史對話都沒變。只有最後 100 tokens 是新的。

但 API 照樣收你 15,100 tokens 的錢。每一次。

如果你的 chatbot 一天處理 10,000 次對話,每次平均 10 輪——那是 100,000 次 API 呼叫。其中 99% 的 input tokens 是重複的。

這就是 Prompt Caching 要解決的事。


兩行程式碼搞定:自動快取

最簡單的用法,加一個參數就好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
cache_control={"type": "ephemeral"}, # 就這一行
system="你是一個專業的客服助理...(8000 tokens 的政策文件)",
messages=[
{"role": "user", "content": "我的訂單 #12345 到哪了?"}
]
)

print(response.usage)

TypeScript 版本:

1
2
3
4
5
6
7
8
9
10
11
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
cache_control: { type: "ephemeral" },
system: "你是一個專業的客服助理...",
messages: [
{ role: "user", content: "我的訂單 #12345 到哪了?" }
],
});

console.log(response.usage);

設了 cache_control 之後,Claude 會自動在最後一個可快取的 block 上設定 breakpoint。第一次呼叫照常處理(還會多花一點寫入快取的成本)。第二次呼叫如果前綴一樣,快取命中,那段 tokens 的費用直接打一折。

回傳的 usage 會告訴你快取的狀況:

1
2
3
4
5
6
{
"input_tokens": 50,
"cache_read_input_tokens": 10000,
"cache_creation_input_tokens": 0,
"output_tokens": 200
}

cache_read_input_tokens: 10000 代表有一萬個 tokens 是從快取讀的。這一萬個 tokens 的費用是正常價的十分之一。


精確控制:手動設定 Breakpoint

自動快取很方便,但有時候你需要更精確的控制。比如你的 prompt 裡有三段不同更新頻率的內容:

  • 工具定義:幾乎不變
  • System prompt + 公司政策:每週更新一次
  • 對話歷史:每次都在變

這時候用手動 breakpoint,最多可以設 4 個:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
tools=[
{
"name": "get_order_status",
"description": "查詢訂單狀態",
"input_schema": {
"type": "object",
"properties": {
"order_id": {"type": "string"}
},
"required": ["order_id"]
},
"cache_control": {"type": "ephemeral"} # breakpoint 1
}
],
system=[
{
"type": "text",
"text": "你是一個專業客服助理。以下是完整的公司政策...",
"cache_control": {"type": "ephemeral"} # breakpoint 2
}
],
messages=[
{"role": "user", "content": "我的訂單到哪了?"}
]
)

層級結構很重要:toolssystemmessages。如果你改了 tools 裡的定義,system 和 messages 的快取全部失效。如果只改了 messages,tools 和 system 的快取繼續用。

這跟餐廳備料的邏輯一樣——你改了味噌湯的配方,所有用到味噌湯的定食都得重做。但你只是換了主菜的魚,白飯和味噌湯不受影響。


價格拆解:什麼時候省、什麼時候虧

以 Claude Opus 4.7 為例(每百萬 tokens):

類型 價格 相對基準
基礎 input $5.00
5 分鐘快取寫入 $6.25 1.25×
1 小時快取寫入 $10.00
快取讀取 $0.50 0.1×

寫入比正常貴 25%,但讀取只要十分之一。所以損益平衡點是:快取被讀取超過 1.39 次就開始省錢

算一下具體數字。一個有 10,000 tokens system prompt 的 chatbot:

  • 不用快取:每次呼叫 input 成本 = 10,000 × $5 / 1M = $0.05
  • 第 1 次呼叫(快取寫入):10,000 × $6.25 / 1M = $0.0625
  • 第 2 次開始(快取讀取):10,000 × $0.50 / 1M = $0.005

第一次多花了 $0.0125(25% 溢價),第二次開始每次省 $0.045(90% 折扣)。用不到兩次就回本了。如果這個 chatbot 5 分鐘內有 100 次對話,光快取讀取就省了 $4.50。

乘以一天、一週、一個月,數字會大到讓你後悔為什麼不早用。

Sonnet 4.6 更便宜:

類型 價格
基礎 input $3.00
快取寫入 $3.75
快取讀取 $0.30

快取的有效期:5 分鐘,或 1 小時

預設快取活 5 分鐘。每次被讀取會重置倒數計時——所以只要每 5 分鐘內至少有一次呼叫,快取就一直活著。

如果你的使用場景是「偶爾用、但不到每 5 分鐘一次」,可以用 1 小時快取:

1
2
3
4
5
6
7
8
9
10
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
cache_control={
"type": "ephemeral",
"ttl": "1h" # 1 小時快取
},
system="你的超長 system prompt...",
messages=[{"role": "user", "content": "問題"}]
)

1 小時快取的寫入成本是 2×(正常的兩倍),但讀取成本跟 5 分鐘快取一樣是 0.1×。適合內部工具、每小時用幾次的 AI 助理。

重要限制:混用 TTL 時,1 小時快取必須放在 5 分鐘快取之前。


Pre-warming:消除第一次呼叫的延遲

第一次呼叫(cache miss)需要處理完整 prompt,延遲比較高。如果你知道使用者「快來了」,可以先暖機:

1
2
3
4
5
6
7
8
9
10
11
12
prewarm = client.messages.create(
model="claude-opus-4-7",
max_tokens=0, # 不需要回覆,只要建立快取
system=[
{
"type": "text",
"text": "你的 system prompt...(10000 tokens)",
"cache_control": {"type": "ephemeral"}
}
],
messages=[{"role": "user", "content": "warmup"}]
)

max_tokens: 0 告訴 Claude「我不需要你回答,只要把 prompt 處理完存進快取」。使用者真正開始問問題的時候,快取已經熱了,第一次呼叫就是 cache hit。

這招特別適合:

  • 部署後的第一個使用者不用等
  • 排程服務每隔幾分鐘暖一次,保持快取活躍
  • 流量高峰前預先建立快取

最低 Token 門檻

不是所有 prompt 都能被快取。每個模型有最低 token 數要求:

模型 最低 tokens
Opus 4.7 / 4.6 / 4.5、Haiku 4.5 4,096
Sonnet 4.6、Haiku 3.5 2,048
Sonnet 4.5 / 4、Opus 4.1 / 4 1,024

低於門檻的 prompt 不會報錯,只是默默不快取。所以如果你的 system prompt 只有 500 tokens,加了 cache_control 也不會有效果。

解法:把工具定義、few-shot examples、參考文件等固定內容塞進 system prompt,湊到門檻以上。這些內容本來就適合放在 prompt 前面,一舉兩得。


快取失效:哪些改動會讓快取爆掉

快取的邏輯是「前綴比對」——從頭開始比,遇到第一個不一樣的地方就停。所以:

  • 改了 tools → tools、system、messages 的快取全部失效
  • 改了 system → system 和 messages 失效,tools 的快取還在
  • 改了 messages → 只有 messages 失效

還有幾個隱藏的地雷:

  • tool_choice 改了 → tools 快取 OK,但 system 和 messages 失效
  • thinking 參數改了 → system 和 messages 失效
  • 圖片增減 → system 和 messages 失效
  • JSON key 順序不穩定 → 某些程式語言的 dict/map 不保證順序,每次 request 的 tools JSON 可能長不一樣,快取永遠 miss

最後那個踩過的人都知道痛。Python 3.7+ 的 dict 有保序,但如果你用某些 JSON serializer 或其他語言,key 順序可能每次不同。快取命中率離奇低的時候,先檢查這個。


多輪對話的快取策略

對話越長,快取越重要。10 輪對話累積下來可能有幾萬個 tokens,每次只新增幾百。

自動快取在這裡表現很好——它會自動把 breakpoint 往前推,確保前面的對話歷史被快取:

1
2
3
4
5
6
7
8
Request 1: System + User(1) [cache]
→ 寫入快取

Request 2: System + User(1) + Asst(1) + User(2) [cache]
→ System~User(1) 從快取讀,Asst(1)+User(2) 寫入新快取

Request 3: System + User(1) + Asst(1) + User(2) + Asst(2) + User(3) [cache]
→ System~User(2) 從快取讀,Asst(2)+User(3) 寫入新快取

每次只有最新的幾百 tokens 需要重新處理,前面的幾萬 tokens 都走快取。

一個注意點:快取系統有 20 block 回溯限制。如果對話超過 20 個 block(大概 10 輪),早期的快取可能會掉出回溯窗口。解法是在中間手動加一個 breakpoint。


實戰範例:RAG 系統的快取優化

RAG 架構最適合用快取。一個典型的 RAG prompt 結構:

1
2
3
4
5
6
system_prompt = """
你是公司的內部知識助理。
以下是相關文件內容:

{retrieved_documents} # 每次檢索結果不同,5000-20000 tokens
"""

問題來了——檢索結果每次都不同,system prompt 不是固定的,快取命中率會很低。

更好的做法是把固定和動態的部分分開:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=[
{
"type": "text",
"text": "你是公司的內部知識助理。回答時引用文件段落。"
"以下是公司完整的產品目錄和 FAQ(50 頁)...",
"cache_control": {"type": "ephemeral"} # 固定部分,快取
},
{
"type": "text",
"text": f"本次檢索到的相關段落:\n{retrieved_docs}"
# 動態部分,不設 cache_control
}
],
messages=[
{"role": "user", "content": user_question}
]
)

固定的產品目錄和 FAQ 被快取(可能有 30,000 tokens),動態檢索結果不快取。這樣每次呼叫只需要處理檢索結果和使用者問題——省了 90% 的固定成本。


監控:怎麼知道快取有沒有生效

每次 API 回傳的 usage 裡有三個關鍵數字:

1
2
3
4
5
6
7
8
9
10
usage = response.usage
print(f"快取讀取: {usage.cache_read_input_tokens} tokens")
print(f"快取寫入: {usage.cache_creation_input_tokens} tokens")
print(f"未快取: {usage.input_tokens} tokens")

total = (usage.cache_read_input_tokens +
usage.cache_creation_input_tokens +
usage.input_tokens)
hit_rate = usage.cache_read_input_tokens / total * 100
print(f"快取命中率: {hit_rate:.1f}%")

健康的數字:

  • cache_read_input_tokens 占總 input 的 80% 以上 → 太好了
  • cache_creation_input_tokens 一直很高 → 快取一直在重建,可能有失效問題
  • cache_read_input_tokens 一直是 0 → 快取沒命中,檢查 prompt 結構

常見踩坑

1. Prompt 太短沒到門檻
你的 system prompt 只有 2000 tokens,用的是 Opus 4.7(門檻 4096)。cache_control 加了但完全沒效果,API 不會報錯,帳單照收全價。

2. JSON key 順序不穩定
tools 陣列裡的 JSON 每次序列化順序不同,等於每次都是新的 prompt,快取永遠 miss。用 json.dumps(tools, sort_keys=True) 或固定順序的 serializer。

3. 第一個 request 的並行陷阱
快取要等第一個 response 開始生成後才會生效。如果你同時送 10 個 request,這 10 個全部是 cache miss——因為快取還沒建好。先送一個,等它開始回應,再送其他的。

4. 忘記靜態內容要放前面
prompt 的順序很重要。把會變的東西(使用者訊息)放前面、固定的東西(system prompt)放後面——快取完全失效。永遠是固定內容在前、動態內容在後。

5. 改了不該改的參數
換了 tool_choice 或調了 thinkingbudget_tokens,system 的快取就爆了。這些參數最好固定下來,不要每次 request 都變。


該用自動還是手動?

情境 推薦
多輪對話 chatbot 自動(cache_control 放 top level)
固定 system prompt + 動態 messages 手動(system 上設 breakpoint)
RAG(固定知識庫 + 動態檢索) 手動(固定部分設 breakpoint)
長文件分析 自動(文件塞 system,自動快取)
多工具 agent 手動(tools 和 system 各設 breakpoint)

大多數情況下從自動開始就好。等你看到快取命中率不夠高,再切手動微調。


Prompt Caching 的核心其實就一件事:告訴 API「這段內容你之前看過了,不用再處理一遍」

像餐廳備料。你不會因為每桌客人都點了白飯,就每次從洗米開始做。你提前煮好一大鍋,客人來了直接盛。備料有成本(煮一大鍋比煮一碗花的時間和瓦斯多一點),但只要客人超過兩個,就划算了。

兩行程式碼,九成的帳單。這大概是 Claude API 裡 ROI 最高的優化。

學完這個之後,下一步可以看 Anthropic 的 Batch API——如果你的任務不需要即時回應,Batch API 可以在快取折扣之上再打五折。兩個一起用,input token 的成本可以壓到基礎價的 5%。

參考來源:Prompt Caching — Anthropic Docs
參考來源:Building with the Claude API — Anthropic Academy