上下文压缩 compaction

LLM 与 Prompt 基础深水13讲解 3

压缩是把接近窗口上限的历史摘要成一段高保真摘要,然后从摘要继续跑。必须保真的是当前目标、已做出的决定、未完成的待办与关键约束;工具结果清理、压缩、缓存三者互相牵制 —— 改写前缀会击穿缓存,少次大压比频繁小压更省也更稳。

也叫:compaction · 上下文压缩 · 历史压缩 · 摘要压缩 · 工具结果清理

原理拆解出自 T1-4

击穿的是会话前缀,不是全部
缓存层级 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)
                   + 摘要写入缓存
                   + 会话前缀重建

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

以上节选自T1-4 上下文预算:压缩、清理与 prompt 缓存的三方博弈,读全文能看到前后语境。

延伸阅读

考这个知识点的题1

会连带问到12