上下文预算:压缩、清理与 prompt 缓存的三方博弈

T1-4模块 1 · Prompt 与上下文工程面试权重 更新于 2026-08
前置T1-3T0-3
关联题目Q1-13Q1-14

这篇学完你能回答什么

  1. 多轮历史越来越长,你怎么压缩?压缩时什么必须保真?
  2. 频繁小压和少次大压,哪个更好?为什么?
  3. prompt 缓存是什么原理?压缩会不会把它击穿?

从一个真实故障讲起

一个编码 Agent。团队自认为把上下文管理做得很勤快:每 10 轮自动做一次摘要压缩,把历史压到 2000 token 以内。逻辑上无懈可击——窗口永远宽裕,成本永远可控。

上线后两个反直觉的结果:

  • 账单不降反升 40%
  • 用户抱怨「它老忘事」——明明第 3 轮说过的偏好,到第 40 轮又要重问一遍。

两个原因都藏在「勤快」这两个字里:

账单升,是因为每次压缩都重写了前缀 → prompt 缓存整段作废 → 后面每一轮都要重新写一次缓存;而且压缩本身是一次额外的模型调用,单独计费。压得越勤,这两笔固定开销付得越多。

老忘事,是因为信息每被转述一次就丢一点。压 4 次,就是丢 4 次,是复利式的失真,不是线性的。

正确做法恰恰和直觉相反:少次大压。

越勤快,账单和失真涨得越快
编码 Agent 每 10 轮自动压一次,逻辑上无懈可击,上线后两个结果都反直觉

  1. 每 10 轮自动压一次

    历史压到 2000 token 以内,窗口永远宽裕

  2. 前缀被重写断点

    prompt 缓存整段作废

  3. 后面每轮重写缓存

    压缩本身还是一次额外采样,单独计费

  4. 账单不降反升 40%

    压得越勤,这两笔固定开销付得越多

  5. 用户抱怨它老忘事

    第 3 轮说过的偏好,第 40 轮又要重问

账单以为会降不降反升 40%
压 4 次以为是线性地丢一点复利式失真,丢 4 次
第 3 轮说过的偏好以为记着第 40 轮又要重问一遍
正确做法频繁小压少次大压
两个原因都藏在「勤快」这两个字里:每压一次就重写一次前缀,缓存整段作废;而压缩本身是一次额外的模型调用,单独计费。正确做法恰恰和直觉相反 —— 少次大压

核心概念:先打比方,再给定义

Compaction(压缩)。把接近窗口上限的对话摘要成一段高保真摘要,然后从这段摘要继续往下跑。比喻:不是每写完一页就重抄一遍笔记,而是写到本子快满时,认真整理一次交接文档。

Tool result clearing(工具结果清理)。把老的工具返回从历史里移除。工具结果一旦进上下文就是「永久居民」,成本按剩余轮次复利——清理就是给它办退租。

Prompt caching(提示缓存)。服务端把某段前缀的中间计算结果留住,下次同样前缀直接复用,命中时按远低于原价计费。

三者的关系是一场博弈:压缩管「历史必然增长」,清理管「工具结果膨胀」,缓存管「重复前缀的成本」——而前两个都要改写前缀,天然和第三个打架。 这一篇讲的就是怎么让它们和解。

压缩管增长,清理管膨胀,都要动前缀
一个是写到本子快满时整理一次交接文档,一个是给永久居民办退租

笔记本 · 快满了才整理

认真整理一次交接文档

整理一次就丢一点 —— 所以别每写完一页就重抄一遍

对应
Compaction · 压缩

把接近上限的对话摘成高保真摘要

从这段摘要继续往下跑;它管的是「历史必然增长」这件事

阈值默认 15 万最低只能设 5 万额外采样单独计费
永久居民 · 办退租

老住户腾房,房租不必一直付

退了租就再也看不到 —— 后面还要回看那些结果就麻烦了

对应
Tool result clearing · 清理

把老的工具返回从历史里移除

工具结果一进上下文就是永久居民,成本按剩余轮次复利

改写前缀清掉就看不到了
第三个玩家是 prompt 缓存:服务端把某段前缀的中间计算结果留住,下次同样前缀直接复用,命中时按远低于原价计费。而前两个都要改写前缀 —— 天然和它打架,这一篇讲的就是怎么让它们和解。

原理拆解

击穿的是会话前缀,不是全部
缓存层级 tools → system → messages,改上层废下层;两把刀都动最下层

缓存前缀 ①
tools · 工具定义顺序一变就失效
② 末尾单独一个断点
system · 系统提示词让它能在压缩中幸存
③ 清理与压缩都动这里
messages · 历史对话 + 工具结果
压缩发生时
会话前缀作废新摘要要重新写入一次
system 那段照常读它自己有断点,活下来了
缓存的四个数(记不住就抄)
数值读法
写入溢价5 分钟约 1.25 倍原价;1 小时约 2 倍写进去要先贵一次
命中0.1 倍原价,并免费续期续期从请求开始时刻算起
最小可缓存长度512 / 1024 / 2048 / 4096 token,按模型不同不到门槛不缓存,且不报错
回溯窗口20 个 block一轮加太多 block,会把上次写入推出去

压缩流程:输入 token 数达阈值触发 → 生成一段摘要块 → 摘要块之前的内容全部作废 → 从摘要继续。阈值默认 15 万,最低只能设到 5 万 —— 这个下限本身就是官方在说:不要频繁小压。用量要跨所有迭代汇总,只看顶层字段会算漏账。

和解只需要一个断点:在系统提示词末尾单独打一个缓存断点,压缩发生时那段缓存仍然有效、照常读取,只有新生成的摘要需要作为新内容写入一次 —— 长系统提示词因此可以跨多次压缩一直活着。

隐形失效项才是排查噩梦:工具定义顺序变了、某些语言序列化时 JSON 键顺序不稳定、图片增删、思考或 effort 配置变动、tool_choice 变化 —— 内容看着一模一样,缓存就是不命中。
原文示意
        ┌──────────── 上下文窗口 ────────────┐
        │ tools │ system │  历史 + 工具结果   │
        └───┬───────┬────────────┬───────────┘
            │       │            │
      改这里 │ 改这里 │       改这里 │
            ▼       ▼            ▼
        ┌───────────────────────────────────┐
        │  缓存前缀层级:tools → system → messages │
        │  改上层 → 下层全部失效                  │
        └───────────────────────────────────┘
              ▲                    ▲
              │                    │
         清理会动这里          压缩会动这里

压缩的机制(截至 2026-08 已经是一等公民 API)

流程是:按输入 token 数达到阈值触发 → 生成一段摘要块 → 摘要块之前的内容全部作废 → 从摘要继续。几个必须知道的细节:

  1. 阈值默认 15 万 token,最低只能设到 5 万。 这个下限本身就是官方在告诉你:不要频繁小压。
  2. 压缩是一次额外采样,单独计费。 用量要跨所有迭代汇总,只看顶层的输入输出 token 字段会算漏账。
  3. 自定义摘要指令是「完全替换」,不是追加。 这是最容易踩的坑——你只写了「重点保留代码片段」,就等于把默认提示里「保留状态、下一步、已有结论」全删了。
  4. 可以在压缩后暂停,让你把最近几条消息原样接在摘要后面,避免刚说完的关键信息立刻被摘要走样。
  5. 带工具时压缩可能失败:模型在摘要那一步跑去调工具了,返回一个空摘要。解法是在指令里明确写「这一步只写文本,不要调用任何工具」。
  6. 摘要用的是同一个模型,不能换个便宜的来做。

缓存的机制

  • 层级 tools → system → messages,改上层废下层;
  • 写入只发生在断点处,读取时向前回溯,回溯窗口是 20 个 block。这意味着一轮加太多 block,会把上一次的写入推出窗口,明明内容没变也命中不了;
  • 有最小可缓存长度门槛,按模型不同为 512 / 1024 / 2048 / 4096 token 不等。不到门槛不缓存,且不报错——两个缓存字段都是 0 就是这种情况;
  • 计费:5 分钟写入约 1.25 倍原价、1 小时写入约 2 倍,命中约 0.1 倍;命中会免费续期,续期从请求开始时刻算起(生成耗时也算在寿命里);
  • 常见的隐形失效项:工具定义顺序变了、某些语言序列化时 JSON 键顺序不稳定、图片增删、思考或 effort 配置变动、tool_choice 变化。

压缩与缓存怎么和解(这是本篇最值钱的一段)

「压缩会击穿缓存」这个说法要说得更精确:击穿的是会话前缀,不是全部。

官方给的解法是:在系统提示词末尾单独打一个缓存断点。这样压缩发生时,系统提示词那段缓存仍然有效、照常读取,只有新生成的摘要需要作为新内容写入一次。长系统提示词因此可以跨多次压缩一直活着。

再配合「少次大压」,整笔账就清楚了:

原文示意
一次压缩的固定开销 = 额外采样(读入约 T 个 token)
                   + 摘要写入缓存
                   + 会话前缀重建

压缩次数 ↑ → 固定开销 × 次数 ↑,同时信息经多次转述失真 ↑
所以阈值应尽量贴近窗口上限,而不是"勤快地小压"。

工程实践(截至 2026-08)

表 1:三种手段的选型

手段 解决什么 代价 什么时候该用
工具结果清理 工具返回膨胀 改写前缀影响缓存;被清掉的内容模型再也看不到 后续步骤还要回看那些结果时
压缩 历史必然增长 额外采样计费 + 信息损耗 + 前缀重建 会话离窗口上限还远时——频繁小压更贵也更糊
prompt 缓存 重复前缀的成本 写入有溢价;要求前缀严格稳定 前缀本来就每次都变时(只会一直付写入费)

压缩时的五类必须保真信息(这一条在 Agent 章已经定过,这里是同一套口径):

  1. 已确认的关键决定与硬性约束
  2. 未完成的待办与下一步
  3. 已经踩过的失败路径——不保留就会重复踩,这是最容易被摘要丢掉、代价又最高的一类;
  4. 外部世界已发生的不可逆动作(已发的邮件、已建的工单、已扣的款);
  5. 用户明确表达的偏好与口径

但更重要的是一条架构判断:关键约束应该活在结构化状态里,而不是对话历史里。 历史是会被有损转述的介质,把不能丢的东西放在会被转述的地方,本身就是设计错误。正确做法是把约束提到一个每轮原样重新注入的状态块里,让它根本不参与压缩。

缓存友好设计清单

  1. 稳定的在前、变化的在后——这是唯一一条不需要记细节的总原则;
  2. 断点打在跨请求完全一致的最后一个 block 上,绝不打在带时间戳或本轮用户输入的 block 上;
  3. 系统提示词末尾单独一个断点,让它能在压缩中幸存;
  4. 长会话要考虑加第二个断点,防止增长把上一次写入推出 20 block 的回溯窗口;
  5. 检查 JSON 序列化的键顺序是否稳定,工具定义顺序是否固定;
  6. 一个会话内 effort 与思考配置保持恒定;
  7. 检查是否够到最小长度门槛——不够是静默失败,只能靠看返回字段发现。

压缩配置清单

  1. 阈值尽量贴近窗口上限,别为了「保险」调低;
  2. 自定义摘要指令时,记得把默认提示里那些通用要点(状态、下一步、已有结论)一起写进去;
  3. 指令里明确禁止在摘要步骤调用工具;
  4. 用「压缩后暂停」把最近若干条原样保留;
  5. 成本核算要跨所有迭代汇总,别只看顶层 token 字段。
关键约束不该活在会被转述的地方
三种手段各买各的东西;压缩要保真五类信息,更好的做法是让它别参与压缩

三种手段
手段解决什么 · 代价什么时候不该用
工具结果清理治工具返回膨胀;代价是改写前缀影响缓存,被清掉的内容模型再也看不到后续步骤还要回看那些结果时
压缩 compaction治历史必然增长;代价是额外采样计费 + 信息损耗 + 前缀重建会话离窗口上限还远时 —— 频繁小压更贵也更糊
prompt 缓存治重复前缀的成本;代价是写入有溢价,且要求前缀严格稳定前缀本来就每次都变时,只会一直付写入费
保真清单与缓存友好设计
五类必须保真
已确认的关键决定与硬性约束;未完成的待办与下一步;已踩过的失败路径;不可逆动作;用户的偏好与口径
最容易丢的一类
已经踩过的失败路径 —— 不保留就会重复踩,是代价最高的那一类
架构判断
关键约束应该活在结构化状态里,每轮原样重新注入,根本不参与压缩
断点打在哪
打在跨请求完全一致的最后一个 block 上,绝不打在带时间戳或本轮用户输入的 block 上
长会话
加第二个断点,防止增长把上一次写入推出 20 block 的回溯窗口;再查够不够最小长度门槛
压缩配置
自定义摘要指令是完全替换,默认的状态、下一步、已有结论要自己写回去;禁止摘要步骤调工具;摘要用的还是同一个模型
还有两条只有踩过才知道:压缩后可以暂停,把最近几条原样接在摘要后面,避免刚说完的关键信息立刻被摘走样;带工具时压缩可能失败 —— 模型在摘要那一步跑去调工具,返回一个空摘要。

面试视角

两题经常连着出,因为是同一笔账
Q1-13 是大厂二三面的压力题,Agent 团队必问;Q1-14 是平台组网关组必问

  1. 先讲博弈关系压缩和清理都改前缀,天然和缓存打架
  2. 给算术依据固定开销 × 次数,失真还是复利式的
  3. 给保真清单再升级成架构判断:关键约束不该放在历史里
  4. 给和解方案系统提示词末尾单独打一个缓存断点
只读过:这些回答会暴露你
  • 认为压缩就是「让模型总结一下历史」
  • 说不出压缩本身要花钱,账只算了省下的那一半
  • 以为开了缓存自然就省钱
  • 把「压缩击穿缓存」当成一个无解的问题
  • 觉得压得越勤,上下文就越干净
真做过:这些细节骗不了人
  • 知道自定义摘要指令是完全替换,不是追加
  • 知道压缩是一次额外采样,单独计费,用量要跨迭代汇总
  • 知道最小可缓存长度不够时是静默失败,两个字段都是 0
  • 说得出至少两个隐形的缓存失效项
  • 能把「关键约束提到结构化状态」讲成架构判断,而不是技巧
三件武器互相冲突:压缩和清理买的是空间,代价是重写前缀;缓存买的是成本,前提是前缀别动。把稳定的钉死在前面、把不能丢的挪出历史、把压缩留到快满时再做,三件事才同时成立。

面试官怎么问。 Q1-13 是大厂二三面的压力题,Agent 团队必问;Q1-14 是平台组和网关组的必问题。两题经常连着出——因为它们本来就是同一笔账的两面。

答题结构建议

  1. 先讲三者的博弈关系(压缩和清理都改前缀,和缓存打架),这一句就能拉开档次;
  2. 给出「少次大压」的算术依据,而不是直觉;
  3. 给出保真清单,并升级成架构判断(关键约束不该放在历史里);
  4. 给出和解方案(系统提示词末尾单独打断点)。

分水岭信号

  • 只读过:认为压缩就是「让模型总结一下历史」;觉得压得越勤上下文越干净;说不出压缩本身要花钱;以为开了缓存自然就省钱;把「压缩击穿缓存」当成无解。
  • 真做过:知道压缩是额外采样并单独计费;知道自定义摘要指令是完全替换;知道最小可缓存长度不够时是静默失败;说得出至少两个隐形的缓存失效项;能把「关键约束提到结构化状态」讲成架构判断而不是技巧。

小结与延伸

一句话收口:上下文治理的三件武器互相冲突——压缩和清理买的是空间,代价是重写前缀;缓存买的是成本,前提是前缀别动。把稳定的钉死在前面、把不能丢的挪出历史、把压缩留到快满时再做,这三件事才能同时成立。

  • 关联题目:Q1-13Q1-14
  • 延伸:T1-3(context rot)、T3-6(Agent 记忆与上下文压缩)、T3-12(成本与延迟优化)、Q6-09(token 成本突涨的排查)

继续深入

本篇归属第 1 章「LLM 与 Prompt 基础」,去做这一章的题