
众所周知:
Agent = Model + Harness
模型可以选,Harness 也应该可以选。
Harness 现在俨然从之前的某个,进化到了 Agent 成败的关键(可以看下我们上周)。
今天我们就来好好拆解一下成为优秀 Harness 的第一步:Cache Hit(缓存命中)
对了,万一你的老板问你,为什么要研究缓存命中?
你要理直气壮地回答:
因为能省钱。🐶

根据 Evan 晒出的图:他使用 Pi Agent 跑 DeepSeek-V4-Flash,累计缓存命中率达到 99.93% 。按 DeepSeek 当天的公开价格,其中 9.47 亿个缓存命中输入 token 约为 2.65 美元;如果全部按未命中价处理,则约为 132.62 美元,正好相差 50 倍。
一、回顾 Transformer:缓存命中是什么
大模型处理一次请求,可以粗略分成两个阶段。
第一个阶段叫 prefill,预填充。模型要先读完系统提示词、工具定义、项目说明、对话历史和用户的新问题,并计算每个 token 的注意力状态。
第二个阶段叫 decode,解码。模型理解上下文之后,再一个 token 一个 token 地生成回答。
在 Transformer 的每一层注意力机制中,读过的 token 会形成 Key 和 Value。这些已经计算好的中间状态,就是 KV Cache。

KV Cache 不是上一次的答案,也不是可以打开阅读的文本。它更像模型读完材料后留下的计算笔记。
这里先解释一个后文会反复出现的词:前缀(Prefix)。
Agent 发给模型的请求,通常按照下面的顺序拼起来:
[系统提示词][工具定义][项目说明][对话历史][本轮新消息]
<---------- 可复用的公共前缀 ---------><-- 新增后缀 -->
所谓前缀,就是从第一个 token 开始,连续相同的一段内容。它不是关键词,也不是“意思差不多”:如果两个请求从开头到第 50,000 个 token 完全一致,那么这 50,000 个 token 就是它们的公共前缀;第 50,001 个 token 一旦不同,后面的内容通常就要沿着新的路径重新计算。
例如,上一轮请求是 A+B+C,这一轮是 A+B+C+D。只要 A+B+C 没变,Provider 就可能复用到 C,只计算新增的 D;如果 B 中间改了一个工具定义,从变化点开始的 B、C 和 D 通常都要重新计算。
所以,提示词缓存首先是一场“从头对答案”的游戏。开头越稳定,可复用的前缀越长;变化出现得越早,后面需要重算的内容就越多。
假设一个 Coding Agent 已经积累了 10 万 tokens 的对话历史,你只输入两个字——“继续”——没有缓存时,模型不能从“继续”直接开工。它要先重新读完前面 10 万 tokens,重新完成 prefill,才知道你让它继续什么。
命中缓存时,只要这 10 万 tokens 仍是符合 Provider 精确匹配规则的有效前缀,模型就可以复用对应的 KV 状态,只计算新增部分,再生成回答。
所以 Agent 的真实成本单位,往往不是“这次回答写了多少字”,而是:
为了回答这句话,模型又被迫从头读了多少已经读过的历史。
长前缀的 prefill 必须在第一个输出 token 出现前完成,既消耗算力,也拉长了首 token 等待时间。
这里还要区分两种完全不同的缓存:
Response Cache:问题相同,直接返回上一次的答案; Prompt Cache:复用相同输入前缀对应的 KV 状态,但答案仍然重新生成。
缓存命中多数指的是后者。在 DeepSeek 官方的上下文硬盘缓存文档[1]明确说明,新输出仍然通过推理生成,继续受到 temperature 等参数影响。
但上一轮回答到了下一轮,就会变成输入历史的一部分。只要它落在匹配前缀中,对应的 KV 状态就仍然可以复用。
二、模型供应商角度:缓存命中的技术
对直接提供推理的模型供应商来说,缓存不是慈善项目。
未命中时,Provider 要重新处理整段输入,完成 prefill,并生成新的 KV 状态。命中时,它只需找到已有缓存、读取对应状态,再计算新增后缀。
缓存当然也有成本:KV 会占用显存、主机内存或硬盘,供应商还要承担索引、搬运、路由和逐出。但与反复计算十万 tokens 的长前缀相比,复用通常便宜得多。

因此,命中越多,直接推理服务商通常越能减少重复计算,让同一批 GPU 处理更多请求。用户省钱,Provider 提高吞吐,电表也能稍微冷静一点。
不过,API 缓存价格并不是供应商硬件成本的公开账本。最终定价还包含缓存存储、网络、调度、逐出和利润。高命中通常更省计算,但不等于供应商必然赚得更多。
1. 缓存存储在哪
有的 Provider 把 KV Cache 放在 GPU 显存或主机内存中,读取快,但资源昂贵;有的使用本地硬盘或分布式存储,容量更大,调度却更复杂。
“缓存之王”——DeepSeek 公开采用“上下文硬盘缓存”;Anthropic 则将 KV 表示与哈希只保存在内存中;Gemini 的隐式缓存也位于 RAM。
存在哪里会影响速度与保留时间,但所有方案都认同一条规矩:缓存认精确前缀,不认“意思差不多”。工具换个顺序、系统提示词多一个时间戳,都可能让后面的历史重新计算。
2. 缓存策略:显式、自动与 TTL
自动缓存由 Provider 自己识别可以复用的公共前缀,调用方不需要单独创建缓存对象。DeepSeek 的硬盘缓存、OpenAI 的隐式断点和 Gemini 的隐式缓存都属于这一类。
显式缓存则由调用方标记缓存断点,或者创建一个独立的缓存对象。这样控制更精确,但也可能需要承担缓存写入溢价或存储费用。Anthropic 的 cache_control、OpenAI GPT-5.6+ 的显式断点,以及 Gemini 的显式缓存对象,都是这种思路。
TTL(Time To Live)是指缓存的存活时间。它只说明缓存可以保留多久,并不保证这段时间内一定命中:缓存仍可能因路由、内存压力或节点故障被提前逐出。
以 DeepSeek 为例,它默认对所有用户开启硬盘缓存,不需要显式创建;每次请求会在用户输入结束位置和模型输出结束位置形成缓存前缀单元。只有完整匹配相应单元,后续请求才能命中。其缓存是 best effort,构建需要数秒,停止使用后通常在数小时到数天内清理,并没有一个固定承诺的 TTL。
3. 各家缓存策略对比
下面的表格粗略的汇总了各家不同的缓存策略:
| Anthropic[2] | cache_control 开启,可自动管理或显式设置断点 | ||
| OpenAI[3] | |||
| Gemini[4] | generateContent API 可创建显式缓存对象 | ||
DeepSeek-V4-Flash 的命中输入价正好是未命中的 1/50。这并不代表完整任务能省 50 倍——输出、新增输入和未命中部分仍会正常收费——但它足以解释前面 2.65 美元与 132.62 美元的差距。
(DeepSeek 的涨价还没落地,惴惴不安😌……)
三、Harness 构建者角度:怎样保护缓存前缀
Coding Agent 每一轮发送的不只是用户刚输入的消息,还包括系统提示词、项目规则、工具 Schema、对话历史、工具调用和执行结果。好的 Harness 的任务,就是尽量让这些内容形成稳定、可复用的长前缀。
1. 缓存前缀与 Session
系统提示词、项目规则、常用工具的名称、说明、Schema 和排列顺序都应尽量稳定。时间戳、随机值、临时状态等动态内容放到后面。前缀越早发生变化,后面被连坐的 token 越多。
以 Pi 为例,它的 Session 采用的是树(tree)。用户可以回到旧节点,再从那里长出新的分支。

对人来说,它们仍然属于同一个任务;对缓存来说,不同分支只能共享分叉点之前的前缀。Session ID 只是路由线索,缓存真正认的是当前那条 token 路径。
所以,同一个 Session ID 不一定对应同一个缓存前缀;不同 Session 也可能继承几乎相同的前缀。Harness 不能只看会话名字,还要知道当前请求究竟走在树的哪一条分支上。
2. 工具组合
工具定义通常位于提示词前部。新增工具、修改 JSON Schema,甚至只改变排列顺序,都可能让后面几万 tokens 的历史失去缓存。

有的 MCP 选择动态加载,会造成为了少发送几百个工具定义 token,却让几万个历史 token 重新 prefill,相当于为了省停车费换了一辆车。
Pi 的思路是:模型原生支持时,让新工具从某条消息之后开始生效,而不是回头改写最前面的工具列表。稳定内容保持稳定,变化内容只往后追加。
3. Prune:删除历史不一定更省
许多 Harness 会 Prune(删除)旧工具结果,认为上下文越短,成本越低。
但如果被删除内容已经位于廉价的缓存前缀中,从中间删掉它,会让删除点之后的上下文重新 prefill。为了少传几百个旧 token,后面几万个 token 可能一起补票。

Prune 当然不是错。接近上下文窗口上限、未来还有很多轮对话、Provider 不给缓存折扣,或者长期无法命中时,裁剪可能更划算。
关键不是“删不删”,而是把 Compaction(上下文压缩)视为一次有计划的缓存重置,先比较未来节省能否覆盖眼前的重算。
4. Pi 的缓存优化原则
优化缓存之前,首先要能测量缓存。DeepSeek 会返回 prompt_cache_hit_tokens 和 prompt_cache_miss_tokens,输入命中率的计算方式是:
输出 token 不放进分母,因为它不是本轮提示词输入。
Pi 会显示缓存读取量、写入量和最新请求的命中率;/session 还可以查看累计命中率、成本,以及显著未命中造成的重新计费估算。看不见就无法优化,只在月底看到信用卡账单,那不叫可观测性,叫悬疑片。
把 Pi 的思路总结成四句话:
稳定输入保持稳定; 对话以追加为主; 只保留必要工具,工具尽量增量加载; 命中、写入和重新计费必须可见。
四、实际使用:缓存怎样变成钱
1. 优化缓存命中,省下的钱最后归谁
对长会话 Agent 来说,在模型与任务不变的前提下,提高缓存命中率往往是当前最直接、收益最大的省钱方法之一。
如果 API 明确按缓存输入折价收费,命中率越高,使用者通常会得到两个直接收益:
输入 token 更便宜; 重复 prefill 减少,首 token 通常来得更快。
注意是“通常”,不是无条件。
固定订阅用户未必直接看到 token 账单;部分网关按统一输入价收费,不会把缓存折扣传给用户;缓存主要改善 prefill 和首 token 等待,也不会让后续 decode 吐字速度按命中率同比起飞。
而 Coding Agent 又比普通聊天更依赖缓存。它每轮发送的不只是你刚输入的“再改一下”,还有系统提示词、项目规则、工具 Schema、对话历史、工具调用和执行结果。
你看到的是四个字,模型收到的可能是一份十万 token 的项目档案。
这也是为什么选 API 或中转服务时,不能只看“输入每百万 token 多少钱”,还要问:
账单是否区分缓存命中与未命中; 是否透传 Provider 的缓存 usage 字段; 缓存折扣是否真的传给用户; 长会话是否会在不同模型或节点之间漂移。
缓存命中在不同商业模式里的重要程度并不一样:
直接调用 Provider,或中转商按命中价透传计费:命中率越高,用户账单越低,省下的钱直接归用户; 上下游都只按统一 token 单价结算:缓存命中不会改变眼前的计费数字,服务商自然没有太强的动力专门优化它; 上游按缓存命中价收费,下游仍按普通输入 token 收费:命中缓存产生的价差会成为服务商的利润空间。以普通 token 的价格卖出缓存 token,Harness 的缓存优化就会直接影响毛利。
这不等于中转服务收费不合理,它还承担路由、支付、并发和运维成本。但计费口径是否透明、缓存 usage 是否透传,应该成为选择服务商的一项指标。
还要提醒一句:99.9% 缓存命中不是 Harness 的总分。
它不代表模型更聪明,也不保证代码写得更好。它只说明一件很重要的事:已经读过的材料,大部分没有重新付费再读。
2. 缓存表现变差的常见原因
缓存命中率突然下降,通常从下面几项开始排查:
空闲超过 TTL:长时间构建、测试、开会或吃饭,都可能让缓存过期。回来只说一句“继续”,账单却以为你让模型重读《战争与和平》; 提示词前部出现动态内容:系统提示词里加入时间戳、随机值或不断变化的状态,会让不匹配点提前; 切换 Session 分支:rewind、fork 或树形导航会改变当前 token 路径,只能复用分叉点之前仍被保留的前缀; 工具组合变化:新增、删除、重排工具,修改名称、说明或 JSON Schema,都可能从提示词前部破坏缓存; Prune 或 Compaction:从中间删除、压缩或重写历史,会让修改点之后的内容重新 prefill; 切换模型或 Provider:KV 状态通常不能跨模型、跨供应商迁移; 扩展改写旧消息:本地 Session 看起来没变,不代表扩展最终发给 Provider 的 payload 没变; Provider 路由或逐出:即使前缀完全一致,缓存也可能因为节点变化、内存压力或服务端逐出而未命中。
如果 Provider 提供更长 TTL,要比较缓存写入溢价与后续命中收益;也没必要为了保活而发送无意义请求。省 token 的第一原则,仍然是别制造没必要的 token。
本文编译自 Earendil Engineering:Prompt Caching in Agents[5] 十分建议阅读原文!
上下文硬盘缓存文档: https://api-docs.deepseek.com/zh-cn/guides/kv_cache/
[2]Anthropic的缓存策略: https://platform.claude.com/docs/en/build-with-claude/prompt-caching
[3]OpenAI的缓存策略: https://developers.openai.com/api/docs/guides/prompt-caching
[4]Gemini的缓存策略: https://ai.google.dev/gemini-api/docs/caching
[5]Earendil Engineering:Prompt Caching in Agents: https://earendil.com/posts/prompt-caching/