使用 Claude API 进行提示缓存:在不更换模型的情况下降低输入成本
提示缓存能将大型稳定输入(你的系统提示、工具定义、少样本示例)在重复请求时的成本降至正常输入定价的大约 10%。其机制是前缀匹配:在稳定内容的末尾放置一个 cache_control 标记,并将所有易变内容保留在它之后。摧毁缓存命中率的错误,就是让时间戳或 UUID 漂移进入前缀。
每周三。28,400+ 读者。纯干货。
✓ 请查收邮箱 — 点击确认链接以完成订阅。
✓ 订阅成功!
✓ 您已在订阅列表中。
Table of contents
Open Table of contents
提示缓存究竟做了什么
每一次对 Claude API 的调用都会发送 token。在没有缓存的情况下,你请求中的每一个 token——系统提示、工具定义、少样本示例以及用户消息——都会按正常的输入费率计价。有了缓存后,这些 token 中的一个前缀会在第一次请求之后被存储在 Anthropic 的服务器上。在后续共享该精确前缀的请求中,你支付的是缓存读取价格,而不是从头重新处理它们。
成本差异是实实在在的:
- 缓存写入: 约为基础输入价格的 1.25 倍(5 分钟 TTL)或约 2 倍(1 小时 TTL)
- 缓存读取: 约为基础输入价格的 0.1 倍
- 盈亏平衡: 5 分钟 TTL 下为 2 次请求,1 小时 TTL 下为 3 次请求
一旦越过盈亏平衡点——对于任何每天运行不止几次的智能体来说,这会很快发生——之后每一次额外的缓存命中,都意味着对这些 token 约 90% 的折扣。
前缀匹配的不变量
这是其他一切都要遵循的唯一规则:缓存键是对你渲染后提示的前缀匹配。
Anthropic 的服务器会存储从你提示的开头一直到 cache_control 标记之间的渲染内容。要让下一次请求发生缓存命中,从提示开头到该标记之间的每一个 token 都必须完全一致——逐字节相同。
用于前缀匹配的渲染顺序是:tools → system → messages。因此你的 tools 数组会被最先哈希,然后是 system 块,最后是按顺序排列的 messages。
这在实践中意味着:稳定内容必须排在最前面。如果你的系统提示引用了任何动态内容——当前日期、用户 ID、请求追踪 ID——并且它出现在 cache_control 标记之前,那么缓存在每个请求上都会未命中,因为前缀一直在变。
应该把缓存标记放在哪里
杠杆最高的目标是:
1. 你的系统提示
系统提示通常是最大的稳定块。一段详细的智能体人设、一组行为规则、一套输出格式指令——所有这些在同一个智能体的每次调用中都是相同的。给它打上标记:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-opus-4-8",
max_tokens: 1024,
system: [
{
type: "text",
text: `You are a content operations agent for alejandrorioja.com.
Your job is to draft blog posts in Alejandro's voice: direct, practitioner,
first-person, numbered lists, honest caveats. No hedging. No filler.
Every section must earn its place.
[... 2000 more tokens of stable instructions ...]`,
cache_control: { type: "ephemeral" },
},
],
messages: [
{
role: "user",
content: "Draft a post about prompt caching.",
},
],
});system 块上的 cache_control: { type: "ephemeral" } 告诉 Claude 缓存到该块为止(含该块)的所有内容。messages 数组是易变的——每个请求各不相同——并且停留在缓存边界之外。
2. 工具定义
如果你的智能体使用工具,那些定义可能相当可观。一个文档完善的工具 schema,包含描述、参数名和枚举值,每个工具可能达到 500–1,000 个 token。如果有 5 个工具,那就是多达 5,000 个 token,你每次调用都要付费重新处理它们:
const response = await client.messages.create({
model: "claude-opus-4-8",
max_tokens: 1024,
tools: [
{
name: "search_airtable",
description: "Search the Airtable content queue...",
input_schema: { type: "object", properties: { query: { type: "string" } } },
},
// ... more tools ...
{
name: "post_to_kit",
description: "Schedule a broadcast via the Kit API...",
input_schema: { /* ... */ },
// Mark the last tool to cache the entire tools array
} as Anthropic.Tool & { cache_control: { type: "ephemeral" } },
],
system: "...",
messages: [...],
});给数组中最后一个工具打标记。前缀匹配将从那一点起覆盖整个 tools 数组。
3. messages 中的少样本示例
如果你把静态的少样本示例作为 messages 数组中靠前的消息传入,那些也可以被缓存。把它们组织成前 N 条消息,并给最后一个示例轮次打标记:
const messages: Anthropic.MessageParam[] = [
{
role: "user",
content: [
{
type: "text",
text: "Here are examples of posts in my voice:\n\n[Example 1...]\n\n[Example 2...]",
cache_control: { type: "ephemeral" },
} as Anthropic.TextBlockParam & { cache_control: { type: "ephemeral" } },
],
},
{
role: "assistant",
content: "Understood. I'll follow that voice.",
},
// The actual user turn follows — this is volatile, no cache marker
{
role: "user",
content: actualUserRequest,
},
];不应该缓存什么(隐性失效因素)
这些是看起来稳定、实则不然的东西——它们会悄无声息地摧毁你的命中率。API 不会警告你。你只会看到每个请求上都出现 cache_creation_input_tokens,然后纳闷究竟为什么。
系统提示中的时间戳。 最常见的单一错误:
// This invalidates the cache on every request
const system = `You are an agent. Current time: ${new Date().toISOString()}`;把时间戳移到它本该所在的用户消息里:
// Stable system prompt — cacheable
const system = `You are an agent. Use the current time provided by the user.`;
// Volatile user message — not cached
const userMessage = `Current time: ${new Date().toISOString()}. Run the daily brief.`;随机 UUID 和追踪 ID。 同样的问题。如果你为了记录日志而往 system 块里注入一个追踪 ID,那么每个请求都会得到一个全新的前缀。
非确定性的 JSON 序列化。 如果你把一个对象序列化进系统提示,而键的顺序无法保证,那么即使底层数据相同,渲染出来的字符串也可能不同。请用稳定的键顺序序列化,或者使用模板字符串。
动态少样本选择。 如果你根据当前查询来选择少样本示例并把它们放进被缓存的前缀里,那你就把那个“稳定”的前缀变成了依赖查询的。要么为缓存层固定使用一组示例,要么把动态示例移到未缓存的消息轮次里。
验证你的缓存命中率
每个响应都包含用量元数据。检查它:
const response = await client.messages.create({ /* ... */ });
console.log({
inputTokens: response.usage.input_tokens,
cacheRead: response.usage.cache_read_input_tokens,
cacheWrite: response.usage.cache_creation_input_tokens,
outputTokens: response.usage.output_tokens,
});在第一次请求时:cache_creation_input_tokens 会非零,cache_read_input_tokens 会是 0。那是写入。
在缓存命中时:cache_read_input_tokens 会非零,cache_creation_input_tokens 会是 0。那是读取。
如果你在每个请求上都看到 cache_creation_input_tokens,说明你的前缀在变。加一条日志语句,在每次调用前打印渲染后系统提示的前 200 个字符——一个漂移的时间戳会立刻跳出来。
1 小时 TTL:何时值得付出额外的写入成本
默认 TTL 是 5 分钟。如果你的智能体运行频率很低——低于每 5 分钟一次——那么你将在大多数请求上支付缓存写入成本,却得不到读取。
// Opt into a 1-hour TTL
cache_control: { type: "ephemeral", ttl: "1h" }1 小时的写入成本约为基础输入价格的 2 倍,而非 1.25 倍。算一笔账:如果你每小时命中缓存 3 次或更多,那么 1 小时 TTL 能省钱。如果你的智能体每天只运行一次(比如我的每日简报),那么即使是 1 小时 TTL 也帮不上忙——你每次都在支付写入成本。在这种情况下,除非系统提示极其庞大,否则缓存带来的好处很有限。
我的每日简报智能体有一个 3,000 token 的系统提示,但每天只运行一次。缓存帮不上忙。我的新闻通讯智能体在起草过程中每个会话要运行几十次——缓存能省下相当可观的成本。
预热:让第一次请求变便宜
如果你已知即将到来的流量高峰——一个批处理作业、一次 API 发布——你可以用一个低成本的虚拟请求来预热缓存:
// Pre-warm: write the cache at near-zero output cost
await client.messages.create({
model: "claude-opus-4-8",
max_tokens: 1, // minimal output
system: [{ type: "text", text: stableSystemPrompt, cache_control: { type: "ephemeral" } }],
messages: [{ role: "user", content: "ping" }],
});
// Now the real requests read from cache这主要在批处理场景中有用——当你同时启动许多并行请求,并希望每一个都命中一个已预热的缓存,而不是争相去写入它。
智能体循环中的提示缓存
在多轮智能体循环中,对话历史在每一轮都会增长。缓存足够聪明,能处理这一点:它使用一个 20 块的回看窗口,在最近的 20 个内容块内寻找最长的匹配前缀。
实际含义是:把你的稳定内容(系统提示、工具定义)锚定在顶部。messages 数组末尾不断增长的对话历史不会破坏稳定块的前缀匹配——它们位于易变内容之前,而前缀匹配是从顶部开始的。
在实践中,我的智能体把轮次组织成这样:
System (cached) → Tools (cached) → Few-shot (cached) → Turn 1 → Turn 2 → ... → Current turn缓存覆盖到少样本标记为止的所有内容。它之后不断增长的轮次历史每次都会被重新处理,但这没关系——那些 token 是会话专属的,相对于稳定前缀来说很小。
它在账单上是什么样子
拿一个高频智能体来说:每天 100 次调用,4,000 token 系统提示,Sonnet 定价。
不使用缓存:
- 100 × 4,000 tokens × $3/1M = 每天 1.20 美元
使用缓存(5 分钟 TTL,假设高峰期每小时 50 次调用):
- 每 5 分钟 1 次写入 × $3.75/1M × 4,000 tokens = 写入约每天 0.02 美元
- 每天约 98 次读取 × $0.30/1M × 4,000 tokens = 读取每天 0.12 美元
那大约是对这些输入 token 的 90% 削减。当规模扩大时——每天 1,000 次调用——差距会进一步复合放大。而且这还是在任何模型路由节省之上的额外收益,参见 Haiku 与 Sonnet 的算账:缓存在每个层级都有效。
运营者的结论
提示缓存是 Claude API 中最简单的成本优化:在你本就要写的内容块上多加一个字段。约束在于围绕前缀稳定性的自律——缓存标记之前不能有任何动态内容。如果你能让系统提示、工具和任何静态示例都不含易变内容,那么每次缓存命中你只需支付正常输入成本的约 10%。对于拥有大型稳定提示的高频智能体来说,这是一个比切换模型层级更大的杠杆。
相关阅读: AI 智能体成本算账:Haiku 何时胜过 Sonnet · 事件触发型与定时型智能体 · 我真正用来经营业务的 5 个 AI 工具
每周三。28,400+ 读者。纯干货。
✓ 请查收邮箱 — 点击确认链接以完成订阅。
✓ 订阅成功!
✓ 您已在订阅列表中。
将AI实战手册发送到您的邮箱
每周三。28,400+ 读者。纯干货。
请查收邮箱。
我们已向您发送确认邮件 — 点击其中的链接以完成订阅。如果一分钟内没收到,请检查垃圾邮件。
订阅成功。
欢迎 — 下一期很快就会送达您的邮箱。
您已在订阅列表中 — 每周三留意查收。