揭秘prompt缓存不生效的真实原因——多数情况下不是模型问题,而是工作流(n8n/LangChain等)在每次运行中悄然改变了prompt前缀的token序列,导致精确匹配失败。
我有一个看起来非常适合 prompt 缓存的 agent 工作流。
相同的 developer prompt。相同的工具。相同的任务形态。长期静态指令。理论上,它应该会随着时间推移越来越便宜。
但每一次请求都表现得像一次全新的请求。
这让我掉进了兔子洞,而答案却简单得令人恼火:
大多数 prompt 缓存不工作的情况,都不是模型的问题,而是工作流的问题。
如果你在使用 GPT-5、Claude,或通过 n8n、Make、Zapier、OpenClaw、LangChain 或你自己的编排层使用任何 OpenAI 兼容的技术栈,你的 工作流很可能在每次运行时会悄悄改变 prompt 前缀。
这会直接杀死缓存。
人们对 prompt 缓存的误解
很多人谈论 prompt 缓存时,总觉得它是按"意图"工作的。
实际上它按"精确渲染后的前缀匹配"工作。
这意味着 provider 问的不是:
"这基本上是同一个 prompt 吗?"
而是:
"在到达缓存边界之前,前缀的字节/ token 完全一致吗?"
这个区别就是一切。
OpenAI 的文档说得很清楚:缓存取决于完整的渲染上下文,包括系统指令、developer messages、工具定义和对话历史。
Anthropic 的文档用略有不同的方式说了同样的内容:prompt 缓存取决于匹配的前缀和缓存断点,无论你是依赖自动行为还是显式的 cache_control。
所以如果你的应用说"相同的 prompt",但你的中间件在前缀稳定部分结束前注入了一行内容,你并没有"相同的 prompt"。
你得到了一个缓存未命中。
我的"相同 prompt"里实际在变化什么
这才是令人沮丧的部分。
如果我在调试 UI 中查看面向用户的 prompt,它看起来完全相同。
但发送到 GPT-5 或 Claude 的实际 payload 到处都是微小的突变。
常见的缓存杀手
这些是我在实际 agent 堆栈中不断看到的问题:
其中任何一个都可能破坏缓存命中。
而且如果突变发生在早期,之后的所有内容也变得 effectively uncached。
这就是为什么 prompt 顶部附近的动态内容如此昂贵。
OpenAI 让人容易误解这一点
OpenAI 说 prompt 缓存在支持的模型上默认启用,缓存的输入 token 最高可享受 90% 的折扣。
这听起来很棒,当你的 prompt 前缀稳定时确实很棒。
但很多开发者听到"默认启用"后会在心里把它翻译成:
"我的 agent 自动在省钱。"
这就是事情开始跑偏的地方。
相同 session 不等于相同的缓存命中。
相同的 prompt 模板不等于相同的渲染前缀。
代码中相同的工具列表不等于网络上相同的工具 payload。
一种非常正常的 OpenAI 失败模式
你通过 Responses API 调用 GPT-5。
你保持了相同的 developer message 和相同的工具。
然后你的中间件在请求发出前添加了:
{
"trace_id": "run_2026_09_22_14_03_18",
"timestamp": "2026-09-22T14:03:18Z"
}
或者你的工具 schema 以不同的键顺序重新生成。
你的应用以为它发送了相同的 prompt。
OpenAI 没有收到相同的渲染前缀。
这意味着没有缓存命中。
Anthropic 有一个不同的陷阱:时间
Anthropic 的 prompt 缓存很可靠,但默认的 ephemeral 缓存生命周期是 5 分钟。
这在多步骤自动化中非常重要。
如果你的工作流流式传输一个长响应,等待 Airtable,调用一个 webhook,然后回来进行后续请求,你可能已经消耗了大部分或全部的缓存窗口。
Anthropic 还提供更长的写入层,但大多数团队记住了这个功能而忘记了时间。
一种正常的 Claude 失败模式
想象一个使用 Claude Opus 的 n8n 或 Make 流程:
加载一个巨大的静态指令块
等待另一个系统
发送期望复用的后续请求
如果时间过去太长,缓存条目就没了。
没有东西损坏。只是你的工作流把 5 分钟缓存当成了永久资产。
基本结构如下:
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-1",
max_tokens=1024,
cache_control={"type": "ephemeral"},
system="You are an assistant that follows a fixed review rubric.",
messages=[
{"role": "user", "content": "Review this PR summary."}
]
)
但如果你的自动化很慢,或者你的前缀在缓存断点之前发生了变化,省钱效果就消失了。
如何判断你的堆栈是否在破坏 prompt 缓存
不要检查你应用中的漂亮 prompt 模板。
检查完全渲染后的请求。
如果你用 Node.js 构建,在发送前记录最终请求体。
import fs from "node:fs";
function logRenderedRequest(payload: unknown) {
fs.writeFileSync(
`./debug/request-${Date.now()}.json`,
JSON.stringify(payload, null, 2)
);
}
然后对比两个应该相同的请求。
diff -u debug/request-1.json debug/request-2.json
或使用 jq 规范化后比较:
jq -S . debug/request-1.json > debug/sorted-1.json
jq -S . debug/request-2.json > debug/sorted-2.json
diff -u debug/sorted-1.json debug/sorted-2.json
如果你看到时间戳、重新排序的键、变化的包装器,或工具定义在移动,那就是你的问题。
Prompt 缓存是真实存在的。
只是它极其字面。
有效的模式无聊但可靠:
把稳定的静态内容放在前面
把易变的内容移到后面
对结构化数据进行确定性序列化
停止向前缀注入每次运行都变化的内容
如果你想要一个实用的规则:
在任何其他操作之前,审查每个请求的前 1,000 个 token。
那里通常是问题所在。
我现在信任的三条规则
不要在可缓存块之前注入时间戳、trace ID、请求 ID 或调试文本。
不要在每次调用时重建工具定义——除非你必须这样做。
{
"system": "You are a support agent. Timestamp: 2026-09-22T14:03:18Z"
}
这不会命中缓存。
{
"system": "You are a support agent."
}
这会命中缓存。
如果你需要时间戳,把它放在后面的 user 或 metadata 块中,不要破坏静态前缀。
如果你发送 JSON blobs、工具 schemas 或配置对象,使它们具有确定性。
这意味着稳定的键顺序和稳定的序列化。
function stableStringify(obj: unknown): string {
return JSON.stringify(sortKeys(obj), null, 2);
}
function sortKeys(value: any): any {
if (Array.isArray(value)) return value.map(sortKeys);
if (value && typeof value === "object") {
return Object.keys(value)
.sort()
.reduce((acc: Record<string, any>, key) => {
acc[key] = sortKeys(value[key]);
return acc;
}, {});
}
return value;
}
然后重用相同的序列化输出,而不是每次运行都重新生成等价但不同的对象。
把静态指令放在前面。
把动态状态往后推。
这是减少上下文浪费最简单的方法之一,而且不会让 agent 变笨。
实践对比:OpenAI vs Anthropic prompt 缓存
OpenAI 的默认启用行为很方便,但也让人变得懒惰。团队假设节省是自动的。
Anthropic 强迫你更认真地思考缓存边界和时间,这很烦人,但通常会带来更好的 prompt 架构。
没有一个 vendor 能从你混乱的编排层中拯救你。
缓存未命中总是 bug 吗?
有时前缀确实需要改变。
问题是意外缓存破坏——没人想过这些东西会有影响:
那是纯粹的自找苦吃。
调试缓存未命中的快速清单
如果 prompt 缓存"不工作",这是我使用的清单:
# 1. 捕获最终请求 payload
# 2. 比较应该匹配的两次运行
# 3. 在 diff 之前排序 JSON 键
# 4. 检查前 500-1000 个 token 的波动性
# 5. 检查工具定义是否有重排
# 6. 检查中间件是否有时间戳/trace ID
# 7. 对于 Anthropic,检查请求之间的已用时间
工程清单版本:
修复方案简单得令人尴尬
一旦我不再把 prompt 缓存当作魔法,而是把它当作严格的字符串匹配契约,问题就变得不那么神秘了。
之后,"坏缓存"问题看起来就不那么像模型行为了,而更像普通工程问题。
这就是主要教训。
如果你的 agent 堆栈不断错过 prompt 缓存命中,不要从责怪 GPT-5、Claude、OpenAI 或 Anthropic 开始。
首先假设你的工作流以某种愚蠢的小方式在改变前缀。
因为很可能确实是这样。
还有一个实践角度:当缓存工作时,成本可预测性仍然很重要
Prompt 缓存有帮助,但它不能解决 agent 团队更大的预算问题:
你仍然要一直考虑 token 经济。
你仍在关注上下文增长、缓存命中率、重试、工具循环和长期运行的工作流,就像它们是财务负债一样。
这是运行大量自动化的团队转向 Standard Compute 这样的固定费率 API 设置的一个大原因。
Standard Compute 是一个面向 agent 和自动化的插入式 OpenAI 兼容 API,具有可预测的月度价格和无限 AI 计算。它与现有 OpenAI SDK 配合使用,可无缝集成到 n8n、Make、Zapier、OpenClaw 和自定义工作流中。
如果你的团队为了保持 agent 24/7 运行而不断做 prompt/成本计算,可预测的定价实际上和 prompt 优化一样有用。
缓存仍然重要。良好的工作流设计仍然重要。
但不必为每个 token 惊慌也是一个相当不错的升级。