Claude API 的 Prompt Caching 怎么用:从少花钱到少踩坑
你接入 Claude API 以后,最容易被忽略的一笔账不是输出 token,而是每次请求里反复发送的那一大段固定上下文。 系统提示词、工具定义、RAG 检索出的长文档、代码仓库规范、对话历史摘要。
正文
你接入 Claude API 以后,最容易被忽略的一笔账不是输出 token,而是每次请求里反复发送的那一大段固定上下文。
系统提示词、工具定义、RAG 检索出的长文档、代码仓库规范、对话历史摘要,这些内容经常一字不差地出现在很多次请求里。模型每次都要重新处理它们,你也会按完整输入 token 付费。Prompt caching 解决的就是这个问题:把稳定的 prompt 前缀缓存起来,后续请求命中缓存时,只为这部分 token 支付更低的读取成本。
这不是一个需要重做架构的优化。真正难的地方在于你要知道哪些内容适合缓存、缓存断点应该放在哪里,以及怎样判断它真的命中了。
先判断:你的请求里有没有“稳定前缀”
Prompt caching 适合这些场景:
- 每次请求都会带同一段系统提示词。
- 工具 schema 很长,而且工具列表不频繁变化。
- RAG 会反复塞入同一批文档、手册或项目规范。
- Agent 循环里会持续复用同一段任务背景。
- 批处理任务在短时间内用同一套说明处理多条输入。
不适合的场景也很明确:
- 每次请求都是一次性短 prompt。
- 固定上下文很小,低于模型的最小缓存 token 门槛。
- 你把时间戳、随机 ID、用户即时输入放进了 prompt 前缀,导致每次前缀都不同。
- 输出 token 才是主要成本,输入 token 占比不高。
可以用一句话判断:如果一段 prompt 会在 TTL 时间窗口内被复用两三次以上,它就值得考虑缓存。
它缓存的不是“某个字段”,而是请求前缀
Prompt caching 的核心是“前缀匹配”。你在某个 content block 上放一个缓存断点,Claude 会把这个断点之前的内容当作可复用前缀。
后续请求只有在前缀保持一致时才容易命中。顺序、空格、工具定义、系统提示词、检索文档,只要在断点之前发生变化,都可能让缓存失效。
更稳妥的组织方式是把请求拆成几层:
工具定义(最稳定)
系统提示词(较稳定)
项目规范 / 知识库文档 / RAG 上下文(按任务复用)
历史摘要或阶段性上下文(短期复用)
当前用户输入(不缓存)
缓存断点应该尽量放在稳定块的最后,而不是放在动态输入后面。动态内容越靠后,缓存命中率越高。
最小实现:先缓存系统提示词
如果你只是想验证效果,可以先从系统提示词开始。下面是 TypeScript 写法:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
const REVIEW_RULES = `
你是一名代码审查助手。
审查重点包括:类型安全、异常处理、边界条件、性能风险和可维护性。
输出必须具体,不要给空泛建议。
`.trim();
export async function reviewCode(diff: string) {
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1200,
system: [
{
type: "text",
text: REVIEW_RULES,
cache_control: { type: "ephemeral" },
},
],
messages: [
{
role: "user",
content: `请审查下面的代码变更:\n\n${diff}`,
},
],
});
console.log({
input_tokens: response.usage.input_tokens,
cache_creation_input_tokens: response.usage.cache_creation_input_tokens,
cache_read_input_tokens: response.usage.cache_read_input_tokens,
});
return response.content;
}
Python 里也是同一个思路:
import os
import anthropic
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
REVIEW_RULES = """
你是一名代码审查助手。
审查重点包括:类型安全、异常处理、边界条件、性能风险和可维护性。
输出必须具体,不要给空泛建议。
""".strip()
def review_code(diff: str):
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1200,
system=[
{
"type": "text",
"text": REVIEW_RULES,
"cache_control": {"type": "ephemeral"},
}
],
messages=[
{
"role": "user",
"content": f"请审查下面的代码变更:\n\n{diff}",
}
],
)
print(
{
"input_tokens": response.usage.input_tokens,
"cache_creation_input_tokens": response.usage.cache_creation_input_tokens,
"cache_read_input_tokens": response.usage.cache_read_input_tokens,
}
)
return response.content
第一次请求通常会看到 cache_creation_input_tokens,后续命中时会看到 cache_read_input_tokens。上线前不要只看“代码加了 cache_control”,要把 usage 指标打进日志,连续观察一段时间的命中率。
5 分钟还是 1 小时:别一上来就选长 TTL
Anthropic 的显式缓存断点支持短 TTL,也支持 1 小时 TTL。短 TTL 写入成本更低,适合高频交互、Agent 循环、批处理队列这种“短时间内连续请求”的场景。1 小时 TTL 的写入成本更高,适合低频但仍会复用同一前缀的任务,比如每十几分钟跑一次的定时分析、长时间代码审查会话、周期性报告生成。
一个实用规则:
| 场景 | 更适合的选择 |
|---|---|
| 聊天、Agent 循环、连续批处理 | 短 TTL |
| 每隔十几分钟复用同一上下文 | 1 小时 TTL |
| 单次请求或很短 prompt | 不必缓存 |
| 前缀经常变化 | 先整理 prompt 结构 |
不要为了“缓存更久”直接选 1 小时。缓存写入也要付费,只有复用次数足够时才划算。
成本账怎么算
以一个约 2,000 token 的稳定前缀为例。如果它在短时间内被请求 10 次,没缓存时你会为这 20,000 个输入 token 按完整输入价付费。
加缓存后,第一轮会产生写入成本,后面命中的请求只产生读取成本。读缓存通常比完整输入便宜很多,所以收益来自“多次复用同一前缀”。
你可以在自己的服务里直接记录三类数字:
完整输入 token:usage.input_tokens
缓存写入 token:usage.cache_creation_input_tokens
缓存读取 token:usage.cache_read_input_tokens
再按官方价格表做一张周报:
节省的输入成本 =
缓存读取 token 按完整输入价计算的成本
- 缓存读取 token 实际读取成本
- 缓存写入带来的额外成本
这比只看总账单更可靠,因为你能看到缓存到底是命中、失效,还是压根没有达到最小缓存门槛。
最常见的缓存失效原因
第一类是动态内容放错位置。比如你把 今天是 2026-05-03、请求 ID、用户昵称放进系统提示词开头,每次前缀都不同,缓存自然很难命中。把这些内容移到当前用户输入里,或者做成后置上下文。
第二类是工具定义不稳定。工具数组顺序变化、schema 字段顺序变化、某个描述字符串被拼接出不同空格,都可能影响前缀匹配。工具定义要集中生成,并保证顺序稳定。
第三类是文档块太小。不同模型的最小可缓存 token 门槛不一样,过短的系统提示词不会带来你期待的缓存写入。短 prompt 不要为了缓存硬凑长度,只有上下文确实会反复复用时才值得优化。
第四类是把缓存当成强一致存储。Prompt caching 是服务端优化能力,不适合承载业务状态。业务状态仍然应该放在你的数据库、队列或会话存储里。
更像生产环境的断点放法
复杂请求不要只放一个断点。你可以按稳定程度分层:
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1600,
tools: [
{
name: "search_docs",
description: "搜索内部文档",
input_schema: {
type: "object",
properties: {
query: { type: "string" },
},
required: ["query"],
},
cache_control: { type: "ephemeral" },
},
],
system: [
{
type: "text",
text: STABLE_SYSTEM_PROMPT,
cache_control: { type: "ephemeral" },
},
],
messages: [
{
role: "user",
content: [
{
type: "text",
text: PROJECT_GUIDE,
cache_control: { type: "ephemeral" },
},
{
type: "text",
text: `当前问题:${question}`,
},
],
},
],
});
工具、系统提示词、项目文档都可以成为稳定前缀的一部分;当前问题不要放进缓存层。这样用户连续追问时,模型不需要每次都重新处理完整工具说明和项目资料。
上线前的检查清单
接入 Prompt caching 之前,先过一遍这几个问题:
- 稳定前缀是否真的会复用?
- 断点之前有没有时间戳、随机数、用户即时输入?
- 工具定义和文档顺序是否稳定?
- 固定上下文是否达到当前模型的最小缓存 token 门槛?
- 日志里是否记录了缓存写入和缓存读取 token?
- TTL 是否和真实请求间隔匹配?
- 价格计算是否使用了你当前调用模型的官方价格?
只要这些问题答案清楚,Prompt caching 通常是 Claude API 集成里投入产出比很高的一项优化。它不会减少输出成本,也不会替你设计会话状态,但它能让那些反复出现的长 prompt 不再每次都按完整输入价付费。
真正值得养成的习惯是:把 prompt 当成可优化的工程资产。固定内容放前面,动态内容放后面;能复用的上下文保持稳定;上线后看 usage 指标,而不是靠感觉判断是否省钱。这样做以后,缓存不是一个临时省钱技巧,而会变成你设计 Claude 应用时的默认约束。
参考资料
- 原文:Prompt Caching With the Claude API: A Practical Guide,DEV Community,https://dev.to/thegdsks/prompt-caching-with-the-claude-api-a-practical-guide-14ce
- Anthropic 官方文档:Prompt caching,https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
相关阅读
- AI API 平台目录:按模型和预算横向比较各站价格与支持情况
- claudeapi 详情:注册入口、模型覆盖和使用限制