在 Coding Agent 里,缓存不是优化项,而是架构地基
做 Coding Agent 的人,迟早会盯上同一个指标:缓存命中率。
原因很直接。Coding Agent 是典型的「长上下文 + 多轮迭代」场景——读文件、跑命令、改代码、再读再改,每一轮都要把完整历史重新发给模型。历史越长,重复付费越多。
业界对这件事的重视程度,可能超出你的想象:Manus 团队曾公开表示,如果只能选一个指标,KV 缓存命中率是生产级 AI Agent 最重要的单一指标;Anthropic 的 Claude Code 团队更是对命中率设置了告警——一旦命中率过低就触发严重事件(SEV),和对待系统宕机一个级别。
更关键的是,在 Coding Agent 里,缓存不是「优化项」,而是「架构地基」。你的提示词结构、工具设计、会话管理方式,几乎都要围着它设计。
这篇文章就把「怎么把命中率做上去」讲清楚。
所有优化都从这一条衍生而来:
缓存匹配是逐字的、从前向后的。请求开头只要有任何一个 token 变了,从变化点往后的全部内容都得重算。
一个典型的 Agent 请求长这样:
[tools 工具定义] → [system 提示词] → [项目规则 CLAUDE.md] → [对话历史] → [工具返回结果] → [本轮用户消息]
只有这段序列的前面部分与上次请求逐字一致时,匹配上的那一段才能按缓存价计费。
三家主流厂商的实现方式略有不同,先看清差异:
| 维度 | Claude | OpenAI | DeepSeek |
|---|---|---|---|
| 控制方式 | 显式:用 cache_control 指定断点 | 自动:前缀够长(1024 token 起)自动尝试 | 默认开启,无需改代码 |
| 命中条件 | 断点之前内容逐字一致 | 精确前缀匹配 | 需先完整持久化前缀单元 |
| 缓存读取价 | 约 0.1x(写入 5m 为 1.25x、1h 为 2x) | 提供折扣读价 | 价差最大,可达数十倍 |
| 有效期 | 默认 5 分钟,可选 1 小时 | 通常 5–10 分钟不活跃失效 | 数小时至数天自动清理 |
一句话:Claude 最可控但工程要求最高,OpenAI 最省心但控制感弱,DeepSeek 价差最猛但依赖链路透明。
如果你直连 API,写个固定 prompt 很容易命中。但 Coding Agent 没那么简单——它已经不是一个 agent,而是一整套调度系统:要管工具、压缩上下文、切模式、拉子任务。
麻烦就出在这里:请求前缀里塞满了工具定义、项目规则、权限模式、MCP schema、git 状态、历史消息……该长期稳定的和每轮都变的,混在了一起。
四个反复出问题的环节:
此外,还有一个容易被忽略的坑:统计口径。很多框架统一成简单格式,只留一个总输入 token,cache_creation 与 cache_read 全被抹平,你根本看不到缓存到底有没有生效。
这是最基础、也最重要的一条。Claude Code 的请求是分层的:
| 层 | 内容 | 缓存粒度 |
|---|---|---|
| 最外层 | 静态 system 提示词 + 工具定义 | 全局缓存(跨会话共享) |
| 第二层 | 项目规则(CLAUDE.md) | 项目内缓存 |
| 第三层 | 会话上下文 | 会话内缓存 |
| 末尾 | 对话消息 | 不缓存(每轮都变) |
越稳定的越往前放,越易变的越往后放。cache_control 断点要打在稳定内容之后,这样后面的用户问题随便变,前面那段前缀仍能复用。
「现在是星期三」「用户刚改了某个文件」这类信息,直觉上是直接改 system 提示词。但这么做代价极大——整段缓存直接失效。
正确做法:把动态信息作为一条消息追加在对话末尾。Claude Code 的做法是在用户消息里加一个标签注入这类信息,前缀完全没动,缓存自然保住。
由此得出一条黄金规范:长期规则写进项目文件(CLAUDE.md),临时变化全部进对话消息。
理由前面已经说了——缓存按模型隔离。真想用不同模型,正确姿势是用子代理(subagent)做侧活:主线模型不动、长前缀不动,子代理独立跑自己的缓存。
工具一多,光工具定义就吃掉大量 token,而且经常变动。Claude Code 的解法是「延迟加载」:默认只发轻量的工具存根(工具名 + defer_loading: true 标记),模型真正需要时再通过一个「工具搜索」工具去发现完整定义。
这样即使你装了个新插件,也不会把整个前缀打翻。
多代理不是简单「多发几份请求」。让子代理复用主会话已建立的稳定前缀,能让「派 5 个子 agent 并行干活、账单却几乎只算一份」成为可能——这也是 Claude Code 架构里最值得学的设计之一。
这一条特别精妙。当历史太长需要压缩时,天真做法是另起一个 prompt、把历史拍平发过去——这次调用必然全部 miss。
正确做法是:复刻会话原本的「系统提示词 + 工具定义 + 历史」前缀,只在尾部追加一句「请把以上内容压缩成摘要」。于是这次摘要调用的前缀,正是上一轮刚被缓存的那一段,压缩本身也命中了缓存。
把日常操作按「是否摧毁缓存」分级,执行起来最直观:
| 级别 | 操作 | 影响 |
|---|---|---|
| 🔴 高危 | 中途换模型、增删工具 / 重载 MCP、改项目规则文件、装新 Skill、闲置超 TTL | 缓存几乎必废 |
| 🟡 中危 | 改 thinking 开关 / 推理预算、重载插件 | 视实现而定 |
| 🟢 低危 | 正常对话(只在末尾追加消息)、侧线探索走 subagent、反复读同一文件 | 基本安全(文件内容也能被复用) |
Claude 场景下最该盯的是两个字段的比例:
cache_creation_input_tokens(写入)cache_read_input_tokens(读取)判断逻辑很简单:
单看总 token 极易误判:输入看起来很多,但如果大部分是 cache read,实际非常便宜。
很多人把前缀优化做得很漂亮,最后却在接入层翻了车。
原因在于,经过中转站后,缓存信息可能直接消失——不少 OpenAI 兼容接口会把 usage 统一成简单格式,只留一个总 input token。而更麻烦的是三层「猫腻」:
影响有多大?算笔账就清楚了。设普通输入价为 1、缓存读取为 0.1:
| 方案 | 计算 | 实际单位成本 |
|---|---|---|
| 3 折、命中率 50% | (0.5×1 + 0.5×0.1) × 0.3 | 0.165 |
| 6 折、命中率 95% | (0.05×1 + 0.95×0.1) × 0.6 | 0.087 |
结论很反直觉:折扣只作用在明面单价上,而命中率改变的是计费基数。长上下文任务里,命中率差一点,价格排序就可能反过来。
所以选接入方式时,至少看三件事:
这也正是向量云在解决的问题:通过火山方舟官方 API 与独享推理接入点,让请求路由固定、缓存空间隔离——缓存不被其他用户挤占,前缀才能稳定命中,独享场景下命中率可达 70%~90%,而不是公共资源池里随波逐流的 20%~40%。
获取 API Key:https://ark.tokenrize.cn/ · 注册即享专属 Token 折扣
一句话总结:前缀稳定性管住前半段,独享接入点守住后半段,命中率才是省出来的。
把全文压成四句话,可以直接贴到团队规范里:
最后回到那句话:在 Coding Agent 里,缓存不是优化项,而是架构地基。你越早把它当成设计的第一性原理,后面要还的技术债就越少。
面向大模型应用落地的技术服务型企业,依托字节跳动火山方舟能力,为合作方提供低折扣模型 Token、可独立部署的运营支撑平台及全链路运营支持。
火山方舟官方 API 与官方 Key 直连 · 向量云 · 按量计费享 Token 折扣
A. 因为 Agent 的请求前缀里混入了大量动态内容:每轮变化的 system prompt(时间、cwd、git 状态、权限模式)、易变的 tool schema,加上中途切模型、compaction 与 subagent 改写前缀,都会让原本稳定的前缀失效。
A. 缓存是按模型隔离的,切换后缓存完全不通用。原模型上攒的稳定缓存需要全额重建,重建的输入成本通常远大于模型单价差。正确做法是用 subagent 做侧活,主线模型保持不变。
A. 不要另起 prompt 把历史拍平。应复刻会话原本的「系统提示词 + 工具定义 + 历史」前缀,只在尾部追加「请压缩成摘要」的指令,这样压缩调用本身也能命中上一轮建立的前缀缓存。
A. 看 cache_creation_input_tokens(写入)与 cache_read_input_tokens(读取)的比例:写入高、读取低说明前缀不稳;读取高而普通输入低说明缓存生效。只盯总 token 容易误判。