上下文压缩技术全面深度测评:DeepAgents如何有效解决AI对话失忆难题
开场:为什么“上下文”会变成负担?
大模型天然受限于“上下文窗口”,其单次能处理的token总量是固定的。 简短对话几乎没有影响。但当Agent需要执行规模较大的长期任务时,每一轮“调用工具 → 获取结果 → 继续推理”都会持续往历史记录中追加大量内容:读取的文件、运行的命令、命令返回的日志……几十轮交涉后,历史列表轻松膨胀到几十万token。
一旦触及窗口上限,会立刻引发三个典型问题:
- **请求直接被拒**:provider返回context overflow错误;
- **API开销飞涨**:每轮对话都需重新发送全部历史,token量线性推高成本;
- **早期指令丢失**:粗暴截断会连带删除最初的系统目标和任务计划,Agent瞬间“失忆”。
因此,主打长链路任务的Agent必须配备一套**上下文压缩(Summarization / Compaction)机制**:将那些“陈旧的、当前无关紧要的”信息折叠成摘要,清出空间以维持后续执行。
这听起来不过是“把旧对话历史总结一下”,但真正要做到精准、稳定、无信息损耗,内里细节远多于表面想象。我们就以DeepAgents的具体实现入手,逐层剖析。
---
一、一句话看懂 DeepAgents 的设计哲学
DeepAgents的压缩方案有一个非常“工程师”的标志:**它没有选择造轮子。** 它直接沿用了LangChain自带的压缩内核——那套“检测阈值 → 定位断点 → 切分 → 生成摘要”的核心逻辑,完全委托给上游组件。DeepAgents只在其外部封装了四项针对长任务Agent的**实用增强**: - “历史落盘,可回溯”——被压缩的消息并非删除,而是归档存储; - “压缩前先裁大参数”——增加一层低成本优化,往往截断后便无需触发LLM摘要; - “撞墙时有容错机制”——真正溢出报错时,自动切换至压缩后重试; - “非破坏式压缩”——原始对话内容一字不动,仅记录压缩事件的元数据。 外加一个「手动压缩工具」,使模型能够自主发起清理请求。 记住这个框架,接下来逐条展开。 ---二、主流程:进模型前的三步流水线
DeepAgents将压缩逻辑挂载在“每次调用大模型之前”的节点上。每次模型调用前,都会执行一条三步流水线:
**第 0 步|重建有效消息**:检查上一次是否执行过压缩,若是,则将“摘要 + 后续新消息”拼装成当前会话的有效视图。原始消息保持不变,这一点尤为重要,将在第四节详析。
**第 1 步|截断大工具参数**:这是成本最低的一步。将历史中过长的`write_file` / `edit_file`参数(例如一次性写入数千行代码内容)缩短为“前20字符 + (已截断)”。多数情况下,仅此一步省下的token就足够,根本无需劳烦大模型。
**第 2 步|阈值判断**:计算当前token总量,确认是否超出触发线。未超出则直接送入模型。此处巧妙之处在于:即便模型依然返回“上下文溢出”错误,系统不会直接抛错,而是“就地转入压缩流程重试”。这为阈值估算增加了一道保险。
**第 3 步|实际压缩**:超出阈值才执行完整压缩。顺序非常讲究——“先定位安全切点 → 将旧历史落盘 → 最后让LLM生成摘要”,然后以“摘要 + 近期消息”替代原始长序列,再发送给模型。
---
三、魔鬼在细节①:切点不能乱切
“将旧消息总结掉”看似简单,但**“从哪个位置下刀”**是真正的技术难点。 Agent的历史记录中存在大量“工具调用对”:AI发出“我要调用bash执行一个命令”(AI消息),紧接着是“命令执行结果”(Tool消息)。二者紧密关联,无法分割。 如果切点恰好落在两者之间,就会留下一条“没有来源的答案”——一条孤儿Tool消息。绝大多数模型遇到这种不完整结构会直接报错。
DeepAgents(借助LangChain内核)的处理手法相当优雅:若检测到切点压在某条Tool消息上,即“向前回溯”,找到发起此调用的那条AI消息,将切点前移,确保这对消息要么一起进入摘要,要么一起保留。token预算采用二分查找精确逼近,但**绝不以此为代价切断工具对。**
---
四、魔鬼在细节②:压缩后,原稿一字不删
这是DeepAgents与LangChain原生实现“最核心的分歧点”,也是最值得记录的设计决策。 LangChain原生的做法简单直接:在压缩节点中“直接改写对话状态”——彻底删除原始消息(`RemoveMessage(REMOVE_ALL_MESSAGES)`),替换为“摘要 + 近期消息”。结果干净,但原始记录丢失了。 DeepAgents选择截然相反的路径:它**不对原始消息进行任何删除**,仅在私有字段`_summarization_event`中记录三项内容:“切点位置、摘要内容、旧历史归档文件路径”。每次模型调用前,根据这个“事件”动态计算出精简后的视图再传递。
为什么坚持这么做?因为保留原稿能带来多重显著优势:
- **支持回放(replay)**:原始对话始终可用,随时重跑、复盘;
- **支持评测(evals)**:以真实完整轨迹进行评估,结果不失真;
- **支持与手动压缩工具共享状态**:两条压缩路径共读写同一份原稿,不会产生冲突(见第六节)。
代价也确实存在:每次调用需要“临时重建视图”,并处理切点坐标的转换。但针对一个追求长期稳定、高度可观测、支持深度调试的Agent平台来说,**“保留原始记录的价值远远超出这部分开销。”**
---
五、魔鬼在细节③:压缩 = 折叠 + 归档,不是删除
许多人将“压缩”等同于“丢弃旧数据,只留一份摘要”。DeepAgents的理解更深入一层:**“压缩是折叠操作,旧内容必须归档,且Agent应能够回溯查阅。”** 正式压缩前,它先以“追加写入”的方式将待丢弃的那批消息存入一个Markdown存档文件(每线程独立文件,每次压缩追加一个带时间戳的区块)。紧接着——这一步最为精妙——**“在摘要消息中嵌入该存档文件的路径。”**
模型最终收到的摘要类似于(大意):
> “文件 `thread_xxx_archive.md` 中保存了第 1 至 20 轮对话的完整历史记录。本次会话目标是……已完成操作包括……后续计划是……”
如此,上下文内容体量大幅缩减,但**任何被折叠的细节,Agent均能通过 `read_file` 操作反向查询。** 摘要遗漏了某个关键命令的输出?直接查阅存档即可。这一机制将“有损压缩”升级为“无损归档 + 有损视图”,是整套方案中最实用的增强。
补充一点:摘要本身同样经过精心设计。提示词强制LLM按四段checklist输出:「SESSION INTENT(本次任务的终极目标) / SUMMARY(关键决策与结论) / ARTIFACTS(涉及哪些文件变动) / NEXT STEPS(下一步执行清单)」——专门防止压缩后Agent重复执行已完成操作,或忘记已生成的产出物。
---
六、自动 + 手动:两层触发,一份状态
DeepAgents为压缩机制配置了“两套触发方式”,覆盖不同使用场景:
- **自动层**(`SummarizationMiddleware`):后台兜底机制。token一旦超过触发线(使用模型profile时,默认设为窗口上限的**85%**)即自动执行压缩,全程无需模型或用户介入。
- **手动层**(`SummarizationToolMiddleware`):为Agent装配一个`compact_conversation`工具,并在系统提示中悄悄告知——“当你切换到全新任务、或已得出结果、先前的上下文不再需要时,可主动调用它进行清理。”
手动层配备一条防呆规则:**“50% 资格闸门”。** 若上下文容量尚未达到自动触发线的一半,模型即使调用了`compact_conversation`也会被礼貌拒绝(“尚未到压缩时机”),以防过早压缩浪费一次LLM调用。
两层设计最巧妙之处在于:**它们共用一个 `_summarization_event` 状态字段。** 自动压缩和手动压缩写入同一份记录,状态互通、配置协同,绝无“压缩两遍”“坐标错乱”的风险。
---