实践中的缓存亲和:让 Agent 提示缓存保持热度的 5 种模式
你上线了一个 agent。月度 token 消耗直接爆炸。你换成"更便宜"的模型,账单却几乎纹丝不动。定价页上没人会印给你看的一件事是:在循环调用里,模型单价和"重复税"一比常常只是个零头——而重复税的本质,恰恰是个缓存亲和性问题。这篇是《为什么 agentic 系统应该关注缓存命中定价》的实战续篇。那篇论证了成本藏在缓存行为里,而不是原始模型价格里;这篇讲的是你实际能做什么。
前缀缓存真正奖励什么
大多数服务商(OpenAI、Gemini、Anthropic,以及构建在它们之上的 OpenAI 兼容网关)都提供自动前缀缓存:如果某次请求的提示词开头与上一次请求逐字节相同,那么被缓存的那部分 token 只按新输入 token 的一个零头计费。关键词是"逐字节相同",而且只有相同部分位于提示词最前面时才生效。调换一行顺序、在系统提示词上方插入一个时间戳、或者用一个全新的 UUID 重新序列化历史记录——你就亲手逐出了自己的缓存。模型根本感知不到这次命中——你只是在每一步都老老实实地付全额输入费。所以"换更便宜的模型"优化的是错误的数字。真正的杠杆是缓存亲和性:你的前缀在循环过程中有多稳定?
让缓存保持温暖的 5 个模式
-
稳定的系统提示词 + 固定的工具 schema 放在最顶部。 工具定义在步骤之间很少变化。把它们原封不动放到最前面,然后别再碰它们。
-
只追加历史记录。 不要每一步都重新序列化整段对话。维护一份标准化的完整对话记录,只做追加;让不变的前缀留在缓存里。
-
在稳定前缀之后注入易变上下文。 草稿区、检索到的文档、工具结果都没问题——只要它们位于系统提示词 + 历史记录之下,而不是之上。
-
把容易的 80% 路由到小模型,但保留共享前缀。 按场景路由是聪明的做法。只是别让小模型重新格式化前缀;不同的分词器会悄悄破坏缓存。
-
一个网关,一个规范化格式化器。 当三个子 agent 各自用自己的方式格式化"上下文"时,你会得到三个互不兼容的前缀,以及零缓存复用。集中化提示词组装。
5 种会击穿缓存的模式(O(n²) 陷阱)
每轮都重新发送整个对话。 框架如果每轮都完整重放记忆,整个会话的 token 消耗就是 O(n²)。前缀永远无法稳定。
在前缀里带上时间戳或 UUID 来重新序列化历史。 每次调用都更换 updated_at,等于每次都是新前缀,缓存永远无法命中。
把易变内容放在稳定前缀之前。 当前时间、请求 ID、trace ID——如果它们出现在系统提示词之前,就会污染下游所有缓存命中。
在模型间混用 tokenizer,却没有稳定的规范化形式。 循环中途切换模型,又不规范化前缀,缓存会重置,输入成本直接翻倍。
没有任何可观测性。 如果看不到每次请求是命中缓存还是未命中,你就无法判断自己犯了上面哪一条。在最大的成本杠杆上,你完全是在盲飞。
你缺少的那条 trace
能让其他一切变得可量化的修复,听起来很无聊,但至关重要:为每个请求生成一条 trace,报告每次调用前缀是否命中缓存,以及实际计费了多少 token、命中缓存了多少 token。
有了它,O(n²) 陷阱就会变成一行账单。你不再靠猜,而是像盯 p99 延迟一样盯缓存命中率。这就是「我们降低了模型成本」和「我们降低了 agent 成本」的区别。