Anthropic 的最新播客,你需要了解的 Prompt Caching 的一切

Claude Code 围绕 Prompt Caching 构建,Anthropic 团队在官方 Blog 中分享了他们在Claude Code 建设中总结的缓存策略,如果你也在建设 Agent,你也应该从一开始就把缓存纳入设计。这篇文章是对原文的理解和总结~

背景:Prompt Caching 为什么如此重要

为什么重要:从 LLM 推理的两个阶段说起

LLM 每次处理请求时,内部分为两个阶段:

  1. Prefill(预填充) :并行处理输入的所有 token,为每个 token 计算它与其他所有 token 的注意力关系(attention),产生一组 KV Cache(Key-Value 缓存)。输入越长,这个阶段越慢、越贵。
  2. Decode(解码):基于 Prefill 产生的 KV Cache,逐个生成输出 token。每生成一个新 token,只需计算它与已有 KV 的关系,成本极低。

Prefill 是成本大头。一个 100k token 的提示词,Prefill 可能耗时数秒,而 Decode 生成每个 token 只需毫秒级。

在多轮对话中,每次新请求都会把之前的全部内容再发一遍------系统提示(System Prompt)、工具定义、历史消息拼在一起,形成一串很长的序列。这串序列从开头到某个位置的连续片段,就叫「前缀」------必须从开头开始,中间不能跳。如果上一轮和这一轮的前缀完全一样,那部分内容的 KV Cache 就可以直接复用,不用重新 Prefill。

Prompt Caching 的本质就是跨请求复用前缀的 KV Cache。 模型仍然「看到」完整文本,输出不会有任何差异------变的只是你等多久收到回复,以及 API 收你多少钱。

打个比方:每次做一套 100 题的卷子,你都必须从第 1 题看到第 100 题。但第一次做时,你把前 80 题的详细推导写在了草稿纸上。第二次考试时,你仍要看完整套卷子,但前 80 题直接复用草稿不用重新推导。

缓存就像考试时的草稿纸------你不是少看了题目,而是少做了重复推导。

在哪一层生效

在开发原生 Agent 的时候会调用 LLM API,你的代码大概长这样------没有任何缓存

python 复制代码
import requests

response = requests.post(
    'https://api.example.com/v1/chat/completions',
    json={
        'model': 'gpt-4',
        'messages': [
            {'role': 'system', 'content': 'You are a helpful assistant.'},
            {'role': 'user', 'content': 'Hello!'}
        ]
    }
)

而 Claude Code 内部调用 Anthropic Claude API 可能长这样:

python 复制代码
from anthropic import Anthropic
client = Anthropic()

response = client.messages.create(
    model='claude-3-5-sonnet-20241022',
    max_tokens=1024,
    system=[
        {'type': 'text', 'text': 'You are a helpful assistant.'},
        {'type': 'cache_control', 'cache_type': 'ephemeral'}  # ← 缓存标记
    ],
    messages=[...]
)

通过 cache_control 标记告诉 API 平台「前面的内容请缓存」。API 会缓存从请求开始到每个 cache_control 断点之间的所有内容。

最佳实践:五条缓存优化策略

以下是 Claude Code 团队基于这个约束总结,在harness中实践缓存的五条策略。

对提示词排序:越不变的越靠前

前缀匹配是严格的逐字节比对,第 5 页改了一个逗号,后面 95 页的 KV Cache 全部作废,那么提示词里各模块的排列顺序就直接决定了缓存效率。Claude Code 的排序原则是:越不变的东西越靠前,越常变的东西越靠后。

具体排序如下:

  1. 静态系统提示词(System Prompt) + 工具定义(极少变动,全局共享)
  2. CLAUDE.md(项目范围内变动,跨会话共享)
  3. 会话上下文(同一次会话内共享)
  4. 对话消息(每轮都在增长)

这个顺序让尽可能多的会话共享同一段前缀。在 Claude Code 中,cache_control 断点被策略性地放在这些层级之间:系统提示词和工具定义之后是全局缓存(所有用户共享);CLAUDE.md 之后是项目级缓存(跨会话共享);会话上下文之后是会话级缓存(同一次对话内共享)。每一层断点都把前面稳定的内容「冻结」,只让后面动态增长的部分逐轮变化。

「Cache rules everything around me」------ 对 Agent 而言,缓存规则确实统治了一切。

用消息传递更新,而不是修改系统提示

通过分层 cache_control 确实极大程度上增加了缓存的命中,但是 Claude Code 团队自己也承认:这个约束比想象中脆弱------有时候确实有修改静态系统提示词(System Prompt)或者工具定义的诉求。

当会话中的信息发生变化------比如当前时间推进了、用户修改了一个文件------直觉的做法是更新系统提示词。但这样做会摧毁前缀,导致整个会话重新 Prefill。(还记得吗,系统提示词位于前缀的最开头,改一个字符就意味着从这个字符开始就不匹配了)。

Claude Code 的做法是:在下一条用户消息或工具结果中插入 标签,将增量信息通过消息流传递给模型。这样静态前缀(系统提示词、工具定义等)保持不变,缓存继续生效,模型依然能在最新的上下文中工作。

python 复制代码
# 不好的做法:修改 system prompt(破坏缓存前缀)
system = 'You are a helpful assistant. Current time: 2026-05-07 10:00'

# 好的做法:在下一回合的消息中传递更新
messages = [
    {'role': 'user', 'content': '帮我改这段代码'},
    {'role': 'assistant', 'content': '...'},
    {'role': 'user', 'content': '<system-reminder>\n用户修改了文件 app.py,当前时间 2026-05-07 10:00\n</system-reminder>\n\n继续优化'}
]

缓存失效的代价,往往比你想象的高一个数量级。一次「顺手」的系统提示词更新,可能让整段长对话的缓存全部作废。

不要在会话中途切换模型

Prompt Caching 是模型级别的。如果你在一个 100k token 的 Opus 对话中,想临时切到 Haiku 处理一个简单问题,直觉上 Haiku 更便宜------但实际上 Haiku 需要从零重建整个 prompt cache,反而比让 Opus 直接回答更贵。

正确的做法是使用子代理(subagent):让当前模型准备一份「交接摘要」,把任务分派给另一个模型的独立会话。子代理有自己的缓存前缀,不会干扰父会话的缓存。Claude Code 的 Explore agents(使用 Haiku)就是这样实现的。

在一次会话中切换模型不是「换更便宜的计算器」,而是「换一张全新的草稿纸」。

不要在会话中途增删工具

工具定义位于缓存前缀的最前面,增加或删除任何一个工具都等于重写了前缀------整段对话的缓存全部作废。这看起来有点反直觉:难道不应该只给模型它当前需要的工具吗?但事实上缓存的约束比「整洁」更重要。

Plan Mode:用工具表达状态转换

Claude Code 的 Plan Mode 是这个原则的经典实践。按理来说,Plan Mode 只能读,不能写,那么我在进入Plan Mode时应该只给模型保留「read_file」工具,防止它乱改代码。但如果真的这样做,就等于在会话中途替换了一套工具定义------前缀从第一行开始就变了,缓存彻底失效。

Claude Code 的解法是:始终保留全部工具定义,把进入 Plan Mode(EnterPlanMode) 和 退出 Plan Mode(ExitPlanMode)本身也设计成工具。当模型调用 EnterPlanMode 时,它会收到一条系统消息,说明「你现在处于计划模式,只能读取不能修改,完成后再调用 ExitPlanMode」。工具定义从头到尾一个字都没变,缓存前缀完全不受影响。

python 复制代码
# 工具定义始终不变,Plan Mode 本身也是一个工具
tools = [
    {'name': 'read_file', 'description': '读取文件内容', 'input_schema': {...}},
    {'name': 'write_file', 'description': '写入文件内容', 'input_schema': {...}},
    {
        'name': 'EnterPlanMode',
        'description': '进入计划模式,此时只能读取不能修改文件',
        'input_schema': {'type': 'object', 'properties': {}}
    },
    {
        'name': 'ExitPlanMode',
        'description': '退出计划模式,恢复正常编码',
        'input_schema': {'type': 'object', 'properties': {}}
    },
    # ... 其他所有工具始终保留
]

这还有一个额外好处:因为 EnterPlanMode 是模型可以自主调用的工具,当它遇到困难问题时,可以自己进入计划模式思考,完全不需要用户干预,也不会造成缓存中断。

defer_loading:用存根替代移除

同样的原则也适用于 MCP 工具管理。Claude Code 可能加载了几十个 MCP 工具,全部展开放在请求里太贵,但中途移除又会破坏缓存。

Claude Code 的解决方案是 defer_loading:不移除工具,而是发送轻量存根(只有工具名,标记 defer_loading: true)。模型需要时通过 「tool search」工具发现完整定义。这样缓存前缀始终稳定------相同的存根、相同的顺序、永远不变。

把状态转换建模为工具调用,把工具精简建模为延迟加载------核心原则只有一个:前缀不能动。

安全压缩:fork 时必须复用父前缀

当对话越来越长,最终会触及上下文窗口的上限,这时需要「压缩」(compaction)------把之前的对话总结成一段摘要,然后用摘要替代原始消息继续对话。

最常见的陷阱是:新开一个 summarization 调用,用一条简洁的系统提示(比如「请总结以下内容」),并且不带任何工具。这样做的问题在于, summarization 调用的前缀从第一个 token 就和原会话不同(系统提示变了、工具没了),所以整段对话都无法命中缓存。你不仅要为 summarization 本身付费,还要为重新发送全部对话历史支付全额费用------而且对话越长,这笔费用越高。

Claude Code 的做法是「cache-safe forking」:压缩调用使用与父会话完全相同的系统提示、用户上下文、系统上下文和工具定义,仅把压缩指令作为最后一条用户消息追加。从 API 的角度看,这个请求和父会话的上一个请求几乎一模一样------同样的前缀、同样的工具、同样的历史------所以缓存被完整复用。唯一新增的 token 只有压缩指令本身。

python 复制代码
# 不好的做法:不同的 system prompt(前缀从第一个 token 就分歧)
summary = client.messages.create(
    model='claude-3-5-sonnet',
    system='请总结以下对话',  # 前缀不同!
    messages=conversation_history,
)

# 好的做法:cache-safe forking,复用完全相同的前缀
summary = client.messages.create(
    model='claude-3-5-sonnet',
    system=original_system,  # 和父会话相同
    tools=original_tools,    # 和父会话相同
    messages=[
        *conversation_history,
        {'role': 'user', 'content': '请把以上对话总结成一段摘要'}
    ]
)

压缩不是「另起炉灶」,而是「在文件堆的顶部放一张文件总结」。前缀保持一致,缓存才能继续生效。

核心结论

  1. Prompt caching 是前缀匹配------任何变动都会使之后全部内容失效
  2. 静态内容前置、动态内容后置,是最大化缓存复用的唯一正确排序
  3. 用消息(如 )传递更新,永远不要修改系统提示词
  4. 不要中途切换模型;需要分派任务时用子代理
  5. 不要中途增删工具;用工具调用建模状态转换,用 defer_loading 替代移除
  6. 压缩 / fork 等旁路操作必须使用与父请求完全一致的缓存前缀
相关推荐
LienJack5 小时前
《Claude Code 源码解析系列》第7章|Skill
claude·源码阅读
LienJack5 小时前
《Claude Code 源码解析系列》第6章|MCP
claude
LienJack5 小时前
《Claude Code 源码解析》第4章|Context 管理
claude
LienJack5 小时前
《Claude Code 源码解析系列》第5章|Tools 总览
claude
LienJack5 小时前
《Claude Code 源码解析系列》第8章|Agent 协作
claude·源码阅读
LienJack5 小时前
《Claude Code 源码解析系列》第3章|Prompt 编写
claude·源码阅读
求索实验室6 小时前
让AI真正"看见"界面:纯视觉GUI自动化编排器开源了
github·agent
来一斤小鲜肉7 小时前
单手掌控Claude Code(一)
ai编程·claude
夜雨深秋来7 小时前
多租户 AI Agent 平台架构设计与实践
架构·langchain·agent