Agent 上下文压缩:当记忆撑爆窗口之后
上下文窗口是 Agent 系统最硬的资源约束。压缩不是简单的"删掉最早的消息",而是一系列有策略的取舍。本文拆解三种主流压缩策略的本质权衡。
问题在哪
Agent 的每次推理输入 = 系统指令 + 工具定义 + 对话历史 + 检索到的记忆。对长时间运行的 Agent,这四个部分加起来很容易超过上下文窗口。
最简单的做法是滑动窗口——只保留最近 N 轮对话,丢掉更早的。这在 chatbot 场景将就够用,但在 Agent 场景不行。Agent 的早期交互里往往包含了关键信息:用户指定的约束条件、某个工具返回的重要数据、Agent 自己做出的决策分支。一旦被窗口切掉,Agent 就丢失了对自身状态的感知,表现出的行为像失忆了一样——重复调用之前已经失败过的工具,或者违反用户在 30 轮前明确说过的规则。
真正的上下文压缩需要回答一个问题:丢掉什么东西损失最小?
策略一:摘要式压缩(Summarization)
最直觉的做法:当上下文接近窗口上限时,把早期对话压缩成一段摘要,替代原始轮次。
这里藏着一个陷阱:摘要本身也占用 token。LlamaIndex 的早期实现里,每次压缩都把旧对话重新摘要一遍,新的摘要+剩余对话还在窗口内。但如果 Agent 运行了上百轮,多次叠加的摘要本身就会膨胀到不可忽视。
改进方案是分层摘要(hierarchical summarization):维护一个摘要层叠结构,最底层是最近 N 轮的完整对话,往上是每 M 轮的压缩摘要,再往上是摘要的摘要。Agent 只保留当前层+上一层的内容,更早的层只在必要时从外部存储拉取。
代价是什么?每生成一次摘要就是一次 LLM 调用。高频压缩场景下,摘要调用的 token 消耗可能超过它节省的。
策略二:选择性驱逐(Selective Eviction)
不压缩内容,而是根据某种标准决定哪些消息可以被直接丢掉。
驱逐策略的核心在于价值函数的设计。简单的时间衰减(越早越该丢)是最差的方案——它忽略了消息的实际信息价值。更好的价值函数考虑两个维度:
- 冗余度:这条消息包含的信息是否有其他消息也在表达?如果有,它是更精确的版本还是更模糊的版本?
- 相关性:这条消息与当前任务的相关度。Agent 完成支付流程时,前 20 轮关于用户偏好的闲聊在这个时刻价值很低。
LangChain 的 ConversationSummaryBufferMemory 使用 token 长度做驱逐触发条件,但驱逐策略是简单的时间顺序。这是典型的"够用但不优雅"。
一个实验性的改进是:用 embedding 相似度做驱逐。把当前查询和每条历史消息都 embedding,计算相似度,保留 top-K。这和 RAG 的检索逻辑同构——区别在于 RAG 从外部库"拉",驱逐从窗口内"推"。
问题是 embedding 相似度对事实型记忆的保留效果不错("用户说密码是 xyz" 这样的信息容易被高相似度命中),但对 Agent 做过的决策路径保留效果很差("我上次尝试了方案 A 然后失败了" 这类信息依赖的是因果链,不是语义相似度)。
策略三:Token 级压缩(Token-Level Compression)
把文本压缩到最短的可保留形式,本质上是一个映射:自然语言描述 → 结构化摘要 → 代码/标记语言。
举例:Agent 调用天气 API 返回了 200 行的 JSON。完整保留这段 JSON 很浪费。压缩后可以变成一句话:weather(london, 2024-03-15) → {temp:12, humidity:65, rain:0.3}。甚至更进一步压缩成 weather_ok(london)——只保留被后续决策引用的信息。
这个策略的有效性取决于工具的输出格式设计。如果工具本身就返回结构化且可压缩的输出,压缩率高且无损。如果工具返回的是一大段自然语言,压缩就退化成摘要。
Token 级压缩的最大风险是过度压缩导致信息丢失——Agent 在处理后续任务时需要某个字段,但它已经在压缩中被丢弃了。解决方案是 lazy compression:只压缩 Agent 明确不会再用到的部分。"明确不会再用到"本身就是个难题——你没法提前知道 Agent 的下一步决策。
组合策略才是答案
没有一种压缩策略能通吃所有场景。生产系统的做法是排列组合:
- 先用 token 级压缩处理工具调用的返回(这是最容易压缩的部分,收益最高)
- 选择性驱逐冗余度高的系统消息和重复的中间状态
- 当摘要触发阈值时,只对最早的信息层级做摘要式压缩,保留最近的交互完整
压缩触发时机也很关键。不要在每轮都检查——用 token 计数做阈值触发,留出 15-20% 的缓冲空间。当使用率超过 80% 时才启动压缩,避免频繁压缩带来的额外开销。
容易被忽略的一点
压缩策略本身需要可审计。Agent 的决策链很长时,压缩后的上下文相当于一份"被剪辑过的证据"——如果 Agent 基于压缩后的信息做出了错误决策,你需要能回溯到压缩前的原始记录,找到信息丢失点。没有这个回溯能力,调试 Agent 的异常行为基本靠猜。
所以压缩策略和日志系统必须耦合设计:压缩前的完整上下文写入外部存储并关联到对应的决策节点,压缩后的上下文只有决策推理。调试时展开节点就能看到原始记录。
上下文窗口不是墙,压缩也不是万能药。但设计好压缩链,Agent 的运行寿命可以从几十轮延伸到几千轮,而不需要在每轮都重新加载全部记忆。
评论
发表评论