Prompt caching 本身并不减少任何东西。当一次请求渲染后的前缀与服务商已经处理过的某个前缀匹配时,它会复用已有工作;不匹配时,它静默失败。所以实践归结为三个问题:什么算作前缀、什么会破坏匹配、以及你如何验证命中真的发生了。本指南用你在生产中最可能遇到的两种机制回答这三个问题,并把机制扣在各家服务商自己的文档上,而不是传说上。用得好,它也是少数能在不改变产品行为的前提下降低 LLM API 成本的杠杆之一,但它是你第二个要修的东西,永远不是第一个。
Prompt caching:两种机制,一条规则
OpenAI 是自动缓存的。按照该服务商的 prompt caching 指南,缓存会复用你请求渲染后的前缀,其中可以包含文本、图片、文档和受支持的音频,而复用要求整个渲染前缀完全匹配。前缀不只是你的第一条消息:它由隐藏的 system 内容、工具定义与 schema、developer 指令以及对话历史拼装而成,大致按这个顺序。如果内容或相关设置在某个断点之前发生变化,该变化之后的一切都无法与已有条目匹配。存在一个最小可缓存长度,它随模型而异,所以当前阈值属于官方页面,而不属于你的记忆。
Anthropic 则靠显式标记工作。它的 prompt caching 文档描述了两种模式:自动缓存——一个顶层cache_control字段把断点放在最后一个可缓存块上,并随对话增长向前移动;以及在你想要细粒度控制时,在个别内容块上显式设置 cache_control 断点。有两点机制在实践中很重要。写入只发生在你的断点处,而哈希是累积的,所以改动断点处或之前的任何块,都会让下一次请求产生不同的哈希。读取则从你的断点向后看,检查更早的位置是否存在先前请求已经写入的条目。这种不对称正是关键:一次请求每个断点最多写入一个缓存条目,之后每个匹配该哈希的请求都复用它,而标记之后的一切都会被重新处理。把标记放对,读取自然跟上。
管道不同,规则同一条:请求中稳定的部分必须放在前面,所有易变的东西必须放在它们之后。缓存生命周期选项和可缓存长度阈值按服务商分别记录,并且会随时间变化,所以请到各家页面上读当前值,而不是从任何地方照抄,包括不要照抄本文。如果你通过 String AI 这样的兼容平台消费模型,平台的文档就是约定,说明哪些行为在其端点上得以保留。
Prompt 缓存不生效?那些静默的破坏者
缓存未命中不会抛错。它只是把一切重新处理一遍,而你唯一的线索是用量数据里的某个字段。下面是破坏前缀最常见的几种写法,每一种都配了修法。
前缀里带时间戳、UUID 或随机种子。任何每次请求都变的东西,都会毒化它之后的每一个 token。症状:prompt 看起来一模一样,命中率却接近零。修法:把易变的值移到断点之后,或者干脆去掉。
用户输入被拼在稳定指令前面。把用户的问题拼在 system 规则之前,意味着从第一个易变 token 起前缀就不同了。症状:system prompt 看起来被缓存了,其实没有。修法:冻结模板的前部,把动态内容追加到最后。
每次请求都改工具定义。OpenAI 的指导很直接:改动工具名称、描述、schema 或顺序都会改变前缀,而工具位于整摞内容的靠前位置。症状:缓存未命中开始于某人“整理”工具列表的那一天。修法:把工具块作为一个整体做版本管理,别随手重排它。
多模态顺序不稳定。图片和文档也属于渲染后的前缀,所以同样的素材以不同顺序附加就是不同的前缀。症状:未命中与某个不保留附件顺序的 UI 相关联。修法:发送前确定性地排序附件。
每轮都会变的上下文块。滚动摘要、运行中的笔记,或者每次调用都被重写的“当前状态”大块内容,会破坏它所触及的前缀,即使周围的对话没变。症状:多轮会话在第一轮之后就再也不命中。修法:如果它必须变,就放在断点之后,只缓存真正持久的东西。
断点放在了变量翻涌的错误一侧。Anthropic 的文档精确描述了这个失败模式:断点落在一个每次请求都变的块上,你就每次都在付一次全新写入的钱,永远读不到命中。症状:有缓存写入,没有缓存读取。修法:把断点移到在多次请求之间保持稳定的最后一个块上。
一个习惯囊括上面六种修法:把前缀当作一个前部冻结的模板,并给这个前部一个你能在自己日志里看到的版本号。在模板旁边留一段短说明,写清哪些部分可能变化、哪些被冻结、断点落在哪里,这样下一位工程师不必靠盯着命中率崩塌来重新发现同样的规则。
在你自己的用量数据里验证缓存命中
会读缓存 token 的用量,是“相信”和“知道”之间的差别。验证回路很短,值得按这个顺序跑一遍。
- 读那些字段。在 OpenAI 的响应里,缓存输入出现在 usage 对象的 token 明细中,前缀首次被存储时还会带有缓存写入的记账。Anthropic 把缓存写入和缓存读取作为两种不同事件报告,这很方便,因为读取计数持续增长正是你想要的签名。
- 把同一个前缀跑两次。相同前缀、两次调用,比较两个 usage 对象。第二次调用就是命中必须出现的地方;如果两者看起来一样,那前缀其实并不相同。如果字段在变却从不累积,先回头检查上一节说的断点位置,再去改别的东西。
- 做一个最小 A/B。一个变体保持前缀不动,另一个变体插入单个变量,然后比较。在接上代码之前,先在终端里跑一次小调用,这就是快速上手里的同一种纪律,因为测机制时,最小复现胜过完整应用。
- 记录一个指纹。对你发送的前缀做哈希,并把哈希记在缓存字段旁边。命中率一变,两个指纹的 diff 就告诉你什么变了,这胜过回去做代码考古。
- 定期审计。定期对照你的用量记录确认读取真的在发生,而不是相信一个几个月前设好、再没碰过的看板。还要记住,用量字段和响应形状在不同端点上有差异,而这正是兼容性清单存在的意义:把这种差异钉死。
如果你在 OpenAI 上,还有一个额外的工具:cache key 可以把缓存记账按用户或工作流分开,让验证和按客户解释都更干净。
什么时候缓存不值得做
缓存奖励的是前缀结构重复的负载,说实话,不是每种负载都符合条件。
- 前缀确实是每次请求都唯一的。长尾的个性化 prompt,断点之前的一切都各不相同,没有任何可复用之处,再怎么调缓存也改变不了。
- 每个产物只调用一次。没有后续一次生成,就没有第二个请求来命中缓存。
- prompt 很短。低于服务商的最小可缓存长度,就根本没有合格的缓存条目,而阈值随模型而异,所以去查官方页面,不要凭假设。
- 你还没测过命中率。给一个你从没量过的负载加缓存层,就是团队增加复杂度然后称之为优化的方式。先测量:如果你的稳定前缀在生产日志里其实并不稳定,那该修的是前缀纪律,不是一个缓存项目。
- 你的客户端在跟你作对。缓存取决于客户端实际发出的内容;那些在请求之间注入 session 标识符、重排附件或重写指令的工具,会破坏前缀,无论你的模板有多好。先修客户端,否则缓存会成为一个永久的谜。
接下来做什么
- 在调优之前先看清花费。 用量与成本归属是本文所假定的测量层。
- 给故障分诊。当缓存的表现怪异、看起来像报错时,错误码指南会把层次分开,包括那些永远不值得重试的。
- 前缀在哪里被塑造。客户端和工具配置是这一切的上游,Cursor 端点演练就是那一层的具体例子。
先修前缀,用你自己的用量数据验证,然后才谈缓存省下了什么。