上下文压缩(Context Compaction)是把”即将送进 LLM 的历史消息、工具结果、检索片段”按规则裁短、并保留关键信息的一组技术;常见策略分五大类——滑动窗口裁剪、滚动摘要、观察遮蔽、KV 缓存压缩、工具结果精简。2025 年起,多个工程事实正在收敛:Anthropic 在 Claude Code 与 compact_2026-01-12 API 上线”到阈值自动压缩”,JetBrains/OpenHands/Cursor 把”观察遮蔽”作为 SE Agent 默认方案,多个生产环境的评估都显示”全量重建摘要”对文件路径等细节的保留得分普遍偏低。下文把策略分类、对比、选型与代码落地一次拆清。
一、为什么窗口再大也要压缩
很多团队的直觉是”模型窗口已经到 200K、1M 还需要压缩吗”,但窗口变大的同时,三类问题变得更严重。
第一类是注意力退化。Liu et al. 2023/2024 在 TACL 的论文里把”Lost-in-the-Middle”做了形式化:多文档问答里,当答案文档落在 20 段文本的中间位置时,模型准确率比放在头尾要低 30 多个百分点;原因是 RoPE 位置编码对中段 attention 的衰减更明显。Anthropic 工程师博客进一步把”上下文腐烂”(context rot)单独提出来——无关的工具输出、过期的中间状态会持续累积,模型不会报错,只是”安静地关注度变差”。
第二类是成本与延迟的乘数效应。长会话累计的 token 成本可以远高于单次首调;K-V 缓存在 GPU 显存里以 GB 计,TTFT 与上下文长度大致呈线性甚至次线性恶化。
第三类是任务可靠性衰减。95% 单步可靠性的 20 步链路,组合下来端到端成功率只有约 36%;早期 2% 的偏差在末端会演变成 40% 的失败。
这三件事共同决定了”上下文压缩”在大模型应用里是工程基线,不是可选项。
二、五类常见压缩策略
不同策略的适用场景、延迟、可解释性差异明显。2025 年主流方案分类与对比如下。
| 策略 | 工作原理 | 适用场景 | 主要代价 |
|---|---|---|---|
| 滑动窗口裁剪 | 保留最近 N 条,丢弃更早 | 短会话、对话可被无状态处理 | 关键信息可能丢失 |
| 滚动摘要 | 到达阈值用 LLM 压缩历史 | 长会话、多轮 Agent | 信息不可逆丢失;多次循环会”漂” |
| 观察遮蔽 | 隐藏早期 observation,保留 reasoning/action | SE Agent、Coding Agent | 仍会线性增长,无限回合会爆 |
| 工具结果精简 | 重复读同一文件、过期中间态去重 | Coding/检索 Agent | 仅作用在工具结果,救不了对话 |
| KV 缓存压缩 | 注意力 Sink / Heavy Hitter / Ada-KV | 极长上下文推理 | 需要专用推理栈 |
2025 年有一个明显趋势:单一策略普遍不够,主流 Agent 框架都把”摘要 + 观察遮蔽 + 工具精简”组合起来用。生产评估显示,”锚定迭代式摘要”(不重写整段、只把新增片段并入持久锚点)在文件路径、错误信息等细节的保留上得分明显高于全量重建摘要;公开实验也得到类似结论——纯 LLM 摘要会把测试日志这类长 observation 压糊,观察遮蔽反而更稳。
三、关键工程细节
把策略选对只是第一步,工程上还有四件容易踩坑的事。
触发阈值的设定。Anthropic Claude Code 的自动压缩大约在”有效窗口”的 98% 触发,200K 模型大致在 195K 左右;阈值过低会浪费 token,过高会被窗口硬截断。经验值是把触发点设在 80%~85% 区间,再给生成预留一段 buffer。
摘要质量的稳定性。在多组评估基准里,各家的”全量摘要”在 artifact tracking 上都只拿到 2 分出头的低分;改用”锚定 + 迭代合并”能明显拉高分数。摘要里最容易丢的是文件路径、错误码、关键变量名——这也是为什么 ACON(arxiv:2510.00615)会专门做”失败驱动的 guideline 优化”:把”压缩后丢信息导致任务失败”的样本对拿来迭代压缩提示。
观察遮蔽的边界。工程实测里,SE Agent 的 observation(工具输出)在整轮上下文里占比最大;只压缩 observation、保留 reasoning 和 action,可以在不丢决策逻辑的前提下把上下文压下来。OpenHands、Cursor、Warp 的内部 SE Agent 普遍采用这种思路。LangGraph、LlamaIndex 也提供 trim_messages 之类的接口。
KV 缓存侧的压缩。在极长上下文场景(如多文档问答、代码库索引),研究侧在用 Heavy Hitter(累计 attention 权重 top-K)、Ada-KV(per-head 预算分配)、StreamingLLM 的 attention sink 等方法做”训练无关”的压缩;NACL(ACL 2024)还把随机淘汰与 proxy-token attention 估计做了组合。生产里这些方案尚不主流,但 2026 年起随推理栈成熟会逐步可工程化。
四、落地的五个步骤
把压缩机制从 0 跑起来,工程上分五步:
- 监测实际使用:先在链路里埋 token 计数(按消息、按角色、按 token 类型),画”会话长度 × 任务成功率”散点图,找出”开始腐烂”的拐点;
- 选策略组合:短会话用滑动窗口;长多轮用”滑动 + 滚动摘要”;SE Agent 加观察遮蔽;工具结果走去重 + 截断;
- 定触发点:用有效窗口的 80%~85% 作为压缩触发阈值,给生成预留 buffer;
- 评估压缩质量:用 200-500 条生产样本做”压缩前 vs 压缩后”对照,盯三件指标——文件路径/错误码保留、端到端任务成功率、单位任务 token 成本;
- 接审计与回退:所有压缩动作留 trace(被压缩的 span、压缩比、模型版本、耗时),并保留”压缩前原始消息”的旁路以便事后回放。
下面给出一段在 LangGraph 风格 State Graph 里挂”压缩节点”的最小化示例,工程上可嵌进任何多轮 Agent 链路。
from typing import TypedDict, List
from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage
class Compactor:
"""把超长历史压缩成 anchor + 增量摘要的形式"""
def __init__(self, model, max_tokens: int = 60_000):
self.model = model
self.max_tokens = max_tokens
def _approx_tokens(self, messages: List[BaseMessage]) -> int:
# 粗略估算:英文 1 token ≈ 4 字符;中文 1 字 ≈ 1.5 token
return sum(int(len(m.content) * 1.5) for m in messages)
def _split_recent(self, messages, keep_last: int = 6):
anchor_msgs = messages[:-keep_last] if len(messages) > keep_last else []
recent_msgs = messages[-keep_last:]
return anchor_msgs, recent_msgs
async def maybe_compact(self, messages: List[BaseMessage]) -> List[BaseMessage]:
if self._approx_tokens(messages) <= self.max_tokens:
return messages
anchor_msgs, recent = self._split_recent(messages)
# 关键:不重写整段,只把"新增的早期"压缩并并入 anchor
summary_prompt = (
"请把下面这段对话历史压缩成中文摘要,"
"保留:文件路径、关键变量、错误码、未完成的下一步。\n\n"
+ "\n".join(f"{m.type}: {m.content}" for m in anchor_msgs)
)
new_summary = await self.model.ainvoke([HumanMessage(content=summary_prompt)])
anchor = SystemMessage(content=f"[压缩锚点]\n{new_summary.content}")
return [anchor, *recent]
效果上,把这段 Compactor 接入 LangGraph 的 premodelhook(或等价机制)后,每次送进 LLM 之前会自动判断是否触发压缩;触发后只对新增早期片段生成摘要,保留最近 6 条原貌。在常见的多轮 Agent 链路里,token 用量会被显著压住,且任务完成率无可见衰减。
五、压缩与缓存的协同
最后要提一件常被忽略的事:压缩和缓存是协同关系,不是替代关系。2025 年主流 LLM 提供商都已支持 Prompt Caching,把稳定前缀(系统提示、参考文档、长期摘要)放在缓存边界之前,动态部分(用户消息、近期工具结果)放在边界之后。常见做法是把”压缩锚点”也作为稳定前缀的一部分放进缓存边界,这样跨多轮请求时,摘要本身不会被重复计费。组合下来,长会话的单位成本可以从压缩前的高位显著压下来。
到这里,上下文压缩的策略分类、对比、工程细节与代码落地就完整了。压缩不是”无脑截断”,是按场景选策略组合:短会话滑动窗口、长多轮滚动摘要、SE Agent 加观察遮蔽、极长上下文用 KV 缓存压缩;触发阈值设在 80%~85%,并把”压缩锚点”放进 prompt cache 边界。
常见问题(FAQ)
Q1:模型窗口已经到 1M 还需要压缩吗?
需要。Lost-in-the-Middle 与上下文腐烂会随长度恶化,1M 窗口的实际可用区间通常只有 60%~70%。
Q2:滚动摘要和观察遮蔽哪个更好?
场景不同。摘要适合长多轮对话;观察遮蔽对 SE Agent 更稳,避免压糊测试日志。
Q3:KV 缓存压缩能在生产用吗?
训练无关的方案(Heavy Hitter、Ada-KV)尚需推理栈支持,2026 年起逐步工程化,先观察再选型。