一、引言:长期运行 Agent 的成本问题,最后都会变成缓存问题
原文链接:Lessons from building Claude Code: Prompt caching is everything
作者:Thariq Shihipar(Claude Code 团队 technical staff)
发布时间:2026 年 4 月 30 日
Claude Code 这类编程 Agent 和普通聊天机器人最大的区别,不是它会调用工具,而是它会在一个会话里持续积累大量上下文:系统提示、工具定义、项目规则、CLAUDE.md、代码片段、终端输出、文件 diff、历史决策,以及用户一轮又一轮追加的新要求。
如果每次模型调用都从头计算完整 prompt,那么会话越长,系统越不可用:输入 Token 成本持续上升,首 Token 延迟变长,用户订阅计划的 rate limit 也会被更快消耗。Anthropic 在这篇文章里给出的核心结论非常直接:长期运行的 Agent 产品之所以在经济上可行,很大程度上依赖 prompt caching。
这不是一个“后期优化项”,而是 Claude Code 从 agent harness 设计之初就必须纳入的基础设施。原文甚至提到,Claude Code 团队会像监控 uptime 一样监控 prompt cache hit rate;当命中率异常下降时,会触发告警,甚至按 SEV 级别处理。
下面这张图概括了 Claude Code 中 prompt caching 所处的位置:
图注:本文配图均为作者基于原文机制整理的概念示意,并非 Anthropic 官方架构图。
二、技术背景:为什么 Agent 比普通 Chat 更需要 prompt caching
普通 Chat 产品通常以“短对话 + 少量上下文”为主。即使没有缓存,每次请求的输入规模也相对可控。而 Claude Code 的典型使用方式完全不同:
| 场景 | 普通聊天助手 | Claude Code 这类编程 Agent |
|---|---|---|
| 会话长度 | 较短,主题容易切换 | 长时间围绕同一代码库推进 |
| 上下文组成 | 用户消息和回答为主 | 系统提示、工具 schema、项目规则、文件内容、命令输出、diff 等 |
| 上下文增长 | 相对缓慢 | 工具调用会快速堆积大量 Token |
| 成本结构 | 单次请求成本较稳定 | 越到后期,完整 prompt 越昂贵 |
| 延迟风险 | 主要来自模型推理 | 输入处理和缓存未命中也会显著影响延迟 |
对于 Agent 来说,很多上下文其实是稳定的:工具定义不会每轮都变,系统提示不会每轮都变,项目规则也不会每轮都变。真正动态增长的部分主要是 conversation messages 和工具输出。因此,prompt caching 的核心价值在于:把稳定前缀的计算结果复用起来,只为新增尾部内容付出完整处理成本。
可以把一次 Claude Code 请求想象成下面的结构:
这也是原文反复强调的布局原则:Static content first, dynamic content last. 稳定内容越靠前、越确定,多个请求之间可复用的前缀就越长,缓存收益也越大。
三、实现原理:prompt caching 本质是 prefix matching
原文给出了一个非常关键的技术事实:Claude API 的 prompt caching 依赖 prefix matching。也就是说,API 会从请求开头开始匹配,直到每个 cache_control breakpoint;只有前缀完全一致的部分,才能命中缓存。
这带来三个直接后果:
- 顺序比内容本身更重要:同样的工具定义,如果顺序随机变化,也会破坏前缀一致性。
- 前面的微小变化会放大为后面的大面积 miss:如果 system prompt 开头加入当前时间戳,后续所有内容都无法复用旧缓存。
- 稳定内容必须前置,动态内容必须后置:越动态的内容越应该放在 prompt 尾部。
可以用下面的流程图理解缓存匹配过程:
一个简化的概念模型如下:
# 概念模型:真实 API 行为以官方文档为准
request = [
stable_system_prompt,
stable_tool_definitions,
project_context,
session_context,
conversation_messages,
]
cacheable_prefix = request[:cache_control_breakpoint]
if cacheable_prefix == previous_cacheable_prefix:
reuse_cached_computation()
compute_only_new_suffix()
else:
recompute_from_first_changed_token()
这也是为什么 prompt caching 不是“加一个缓存开关”这么简单。它要求整个 Agent 的上下文组织方式都围绕“前缀稳定”来设计。
四、核心优势:降低成本、降低延迟、扩大上下文可用性
Prompt caching 对 Claude Code 的价值可以概括为四类。
4.1 成本优势:长会话不再每轮从零开始
在一个 100k tokens 的长会话中,如果每轮请求都重新处理完整输入,越到后期成本越高。缓存命中后,系统可以复用前面稳定内容的计算结果,只处理新增消息、工具输出和用户指令。
| 对比维度 | 缓存命中率低 | 缓存命中率高 |
|---|---|---|
| 长会话输入成本 | 每轮接近重新处理完整上下文 | 大量稳定前缀可复用 |
| 订阅计划 rate limit | 更容易被长会话消耗 | 可提供更宽松的使用额度 |
| 会话越长的边际成本 | 持续放大 | 主要由新增尾部内容决定 |
| 工程可预测性 | 成本波动明显 | 成本模型更稳定 |
原文明确指出,高 prompt cache hit rate 能降低成本,并帮助 Claude Code 提供更宽松的订阅计划 rate limits。这说明缓存命中率不仅是后端指标,还会直接影响产品形态。
4.2 延迟优势:用户感知的是“是否立刻开始思考”
编程 Agent 的用户体验很大程度上取决于首 Token 延迟。如果每轮都重新吞入巨大上下文,用户会感觉 Agent “卡住了”。缓存命中可以减少重复前缀处理,让模型更快进入新增问题的推理阶段。
4.3 上下文优势:让更长的 Agent 会话变得可用
Claude Code 的价值来自持续上下文:它知道前面读过哪些文件、做过哪些修改、哪些方案被否决、哪些测试失败过。如果没有缓存,长上下文会在成本和延迟上变得不可接受;有了缓存,长会话才具备工程可行性。
4.4 可靠性优势:缓存命中率成为生产指标
原文中最有启发的一点是:Claude Code 团队会监控 prompt cache hit rate,并在命中率下降时触发告警。这意味着缓存已经从“性能优化”升级为“可靠性指标”。
对 Agent 平台来说,下面这些指标应当一起观察:
| 指标 | 代表含义 | 异常时的常见原因 |
|---|---|---|
| Cache hit rate | 稳定前缀是否被复用 | prompt 顺序变化、工具集合变化、动态信息放在前部 |
| Uncached input tokens | 未命中部分的输入规模 | compaction 方式错误、模型切换、system prompt 改动 |
| First token latency | 用户感知启动速度 | 大量前缀重新计算、工具 schema 过重 |
| Cost per session | 单个会话总成本 | 长会话 miss、fork 不复用父上下文 |
五、最容易破坏缓存的三个细节
原文把 Claude Code 的缓存布局形容为“surprisingly fragile”。很多缓存事故并不来自大的架构调整,而来自看似无害的小改动。
5.1 在 system prompt 中加入详细时间戳
如果 system prompt 里直接写入当前时间,例如 current_time: 2026-05-03 01:07:23,那么每次请求的前缀都会变化。由于 system prompt 位于最前面,这个变化会导致后续工具定义、项目上下文、历史消息全部无法复用旧缓存。
更好的做法是:保持 system prompt 稳定,把动态信息放到后续 message 或 tool result 中。 Claude Code 使用类似 <system-reminder> 的消息机制传递临时状态,例如时间变化、文件被用户修改等。
5.2 非确定性地排列工具定义
工具定义是缓存前缀的一部分。如果工具列表来自 map 遍历、插件扫描或 MCP server 聚合,而排序不稳定,那么即使工具内容完全一样,也可能因为顺序变化导致缓存 miss。
工程上应当保证:
- 工具按稳定 key 排序;
- tool schema 序列化结果确定;
- 插件加载顺序不影响最终 prompt;
- 新增工具时评估对 cache hit rate 的影响。
5.3 会话中途切换模型或工具集合
原文特别强调:prompt caches are unique to models。不同模型之间不共享 prompt cache。一个反直觉例子是:如果你已经在 Opus 中进行到 100k tokens 的长会话,此时切到 Haiku 回答一个简单问题,可能并不更便宜,因为 Haiku 需要重新构建完整缓存。
同理,会话中途添加、删除或重排 tools,也是最常见的缓存破坏方式之一。直觉上,“只给模型当前需要的工具”似乎更省;但对长会话来说,动态裁剪工具集合会破坏前缀,反而更贵。
六、Claude Code 的关键设计模式
6.1 用 messages 表达状态变化,而不是修改 system prompt
Agent 运行过程中一定会遇到动态状态:当前时间变化、用户手动编辑了文件、某个命令刚刚失败、工作模式从执行切换到规划。Claude Code 的经验是,不要为了这些状态去改 system prompt,而是用消息把变化追加到上下文尾部。
这种设计背后的原则是:状态是动态的,规则是稳定的;动态状态应当出现在 prompt 尾部,稳定规则才适合放在 prompt 前缀。
6.2 Plan Mode:用工具建模状态迁移,而不是改变工具集合
Plan Mode 是原文中非常典型的案例。直觉设计是:进入 Plan Mode 后,只给模型 read-only tools,禁用编辑工具。但这会改变 tool set,从而破坏缓存。
Claude Code 的做法更缓存友好:
- 请求中始终保留完整工具集合;
- 把
EnterPlanMode和ExitPlanMode设计成工具; - 进入 Plan Mode 时,通过消息告诉 Agent 当前处于规划模式,不要编辑文件;
- 完成计划后由 Agent 调用
ExitPlanMode。
这个模式可以抽象为一句话:Use tools to model state transitions rather than changing the tool set. 对任何复杂 Agent 系统,这都是非常重要的架构原则。
6.3 MCP 工具很多时,用 defer_loading,而不是动态移除工具
Claude Code 可能接入 dozens of MCP tools。如果每次请求都发送所有完整 tool schema,输入会很重;但如果按需增删工具,又会破坏缓存。
原文提到的折中方案是 tool search 和 defer_loading:
- 始终发送稳定、轻量的 tool stubs;
- stub 中保留 tool name 和必要元信息;
- 标记
defer_loading: true; - 当模型通过 tool search 选中某个工具时,再加载完整 schema;
- 所有 stub 始终按确定顺序出现。
这解决的是一个典型矛盾:工具太多会增加 prompt 体积,工具动态裁剪会破坏缓存。 延迟加载让系统同时获得“工具列表稳定”和“完整 schema 按需加载”的好处。
七、Compaction:最容易掉进的成本陷阱
当上下文窗口接近上限时,Agent 通常需要 compaction:把历史对话总结成摘要,再用摘要替换原始消息,继续后续工作。
一个看似自然的实现方式是:单独发起一次 summarization call,用一个新的 system prompt,例如“请总结下面的对话”,然后把完整历史塞进去。原文指出,这正是成本陷阱:这个请求从第一个 token 开始就和父会话不同,因此无法复用父会话缓存,需要为整段历史支付 uncached input cost。
Claude Code 的解决方案是 cache-safe forking:
- fork 请求使用与父会话完全相同的 system prompt、user context、system context 和 tool definitions;
- 先放入父会话已有 conversation messages;
- 最后追加 compaction prompt,作为新的 user message;
- 这样 API 看到的前缀几乎等同于父会话最后一次请求,可以复用已有缓存;
- 同时预留 compaction buffer,确保有空间容纳压缩指令和摘要输出。
这条经验可以推广到所有 fork 类操作:总结、子任务、技能执行、旁路分析、审查任务,只要希望复用父会话上下文,就必须尽量共享父会话 prefix。
八、实际应用场景:不只是 Claude Code
Prompt caching 的经验并不只适用于 Claude Code。任何长期运行、上下文密集、工具密集的 Agent 系统,都可以借鉴这些模式。
8.1 企业代码助手
企业代码助手通常会注入大量稳定上下文:安全规范、代码风格、仓库结构、构建命令、内部 SDK 文档。如果这些内容每轮都变化或顺序不稳定,缓存命中率会很差。
推荐做法:
- 把组织级规范放在最前面,并保持版本稳定;
- 项目级规则放在组织规则之后;
- 用户本轮需求和工具输出放在最后;
- 对规则文件变更建立版本号,而不是每轮注入时间戳。
8.2 多 Agent 协作系统
多 Agent 系统常见做法是让主 Agent 把完整上下文复制给子 Agent。但如果子 Agent 使用不同模型、不同工具集合、不同 system prompt,就会失去父会话缓存。
更好的方式是:
- 主会话保持稳定;
- 子 Agent 通过 hand-off message 接收必要摘要;
- 简单任务用轻量 hand-off,避免复制 100k tokens;
- 需要完整上下文的 fork 操作尽量复用父会话 prefix。
8.3 MCP 工具平台
MCP 生态的工具数量可能迅速膨胀。一个企业内部 Agent 可能同时接入 Git、CI、监控、文档、工单、数据库、云资源等几十类工具。
此时不要在每轮按“猜测需要什么工具”动态裁剪 tool set,而应:
- 保持工具 stub 列表稳定;
- 使用 tool search 发现能力;
- 对完整 schema 延迟加载;
- 对工具定义排序和序列化做确定性约束。
8.4 长文档 / 长代码库分析
对于长文档分析、代码库审查、迁移规划等任务,用户往往希望 Agent 保留上下文并持续推进。Prompt caching 可以让长上下文的使用成本更接近“增量更新”,而不是“每轮重读整本书”。
九、工程落地清单
如果要把 Claude Code 的经验迁移到自己的 Agent 产品,可以从下面这份清单开始。
9.1 Prompt 布局清单
- 稳定 system prompt 放在最前面。
- 工具定义放在动态消息之前。
- 工具列表顺序确定,schema 序列化确定。
- 项目级规则放在会话级动态状态之前。
- 用户消息、工具输出、临时提醒放在尾部。
- 不在 system prompt 中放当前时间、随机 ID、临时状态。
9.2 工具设计清单
- 不在会话中途添加、删除或重排 tools。
- 用工具调用表达状态迁移,例如进入 / 退出规划模式。
- 工具过多时使用稳定 stub + 延迟加载,而不是动态裁剪。
- 新增工具时观察 cache hit rate 和 uncached input tokens。
9.3 模型与子任务清单
- 不在长会话中途随意切换模型。
- 如需低成本模型处理子任务,优先使用 hand-off message。
- fork 类请求尽量共享父会话 prefix。
- compaction 预留 buffer,避免上下文满了才被动压缩。
9.4 可观测性清单
| 监控项 | 建议告警方向 |
|---|---|
| Prompt cache hit rate | 低于基线或短时间突降时告警 |
| Uncached input tokens | 单会话异常升高时排查 prompt 变化 |
| Tool schema token 占比 | 工具膨胀时评估 defer loading |
| Compaction cost | 压缩请求成本异常时检查是否 cache-safe fork |
| Model switch rate | 长会话频繁切模型时评估是否破坏缓存 |
十、总结:把缓存当成 Agent 架构约束,而不是性能补丁
Claude Code 的 prompt caching 经验可以浓缩成五句话:
- Prompt caching 是 prefix matching:任何前缀变化都会影响后续缓存。
- 稳定内容前置,动态内容后置:prompt 顺序决定缓存收益上限。
- 用 messages 表达动态状态:不要为了临时状态修改 system prompt。
- 不要中途改变 tools 或 models:工具集合和模型都是缓存边界的一部分。
- fork 操作必须共享父会话 prefix:compaction、总结和子任务都要避免从零开始。
这篇 Anthropic 官方博客最值得借鉴的地方,不是某个具体 API 参数,而是它展示了一种 Agent 工程观:当 Agent 会话变长、工具变多、上下文变复杂,缓存命中率就会从性能指标变成产品可用性的底层约束。
对开发者而言,真正的问题不再是“要不要开启 prompt caching”,而是“你的 Agent 架构是否从第一天起就尊重缓存的工作方式”。如果答案是否定的,那么随着会话长度和工具规模增长,成本、延迟和稳定性问题迟早会一起出现。
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付