你知道什么是 Prompt Caching 吗

14 阅读10分钟

工程界常说"缓存决定一切",而这一法则同样适用于 AI Agent。对于一个要频繁调用大模型的系统来说,推理成本和响应延迟是绕不开的两座大山,而 Prompt Caching 恰恰是撬动这两个问题的关键杠杆。今天我们就来好好聊聊它。

什么是 Prompt Caching,它是怎么工作的?

先从一个直觉上的比喻入手。

想象一下,你雇了一位大厨。

  • 没有 Caching: 你每次叫他炒菜,都得先花 10 分钟教他你的口味偏好、厨房用具在哪、盐放哪个罐子里。每次都从头开始,效率极低。
  • 有了 Caching: 大厨已经把这些背景记在脑子里了。你只需要说一句:"来个回锅肉,照旧。"他瞬间就能开火。

落到模型推理上,道理是一样的。Prompt Caching 的核心思路是:对于已经出现过的内容,我们直接复用此前的计算结果,只把精力放在真正新的内容上,从而提升响应速度、降低推理成本。

那它底层是怎么实现的呢?要搞清楚这个,我们得先捋一捋三个容易混淆的概念:模型Transformer推理引擎,以及它们各自在整个链条里扮演什么角色。

模型(大厨的脑子/知识)

它是经过海量数据训练后的静态结果,也就是我们常说的权重文件。里面存着几千亿个参数,决定了 AI 看到"天亮了"这三个字之后,概率最高的回应是"起床了"而不是"下雨了"。简单来说,它是 AI 的"知识库",但本身只是一堆静止的数字,没有任何主动执行的能力。

Transformer(菜谱/原理)

它是 AI 的底层逻辑框架,规定了模型应该如何"关注"上下文中的关键词,也就是我们熟知的 Attention 机制。你可以把它理解成"如何思考"的数学公式——模型有了知识,但得靠 Transformer 来告诉它怎么用。

推理引擎(厨房/执行环境)

它是真正干活的软件,比如 vLLM、TensorRT-LLM 这类工具。它负责把庞大的模型加载到 GPU 显存里,然后按照 Transformer 定义的逻辑来驱动硬件完成计算。而 Prompt Caching 的核心调度逻辑,就由它来负责。

搞清楚这三者之后,我们再来看当一条请求进来时,推理引擎内部到底发生了什么。

1. 切片与指纹识别(Hashing)

推理引擎拿到 Prompt 之后,不会整体处理,而是先将其"切片"——把整段提示词分成若干小块(通常以几十个 Token 为一块)。然后为每一块单独计算一个哈希值,就像给每块拼图盖上一个唯一的"指纹章",方便后续快速比对。

2. 贪婪匹配(Searching)

接下来,引擎会去缓存池里搜索:"有没有人之前计算过跟这一块指纹完全一样的内容?"

这里有个值得关注的细节:匹配策略是贪婪的,从 Prompt 的开头开始逐块比对。只要前面的指纹对得上,就直接调用已经存好的 KV Cache(也就是中间的计算结果),跳过重新计算这一步。匹配到的块越多,节省的计算量也就越大。

3. 用完即存(Caching)

如果遇到的是全新内容,引擎就老老实实地完整跑一遍计算,过程中会产生大量中间数据。而这些数据并不会在给你返回答案后就直接丢弃,引擎会默认尝试把它们存进显存。那些被反复命中的内容自然会长期留着,而那些很久没人访问的内容则会被自动淘汰——有点像操作系统的内存页面置换。

4. 瞬间复用(Reusing)

下一次有新的请求进来时,只要 Prompt 的前缀和之前的一样,引擎就能直接跳过那些沉重的计算步骤,响应速度快得像"瞬间移动"一样。

注意: 前缀一致是触发缓存的前提。只要开头改了一个标点符号,哈希值就全变了,对应的缓存也随之失效。

作为应用层开发者,我们如何用好 Prompt Caching?

理解了底层原理,我们自然就能推导出一条关键结论:Prompt Caching 是通过前缀匹配来工作的,因此你放置内容的顺序非常重要——你希望尽可能多的请求共享同一个前缀,让缓存命中率尽量高。

但话说起来容易,实际开发中有哪些可以直接落地的技巧呢?我们不妨来看一看 Claude Code 团队在工程实践中总结的六条经验:

技巧一:静态内容前置,动态内容后置

这是设计提示词结构最基础的原则,没有之一。越稳定的内容越往前放,越容易变化的内容越往后放。Claude Code 的分层结构是这样的:

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

技巧二:用消息传递动态更新,而非修改系统提示词

实际开发中经常会遇到这种情况:系统提示词里有一些会随时间变化的信息,比如当前时间、用户最近修改的文件内容等。直觉上你会想直接去改系统提示词,但这样做会让整段缓存失效。

更好的方式是,在下一轮对话的用户消息里附上这些更新。Claude Code 的做法是在用户消息中追加一个 <system-reminder> 标签,把动态信息(比如"现在是星期三")传递给模型。这样前面的系统提示词前缀完全没被动,缓存自然保住了。

技巧三:会话中途不要切换模型

这一条很多人容易踩坑。Prompt Caching 是和具体模型绑定的,不同模型之间的缓存不能共用。如果你在用 Opus 的对话里已经积累了 10 万 Token 的缓存,这时候切换到 Haiku,之前的缓存全部作废,还得重新为 Haiku 构建一遍——这反而可能比继续用 Opus 还贵。真的有需要切换模型的场景,建议通过子智能体(Subagent)来隔离处理。

技巧四:绝不在会话中途添加或移除工具

工具定义是缓存前缀的一部分,只要中途动了工具集,整段对话的缓存就全完了。

Claude Code 的 Plan Mode 就是个很好的反面教材和正面示范。进入计划模式时,你可能会想"反正只读不写,把写入工具全去掉就好了"——但这样做会直接破坏缓存。Claude Code 的实际做法是:所有工具始终保留在请求里,把 EnterPlanModeExitPlanMode 本身也作为工具加进来,切换模式时只是让 Agent 收到一条行为约束的指令("你可以探索,但不要修改文件"),工具定义本身动都不动。

技巧五:工具按需加载,而非动态移除

这条和上一条是一个硬币的两面。把所有 MCP Tools 都塞进每次请求,Token 消耗太高;但是根据需求动态移除工具,又会破坏缓存。两头都是坑,怎么办?

Claude Code 团队的解法是延迟加载:提示词中只带轻量级的工具描述(相当于一个目录),当模型根据需求确认要用某个工具时,再把完整的工具定义和使用说明加载进来。既控制了 Token 消耗,又没有动缓存前缀的核心结构。这个思路和现在大热的 Skill 机制其实是同一个道理。

技巧六:上下文压缩时,复用前缀结构

随着对话越来越长,上下文窗口迟早会撑不住,这时候就需要对历史对话做压缩总结。这个操作很容易把缓存前缀破坏掉。

Claude Code 的处理方式是:把压缩后的摘要当作一条新的用户消息追加到最末尾,而系统提示词、工具定义、历史记录这些前置内容全部保持不动。这样,前面所有已经缓存好的前缀依然可以正常命中,只是对话的"尾巴"换了一条新消息而已。

逻辑层的缓存

聊完推理层的 Prompt Caching,我们再往上走一层,看看在应用逻辑层还能做什么。

其实除了依赖底层的 KV Cache,我们在开发 Agent 时还可以在自己的业务逻辑里构建一层缓存:对于语义相似的问题,直接从缓存里返回答案,完全跳过 LLM 调用。这跟数据库查询里的结果集缓存是一个思路。

在展开之前,有一个很容易混淆的概念值得先说清楚:逻辑层缓存RAG 到底有什么区别?

  • 逻辑层缓存(Result Reuse): 它更像是 Agent 的"记忆力"。核心逻辑是——"这个问题我以前答过,答案是 A,直接给你。"
  • RAG(Knowledge Retrieval): 它更像是 Agent 的"业务手册"。核心逻辑是——"这个问题我没见过,但我知道去哪查,翻完手册再告诉你。"

一句话总结:缓存复用的是"结果",RAG 检索的是"依据"。

我们用一个电商 AI 客服的场景来具体感受一下。

场景一:高频重复问题的"秒回"

  • 用户 A 问:"怎么申请退款?"
  • 系统把这句话转化为向量 V1,在向量存储里没找到相似记录,于是走 RAG 流程生成答案,并把 (V1, 答案) 这个键值对存入 Redis。
  • 用户 B 问:"我想退钱,怎么操作?"
  • 系统把这句话转化为向量 V2,和 V1 做相似度比对,发现两者高度相似,判定是同一个问题的不同表达。
  • 结果:直接从缓存取出答案返回,响应时间从几秒缩减到毫秒级,全程没有触发 LLM,成本为 0。

场景二:冷门长尾问题的"现场翻书"

  • 用户 C 问:"这款 2026 款手机的内屏保修政策在极寒地区是否有特殊条款?"
  • 系统生成向量 V3,去缓存里比对,发现即使是最近邻的向量,相似度也低于阈值——显然这是一个从未见过的问题。
  • 逻辑层放行,将 V3 转交给 RAG 管道。RAG 在知识库里做第二次相似度检索,找到匹配的产品说明片段,最后交由 LLM 整合成自然语言的回答。

当然,逻辑层缓存也不是万能的。如果你的业务本身问题就很多样、很具体,缓存命中率天然偏低,强行加一层反而增加了系统复杂度,得不偿失。这一层该不该加,还是得结合实际的业务场景来评估。


写到这里,整篇文章差不多也就收尾了。从推理引擎底层的 KV Cache 机制,到应用层的六条工程技巧,再到逻辑层的语义缓存,Prompt Caching 在不同的层面有着不同的形态,但背后的核心思路始终是一个:尽量复用已有的计算结果,把算力留给真正需要新鲜推理的地方。

如果你正在搭建自己的 Agent 系统,希望这篇文章能给你一些实际可以拿来用的参考。我也会持续把这类实践经验整理成文章分享出来,感兴趣的话可以关注一下。