开始之前
当许多请求都以同一个大块内容开头时,缓存才划算,比如系统提示词、工具定义、参考文档或很长的历史。第一次请求在响应开始后,把这段前缀写入缓存。之后前缀完全相同的请求,会把它读回来。文档明确说,缓存不会影响你收到的输出。
你需要第 1 课里的环境,而第 2 课里的工具列表,就是一个典型的缓存对象。缓存已经不再需要 beta 请求头。提示词缓存页面提到了唯一的陷阱:老教程里调用 client.beta.prompt_caching 的写法,在当前 SDK 里会抛出 AttributeError。请改用 client.messages.create。
添加缓存断点并验证它生效
示例用一份存在文件里的风格指南来审阅文字。指南在每次请求里都一样,所以断点加在它身上,跟在后面的问题则每次不同。请用一份至少 512 个 token 的真实指南,否则缓存会被跳过。
import anthropic
client = anthropic.Anthropic()
# Your stable text. It must reach the model's minimum, 512 tokens on Opus 5.5.
STYLE_GUIDE = open("style_guide.md", encoding="utf-8").read()
def ask(question: str):
return client.messages.create(
model="claude-opus-5-5",
max_tokens=500,
system=[
{"type": "text", "text": "You review text against the style guide below."},
{
"type": "text",
"text": STYLE_GUIDE,
"cache_control": {"type": "ephemeral"},
},
],
messages=[{"role": "user", "content": question}],
)
for question in ["Is the word 'utilize' allowed?", "How should I write a date?"]:
usage = ask(question).usage
print(
"written:", usage.cache_creation_input_tokens,
"read:", usage.cache_read_input_tokens,
"uncached:", usage.input_tokens,
)- 把稳定的内容放在最前面。缓存前缀按
tools、system、messages的顺序构建。 - 把
"cache_control": {"type": "ephemeral"}加到每次请求里都相同的最后一个块上。 - 运行脚本。第二次请求要在第一次的五分钟之内发出。
- 读打印出来的数字。第一行显示写入缓存的 token 数,第二行显示同样数量的 token 被读取。
- 如果写入和读取都是 0,说明前缀低于该模型的最小长度,或者被标记的块在两次调用之间变了。
断点覆盖什么
断点会缓存它之前的一切,从第一个工具一直到被标记的块。写入只发生在断点处。读取则反方向进行:API 先在你的断点处找条目,再往前回溯之前的块,最多查 20 个位置,找早先某次请求写下的条目。它永远找不到那些写入时并不是断点的稳定文字。
这条规则解释了最典型的失败。把时间戳或用户消息放进被标记的块里,每次请求都会写入一个新条目,却没有一次读到。应当标记静态前缀的末尾,让变化的部分跟在后面。
你最多可以放四个断点,断点本身不花钱。计费只涉及写入、读取和未缓存的输入。长对话里第二个断点很重要:当某一回合在上次写入之后新增了二十个或更多块,除非更近的断点一直在写入,否则回溯就找不到旧条目。
| 方面 | 自动缓存 | 显式断点 |
|---|---|---|
| 放在哪里 | 请求顶层放一个 cache_control | 放在工具、系统或消息块上的 cache_control |
| 对话变长时 | 每次请求都移到最后一个可缓存的块 | 停在你放的位置 |
| 适合什么 | 多轮对话和智能体循环 | 变化速度不同的部分,比如工具、系统提示词和文档 |
| 占用名额 | 占用四个名额中的一个 | 最多四个 |
| 何时失败 | 最后一个块带有 TTL 不同的显式标记,或者四个名额已经用完 | 被标记的块在请求之间发生变化 |
各模型的最小长度
短于最小长度的提示词会照常处理但不做缓存,API 也不会返回错误。你只能从 usage 字段发现:如果两个缓存计数都是 0,就是前缀太短,或者没有匹配上。
如果你的提示词刚好差一点,文档说把缓存内容补到阈值,往往是值得的,因为读取比未缓存的输入便宜。
| 可缓存的最小提示词 | 模型 |
|---|---|
| 512 个 token | Fable 5.1、Opus 5.5、Sonnet 5.5、Haiku 5.5、Opus 5、Fable 5 |
| 1,024 个 token | Opus 4.8、Sonnet 5、Sonnet 4.6、Sonnet 4.5 |
| 2,048 个 token | Opus 4.7 |
| 4,096 个 token | Opus 4.6、Opus 4.5、Haiku 4.5 |
存活时间与成本
默认存活时间是五分钟,从写入或读取该条目的那次请求开始算起。每次读取都会免费刷新它,所以繁忙的智能体会让自己的缓存一直保持有效。一次流式输出了四分钟的响应,只给后续请求留下大约一分钟的启动时间。
间隔更长时,在 cache_control 里设置 "ttl": "1h"。一小时的写入成本更高,文档建议用在运行超过五分钟的子智能体,或者用户可能不会马上回复的聊天上。同一次请求混用两种存活时间时,一小时的条目必须排在前面。这时 usage 对象会把写入拆成 ephemeral_5m_input_tokens 和 ephemeral_1h_input_tokens。
usage 的几个字段加起来才是全部:总输入等于 cache_read_input_tokens 加 cache_creation_input_tokens 再加 input_tokens。最后一个只统计最后一个断点之后的部分,所以缓存生效之后它会变小。
什么会让缓存失效
工具、系统提示词、消息这个顺序里,任何一层的改动,都会让这一层和它后面的每一层失效。工具定义排在最前面,所以改动一个名称、一段描述或一个参数,就会让一切作废。
缓存诊断页面点出了一个让人意外的原因:缓存是按模型分开的,所以路由或回退换了另一个模型,就要从零开始。另一个原因来自写 JSON 的代码。Swift 和 Go 等语言会随机打乱键的顺序,所以 tool_use 块里的键,每次都必须以同样的顺序输出。
| 改动的内容 | 工具 | 系统 | 消息 |
|---|---|---|---|
| 工具定义 | 失效 | 失效 | 失效 |
| 网页搜索或引用开关 | 保留 | 失效 | 失效 |
| 速度设置,快速或标准 | 保留 | 失效 | 失效 |
tool_choice | 保留 | 保留 | 失效 |
| 增加或移除图片 | 保留 | 保留 | 失效 |
思考参数、output_config.effort | 取决于模型 | 取决于模型 | 失效 |
查出请求为什么没命中
读 usage 只能告诉你缓存没命中,说不出原因。缓存诊断会比较两次请求,指出它们最先出现差异的地方。第一个回合传入 diagnostics 对象,把 previous_message_id 设为 null,下一个回合再传上一次响应的 id。它只在 Claude API 上可用。以前需要的 beta 请求头现在已经不用了,不过文档里的 Python 示例仍然走 client.beta.messages。
如果响应里的 cache_miss_reason 是 model_changed、system_changed、tools_changed 或 messages_changed,就指出了问题所在,cache_missed_input_tokens 则粗略表示丢掉的前缀有多大。messages_changed 通常意味着历史被截断、编辑或重新序列化了,而不是只往后追加(见缓存诊断页面)。
r1 = client.beta.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
cache_control={"type": "ephemeral"},
system=SYSTEM,
messages=[{"role": "user", "content": "Summarize section 1."}],
diagnostics={"previous_message_id": None}, # opt in on the first turn
)
r2 = client.beta.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
cache_control={"type": "ephemeral"},
system=SYSTEM,
messages=[
{"role": "user", "content": "Summarize section 1."},
{"role": "assistant", "content": r1.content},
{"role": "user", "content": "Now summarize section 2."},
],
diagnostics={"previous_message_id": r1.id},
)
if r2.diagnostics and r2.diagnostics.cache_miss_reason:
print(r2.diagnostics.cache_miss_reason.type)- 结果为 null 而且缓存读取很多,说明前缀没变,缓存命中了。
- 结果为 null 但读取为零,说明请求一致,可是条目已经过期,所以要缩短间隔,或者改用一小时的存活时间。
- 出现 changed 类型而且读取为零,说明请求变了。按类型指出的内容去改,先改第一个被报告的。
在流量到来之前预热缓存
条目要等第一个响应开始之后才存在。同一时刻发出的请求读不到它,所以先发一个,等它的响应开始,再并行发送其余的。
想在用户提问之前填好缓存,就发一个预热请求:把 max_tokens 设为 0,并在真实请求共享的最后一个块上放显式断点,通常是系统提示词或工具列表。它返回空的 content 数组、stop_reason 为 max_tokens,以及完整的 usage 块。不会计输出 token,但仍然会计一次缓存写入。
零 token 的请求如果设置了 stream、扩展思考、结构化输出或强制的 tool_choice,API 会拒绝,在批处理里也会被拒绝。要让思考和强度设置与真实请求保持一致,因为缓存是按它们来区分的。
Thariq Shihipar 和 ClaudeDevs 谈围绕缓存来设计
Thariq Shihipar 发帖时在 Anthropic 做 Claude Code,他 2026-02-20 在 X 上写道,智能体必须先围绕提示词缓存来设计,因为几乎每个功能都会以某种方式碰到缓存。放到你自己的智能体上,就是先定好断点放在哪里,再去加那些会在会话中途改动系统提示词或工具列表的功能。
ClaudeDevs 是 Anthropic 面向开发者的账号,它在 2026-05-14 分享过一个技巧:先把系统提示词发出去,Claude 会把它写进缓存,但不生成任何输出,等真正的用户请求到来,就会命中已经预热的缓存。这就是本课里 max_tokens 为 0 的预热请求。文档还补了一条帖子里没说的规则:用默认的五分钟有效期时,至少每五分钟重新预热一次。
从根本上说,设计智能体时必须先考虑提示词缓存,几乎每个功能都会以某种方式碰到它。
X 官方嵌入卡片的截图,拍摄于 2026-11-05,时间为 UTC。点击截图可到 X 上打开原帖。
常见错误
- 标记了一个含有时间戳、请求 id 或用户消息的块。
- 低于模型最小长度还期待命中,结果既没有命中,也没有报错。
- 开启缓存之后,还把
input_tokens当成全部输入。 - 在请求之间编辑、重排或重新序列化工具列表。
- 在对话中途切换
tool_choice、强度或图片,然后纳闷消息为什么不再命中。 - 停顿时间超过了存活时间,还指望缓存还在。
- 以为第二个工作区会共享缓存。
自己试一试
- 把示例运行两次,打印那三个 usage 数字。再改动风格指南里的一个字符,重新运行。哪个数字变了?
- 在断点上方的第一个系统块里加一个时间戳。运行两次,看读取数一直是 0。
- 设置
"ttl": "1h",打印usage.cache_creation,看两种存活时间是怎样分开计数的。 - 两次调用之间等六分钟,看 usage 字段显示什么。
接下来
下一课会在每次调用里加上顶层的 cache_control,写出完整的智能体循环,让每个新回合之前的历史都从缓存里读取。缓存只节省输入方面的工作,上下文窗口那一课讲了哪些内容仍然要放得下。
自测一下
cache_control 断点应该放在哪里?
查看答案
D. 放在各次请求里始终相同的最后一个块上 写入只发生在断点处,所以放在会变化的块上,永远不会产生读取。
一次带有 cache_control 的调用,两个缓存 usage 字段都是 0,最可能的原因是什么?
查看答案
A. 前缀比模型的最小长度短 低于最小长度的前缀会照常处理但不做缓存,也不会返回错误。
开启缓存之后,usage 里的 input_tokens 统计的是什么?
查看答案
B. 只有最后一个断点之后的 token 总输入等于缓存读取加缓存写入再加 input_tokens,最后一项只覆盖最后一个断点之后的部分。
哪一种改动会让工具、系统和消息三层缓存同时失效?
查看答案
C. 编辑工具定义 缓存顺序里工具排在最前面,所以工具定义一变,它后面的每一层都会失效。
常见问题
缓存会改变 Claude 的回答吗?
不会。文档说提示词缓存不影响输出 token 的生成,所以你得到的,和不用缓存时是同样的响应。
缓存能和 Batch API 一起用吗?
可以,但命中只是尽力而为,因为批处理请求可能并发运行,顺序也不固定。文档建议先发一个带共享前缀和一小时缓存块的请求,再发批处理里的其余请求。
能手动清除缓存吗?
不能,没有清除缓存的办法。条目在至少五分钟没有被使用之后过期,或者在它的一小时存活时间之后过期。
thinking 块会被缓存吗?
它们自己不能带 cache_control。当它们位于之前的助手回合里时,会和其他内容一起被缓存,从缓存读取时也计入输入 token。
缓存读取比普通输入便宜多少?
2026 年 11 月 5 日,文档列出的是:大多数模型为基础输入价格的 0.1 倍,Opus 和 Sonnet 5.5 为 0.05 倍,Fable 5.1 为 0.025 倍。写入比普通输入贵,所以只用一次的前缀永远回不了本。
资料来源
- Anthropic Docs: Prompt caching https://platform.claude.com/docs/en/build-with-claude/prompt-caching
- Anthropic Docs: Cache diagnostics https://platform.claude.com/docs/en/build-with-claude/cache-diagnostics
- Anthropic Docs: Models overview https://platform.claude.com/docs/en/models/overview
- X: Thariq Shihipar on designing agents for prompt caching first https://x.com/trq212/status/2024638793719177291
- X: ClaudeDevs on pre-warming the prompt cache https://x.com/ClaudeDevs/status/2055069548672631218


