聊聊 Agent 开发中的 Prompt Cache

2026-04-23 10:01
Prompt Cache KV Cache Agent 开发 自回归模型 上下文设计 缓存命中率 上下文压缩
摘要:这篇文章从自回归模型原理出发,深入讲解大语言模型推理中的 Prompt Cache(KV Cache)机制,以及如何在 Agent 开发中围绕缓存命中率设计上下文结构。涵盖缓存友好的分层 Context 设计、常见失效踩坑、上下文压缩策略等实战要点,帮你从第一天就把成本优化纳入 Agent 架构。适合正在构建 LLM Agent 的开发者阅读。

做 Agent 开发,Prompt Cache 是绕不开的一环。我之前花了不少时间专门研究它,但知识点一直零散地记在脑子里,始终觉得没能真正吃透。于是这次索性从原理到实践完整梳理了一遍,算是给自己做一次系统性的总结。

自回归模型

目前主流的大语言模型(GPT、Kimi、GLM、DeepSeek 等)几乎清一色是自回归 decoder-only 架构。GPT 作为这一架构的奠基者之一,已经证明它在扩展性和生成质量上最为稳定。

大模型生成文本时采用自回归方式:逐个生成 token,每次生成都依赖之前所有已生成的 token。这意味着每次生成新 token 时,都需要重新计算所有历史 token 的注意力(Attention),产生大量重复计算。

示例:

生成第 1 个 token:计算 [A] 的注意力
生成第 2 个 token:重新计算 [A, B] 的注意力
生成第 3 个 token:重新计算 [A, B, C] 的注意力
...
生成第 N 个 token:重新计算 [A, B, C, ..., N] 的注意力 ← 冗余度 O(N²)

既然存在大量重复计算,自然就会想到如何消除这些冗余,这就引出了 Prompt Cache。

什么是 Prompt Cache?

大模型推理中的 Prompt Cache,底层机制是 KV Cache(Key-Value 缓存)。要理解 Key 和 Value 是什么,需要先了解注意力计算公式:

注意力计算公式

你不需要看懂公式的细节,只需要知道:在 Transformer 的注意力机制中,每个 token 经过投影后会得到三个向量——Query(Q)、Key(K)、Value(V)。

用图书馆检索来类比:

Q(Query):你的查询请求,"我想找什么信息",发出问题的一方 K(Key):每本书的索引标签,token 对外"表明自己是什么",用来和 Q 匹配相关度 V(Value):书的实际内容,真正被读取和汇聚的信息

简单说:Q 决定"我对谁感兴趣",K 决定"我能被谁关注",V 决定"被关注时我贡献什么"。

回到自回归生成:

  • 历史 token 的 K 和 V 是固定的,不会随新 token 的生成而改变
  • 只有新 token 的 Q,需要与所有历史 K 计算注意力分数

因此,历史 token 的 K、V 可以缓存复用;Q 每步都是新的,缓存没有意义。

每处理一个 token,就将其 K、V 向量保存到缓存中。生成下一个 token 时,直接读取缓存中的历史 K、V,只计算新 token 的 Q,再与缓存做注意力计算。以空间换时间,将平方复杂度降为线性,推理速度可提升数十倍乃至更多。

省下的计算量,就是低价推理定价的来源。

围绕 Prompt Cache 构建你的 Agent

了解了 KV Cache 的原理后,设计 Agent 时就应该把"尽可能命中 Prompt Cache"作为基本原则。高命中率不只是省钱,还直接影响服务的稳定性和响应速度。

Claude Code 将 Prompt Cache 视为基础设施,命中率是一等公民指标。Anthropic 内部对 Claude Code 的缓存命中率设有监控告警,命中率异常低通常意味着:

  • 请求结构频繁变化,导致缓存持续失效
  • 客户端或 SDK 侧存在 bug,导致前缀未对齐
  • 算力和成本超出预期

设计缓存友好的 Context

Context设计图

把 Context 分层来看:

虚线上方(System Prompt → Conversation History) 是 Prompt Cache 的命中区,这几层在一次会话里变化缓慢。越靠上越稳定——System Prompt 和 Tool Definitions 理想情况下整个会话保持不变,Long-term Context 偶尔更新,Conversation History 逐轮追加但前缀不变。只要每次请求的前缀与上次一致,Cache 就能命中。

Current User Input 是唯一的"新鲜输入":每次请求只有这一层是全新的,它驱动新 token 的 Q 生成,其他层提供历史 K/V 上下文——这正好对应 KV Cache 的工作原理。

输出回写到对话历史:每轮 Model Output(含工具调用结果)都会追加到 Conversation History,成为下一轮请求缓存前缀的一部分。对话越长,可复用的缓存前缀越长,每次只需计算最新几个 token 的 KV。这就是为什么高命中率对长会话 Agent 格外重要。

缓存失效的常见踩坑

System Prompt 混入动态内容:比如时间戳,导致每次请求都不同,缓存永远无法命中。

Tool Definitions 发生变动:顺序、数量、描述任意一处改变,都会让缓存失效。

Long-term Context 实时覆盖而非追加:每次用全新内容替换,导致前缀与上次完全不同,历史缓存全部作废。

Session Memory 替换而非追加:正确做法是在末尾增量追加新的状态片段,保持前缀不变。

中途切换模型:缓存是绑定模型的。很多人觉得当前任务简单就换便宜的模型,结果缓存全部失效,反而比继续用原模型更贵。

一个通用原则:越靠上的层,变化成本越高——它的变化会让其下所有层的缓存全部失效。设计时的基本思路是:

变化频率低的内容 → 尽量靠上
变化频率高的内容 → 尽量靠下
绝对动态的内容 → 只放在 Current User Input

例如任务中必须用到当前时间,不要修改 System Prompt,而是放到下一条用户消息里传进去。

上下文压缩

这个单拉出来说。

滚动窗口:会丢失早期重要信息,复杂场景不适用,短平快的任务问题不大。

摘要压缩:近期对话保留原文,只压缩远期内容。模型对近期 token 的注意力权重更高,压缩近期内容损失更大。

工具调用结果压缩:对话历史里最有价值的往往不是用户消息,而是工具返回的大块内容(搜索结果、API 响应、文件内容)。只需保留模型从中提炼的结论,原始返回可以截断或删除。

最后

Prompt Cache 并不是上线后才去补的优化项,而是从第一天设计 Context 结构时,就必须纳入考量的硬性约束。

这些还只是我对它原理与结构层面的粗浅理解,真正落地时还有大量细节要抠。结构搭对了,成本自然会降下来;结构没搭好,就算换成再便宜的模型,也填不上 token 消耗的窟窿。

顺带说句题外话:

同样是用 Claude Code 这类 Agent,有人 Token 消耗惊人,有人却能用得很省,本质上也是 Prompt Cache 思路的另一种体现。怎么跟模型对话、怎么压缩上下文、怎么拆分任务、怎么衔接过渡,其实都有讲究。

当然,尊贵的 Max 用户可以完全不用在意这些。

后面我也会慢慢整理 Claude Code 的实际使用技巧,有机会再分享一些实战心得。