工程领域常说“缓存主宰一切”,这一原则同样适用于智能代理。

像Claude Code这样需要长时间运行的智能代理产品,得益于提示缓存(prompt caching),它让我们能够重复利用之前交互的计算结果,大幅降低延迟和成本。

在Claude Code中,我们的整个系统架构都围绕提示缓存设计。高缓存命中率不仅降低成本,还能让我们为订阅用户提供更宽松的调用限制,因此我们会监控缓存命中率,若过低则触发严重事件(SEV)。

以下是我们在大规模优化提示缓存过程中总结出的一些(常常不直观的)经验。

设计提示内容以利缓存

提示缓存示意图

提示缓存基于前缀匹配——API会缓存请求开头直到每个cache_control断点的内容。因此,内容顺序极其重要,尽量让更多请求共享相同前缀。

最佳做法是先放静态内容,后放动态内容。Claude Code的顺序是:

  1. 静态系统提示和工具(全局缓存)
  2. CLAUDE.md(项目内缓存)
  3. 会话上下文(会话内缓存)
  4. 对话消息

这样可以最大化多个会话共享缓存命中。

但这种方式也很脆弱。我们曾因多种原因破坏过顺序,比如在静态系统提示中加入详细时间戳、非确定性地调整工具顺序、更新工具参数(如Agent工具可调用的代理)等。

用消息传递更新信息

当提示中的信息过时(例如时间变化或用户修改文件)时,直接更新提示会导致缓存失效,增加成本。

可以考虑通过下一轮对话消息传递更新信息。在Claude Code中,我们会在下一条用户消息或工具结果中加入``标签,向模型传递更新内容,从而保持缓存。

会话中不要切换模型

提示缓存是模型特定的,这让缓存机制变得不直观。

例如,在与Opus模型进行10万token的对话后,若想问一个简单问题,切换到Haiku模型反而更贵,因为需要重建Haiku的缓存。

若必须切换模型,最佳做法是使用子代理。例如,部署一个子代理让Opus准备“交接”消息,转给另一个模型处理任务。Claude Code的探索代理就是这样使用Haiku的。

会话中不要增减工具

中途更改工具集是破坏提示缓存的常见原因。虽然直觉上只给模型当前需要的工具,但工具是缓存前缀的一部分,增减工具会使整个对话缓存失效。

用计划模式设计缓存友好功能

计划模式是围绕缓存限制设计功能的典范。直觉做法是进入计划模式时切换为只读工具集,但这会破坏缓存。

我们始终保持所有工具在请求中,通过EnterPlanMode和ExitPlanMode作为工具本身。当用户切换计划模式,代理收到系统消息说明当前处于计划模式及相关指令:探索代码库,不编辑文件,计划完成时调用ExitPlanMode。工具定义始终不变。

这还有额外好处:因为EnterPlanMode是模型可调用的工具,模型能自主进入计划模式处理复杂问题,无需破坏缓存。

用工具搜索延迟加载而非移除

同理,Claude Code加载数十个MCP工具,全部包含在请求中成本高,但中途移除工具会破坏缓存。

解决方案是defer_loading。我们发送轻量级工具存根(仅工具名,带defer_loading: true),模型通过工具搜索按需发现并加载完整工具定义。这样缓存前缀稳定,存根顺序不变。

你也可以通过API使用工具搜索简化操作。

在不破坏缓存的情况下进行压缩

压缩示意图

压缩是当上下文窗口不足时,将对话总结并开启新会话继续。

压缩与提示缓存交互复杂。压缩时需将完整对话发送给模型生成摘要,最简单做法是单独调用API,使用不同系统提示且无工具,但这会导致缓存前缀完全不同,缓存失效,需支付全额费用。对话越长,成本越高。

解决方案:缓存安全的分叉

压缩时,我们使用与父对话完全相同的系统提示、用户上下文、系统上下文和工具定义。先附加父对话消息,再在末尾添加压缩提示作为新用户消息。

从API角度看,该请求与父对话最后请求几乎相同——前缀、工具、历史一致,缓存得以复用。唯一新增的是压缩提示本身。

这要求我们保留“压缩缓冲区”,确保上下文窗口有足够空间容纳压缩消息和摘要输出。

压缩虽复杂,但你无需自行摸索——基于Claude Code经验,我们已将压缩功能内置于API,方便你在应用中使用。

总结经验

构建智能代理时,优化提示缓存的几个关键模式:

  1. 提示缓存基于前缀匹配。前缀任何变化都会使后续缓存失效。设计系统时务必遵守此约束,正确排序即可大幅提升缓存效果。
  2. 用消息替代系统提示修改。避免直接修改系统提示(如进入计划模式、更新时间等),改为在对话中插入消息。
  3. 会话中不更换工具或模型。用工具模拟状态切换(如计划模式),延迟加载工具而非移除。
  4. 像监控运行时间一样监控缓存命中率。缓存失效会显著影响成本和延迟,需及时报警处理。
  5. 分叉操作需共享父前缀。执行侧计算(压缩、总结、技能调用)时,使用相同缓存安全参数,确保缓存命中。

Claude Code从一开始就围绕提示缓存构建,建议你在构建智能代理时也采用类似策略。

立即开始使用Claude Code

本文作者为Claude Code团队技术成员Thariq Shihipar。