--- name: kv-cache-design description: 优化 Agent 推理延迟与成本、诊断首 token 变慢或缓存命中率下降时使用——KV Cache 前缀不变性原则、三条铁律、五种破坏缓存的错误上下文管理模式、Chat Template 与历史思维链回传策略、缓存作为架构约束(缓存边界、子 Agent 字节级对齐、替换字符串冻结)。 --- # KV Cache 友好的上下文设计 KV Cache 把前文 token 的键值对(K/V)缓存下来,下一轮只计算新增部分。**前提是要复用的 token 前缀保持不变**——若序列从某位置开始不同,首个不同 token 及其后的 KV 状态需重新计算,此前位置不受影响。跨请求的对应机制叫 Prompt Cache。一行看似无害的动态代码,可能让整条推理链路慢一个量级。 ## 何时使用 - 诊断首 token 延迟(TTFT)升高、推理账单翻倍、`cached_tokens` 命中率低 - 设计 Agent 的上下文布局(哪些内容进 system、哪些追加到末尾) - 决定子 Agent 派生方式、工具定义加载方式、会话恢复机制 - 处理历史思维链(`reasoning_content` / thinking block)的回传与跨模型轨迹迁移 - 评估「动态信息该怎么送进上下文」的任何技术方案 ## 核心原则 - **铁律一:系统提示词和工具定义一旦确定就不要改。** 任何改动,哪怕多一个空格,都可能改变 token 序列,使首个不同 token 及其后的缓存无法复用;改动越靠前,重新计算和计费的 token 越多,延迟影响通常越大(实测可达数倍)。 - **铁律二:动态信息永远追加到末尾。** 时间戳、用户状态、TODO 进度等变化内容作为新消息追加到对话末尾,而不是修改已有的系统提示词。 - **铁律三:使用标准 API 格式,不要自行拼接消息。** 结构化消息经 Chat Template 翻译成模型训练时见过的固定 token 序列;自行拼成 `USER: ... ASSISTANT: ...` 的根本问题是偏离训练格式,会削弱多步思考能力。 - **Transformer 层是串联的**:第 k 个 token 变化,k 之前状态不受影响,从 k 开始的表示逐层受影响——缓存只能保留到首个不同 token 之前。 - **无缓存时 prefill 注意力计算量随上下文长度平方级增长**;有缓存时省去历史 K/V 投影重算,但每个新 token 的注意力仍要遍历全部缓存 K/V(线性增长)——这是长上下文解码变慢、显存带宽成为瓶颈的原因。 - **Prompt Cache 的读取成本远低于首次计算**,约为十分之一(Anthropic、DeepSeek、GPT-5 量级);各家启用方式与计费细节差异大,使用前查最新文档。 - **缓存是架构约束,不是事后优化。** 越早纳入设计,后续工程代价越小。 ## 实践模式 ### 1. 上下文分层布局 ``` [ system prompt ][ tool definitions ] 静态前缀,字节级稳定,跨请求/用户/会话缓存 [ user / assistant / tool ... ] 轨迹,只追加不修改 [ 状态栏 / 新工具 schema / 动态信息 ] 追加到末尾 ``` - 每个运行时条件(OS 类型、当前模式、用户偏好)若放在缓存边界之前,就会把缓存键的变体数量翻倍;N 个二值条件产生 2^N 种组合(3 个条件 = 2x2x2 = 8 种缓存键)。**所有动态元素放到边界之后。** - 提示词的排列顺序首先由缓存的经济性决定,其次才是语义逻辑。 - 按需加载的工具 schema 追加到末尾是安全的:因果注意力决定每个 token 的 KV 只依赖它之前的 token,末尾追加不改变任何已缓存 token 的 K/V;新增 schema 首次出现时计算一次(一次性写入),此后并入持续增长的前缀,后续所有轮次持续命中。 ### 2. 三种缓存一致性设计 - **子 Agent 字节级对齐**:主 Agent 派生子 Agent 或旁路查询时,子 Agent 的提示词、工具定义、模型配置、消息前缀和思考配置必须与父 Agent 逐字节匹配,才能命中服务商 Prompt Cache。若框架故意使用不同上下文/提示词,则不要求对齐。 - **替换字符串首次出现即冻结**:大型工具输出被替换为摘要预览时,替换后的字符串持久化保存;即使会话重启,也使用完全相同的替换字符串,保证恢复后的消息序列与缓存字节流一致。 - **思考配置与前缀一起冻结**:CoT 是否回传、回传哪些字段,属于前缀的一部分,改动同样使缓存失效。 ### 3. Chat Template 与历史思维链 - Chat Template 是「信封格式」:API 消息是信的内容,模板规定如何用 `system`、`user`、`assistant`、`tool` 等特殊 token 划分每条消息的边界和角色。不同模型家族(Qwen、Llama、Gemma)格式不同,服务端自动转换,开发者不需要手写,但必须知道它的存在。 - **偏离标准格式的真实代价**:Qwen3 会把 `` 内的历史思考保留下来以保证多步思考连贯,但 Chat Template 检测到新的用户查询时默认「用户换了个话题」,清理之前的思考。若工具结果被错误标记为 user 消息,就会误触发清理——相当于模型正算到一半,草稿纸被人收走了。 - **各厂商历史思考回传策略差异极大,迁移前必查文档**: - DeepSeek R1:剥离全部历史思考,只回传 `content`,不回传 `reasoning_content`(训练时历史 CoT 从不出现在输入里)。 - DeepSeek V4:彻底反转——只要请求携带 `tools`,两个 user 消息之间的每条 assistant 消息(哪怕该轮未调用工具)都必须原样回传 `reasoning_content`,否则 API 直接返回 400。Kimi K2、GLM-5 采用同样协议。 - Claude:工具调用循环中必须把带签名校验的 thinking block 原样回传;新的用户输入之后,服务端会忽略最后一次用户输入之前的 thinking block。 - **这些差异在多轮对话里只关系省不省 token,一旦要把跑到一半的轨迹交给另一家模型接着跑,就会变成实打实的接口错误。** ### 4. 正确 / 错误模式对照 | 模式 | 后果 | 正确做法 | | --- | --- | --- | | 动态系统提示词(时间戳) | 前缀从时间戳处全失效,TTFT 从 0.5s 涨到 3-5s | 时间作为 user 消息追加末尾,或需要时用工具获取 | | 动态用户配置(余额/额度) | 每轮改写前缀,缓存全失效 | 用专门的状态管理机制按需获取 | | 工具定义动态排序 | 从首个变动的工具起全部失效(每个工具定义可达数百 token) | 固定顺序——实验表明固定顺序对模型选工具能力几乎无影响,性能提升显著 | | 滑动窗口历史 | 破坏前缀一致性 + 丢失关键工具结果 | 改用压缩/状态栏,见 `context-compression` | | 文本格式化(USER:/ASSISTANT:) | 偏离训练格式:重复执行已完成操作、忽略工具结果、该调工具时输出文本 | 用标准结构化消息 | ### 5. 落地检查清单 - [ ] system prompt 与 tools 顺序在代码中是常量,不包含任何运行时插值 - [ ] 时间、用户状态、TODO 等动态信息走末尾追加 - [ ] 工具列表顺序确定(注册顺序或字典序),不按使用频率排序 - [ ] 压缩/摘要只在两次 API 调用之间做,且不动 system 与 tools - [ ] 会话恢复时能重建与缓存字节流一致的消息序列 ## 常见陷阱 - **为了「让 Agent 知道现在几点」在 system prompt 里加 `Current time: {{now}}`**:这是最高频的错误。某团队客服 Agent 每天 10 万次对话,加了一行时间戳后首 token 延迟从 0.5 秒涨到 3-5 秒,月度推理账单几乎翻倍。 - **按使用频率动态排序工具**:隐蔽但破坏力大,且对模型能力毫无收益。 - **用滑动窗口控制上下文长度**:窗口 10 轮时,第 2 轮拿到的关键工具结果到第 15 轮已滑出窗口,模型只能基于被截断的对话推断,错误率显著上升;实验中 Agent 经常陷入循环,反复执行相同工具调用,因为它「忘记」了已获得的结果。 - **把工具结果作为普通 user 消息传递**:既破坏 Chat Template 的角色体系,又误触发思维链清理,还抹掉了模型辨别指令与数据的依据。 - **每轮从头重建整个消息列表**:即使内容相同,重建过程若引入任何字节差异(时间戳、随机排序、序列化顺序),缓存即失效。 - **以为「追加到末尾」是零成本**:目录和 Skill 正文首次进入请求需要处理,只有前缀稳定后后续请求才能复用。 - **以为前缀铁律不可动摇而放弃优化**:研究前沿(Models Take Notes at Prefill)显示 prefill 阶段模型把字段的「结论」写进下游 KV 状态,字段自身 token 的贡献往往不到 1%;配合显式 CoT 可用约 1% 算力完成编辑,或用 RoPE 重定位做缓存块组合(vLLM 上 p90 首 token 延迟最多降低数十至数百倍,命中率约 98.5%,12 个模型 logit 余弦相似度 0.90-0.999)。但这是研究阶段,**生产系统仍应遵守前述三条铁律作为默认原则。** ## 配套代码 - `chapter2/kv-cache/` — 实验 2-3:ReAct Agent 在 correct 与五种反模式(dynamic_system / shuffled_tools / dynamic_profile / sliding_window / text_format)下的 KV Cache 对比,测量 TTFT、缓存命中率与 token 用量;支持 `--report` 离线对比和 `--cache-price-ratio` 成本估算。 ## 深度阅读 - `book/chapter2.md`「KV Cache 友好的上下文设计」