首页 > 教程攻略 > ai教程 >Agent 聊着聊着就"失忆"?拆解 DeepAgents 的上下文压缩黑科技

Agent 聊着聊着就"失忆"?拆解 DeepAgents 的上下文压缩黑科技

来源:互联网 时间:2026-06-06 07:28:34

开场:为什么“上下文”会变成负担?

大模型有个硬性约束,叫作“上下文窗口”——它一次能“看”的 token 数,是有限的。 短对话当然无所谓。但当你让 Agent 干点长活的时候,每一轮“调用工具 → 拿到结果 → 再思考”都会往历史里追加一大坨内容:读了哪些文件、跑了哪些命令、命令吐了多长的日志……结果呢?几十轮下来,历史列表就轻松膨胀到几十万 token。 \ 一旦撞上窗口上限,会遇到三件糟心事: - “请求被拒”:provider 直接报个 context overflow 的错误; - “费用飙升”:每轮对话都得把全部历史重发一遍,token 越多越贵; - “早期信息丢失”:粗暴截断会把最初的任务目标也一起咔嚓掉,Agent 当场“失忆”。 所以,长任务型 Agent 必须有一套**上下文压缩(Summarization / Compaction)**机制:把那些“旧的、暂时用不上的”折叠成摘要,腾出空间继续干活。 这事听起来简单——“把旧的对话历史总结一下嘛”——但真要做得对、做得稳、做得不丢信息,里头的讲究比你想的要多得多。我们就以 DeepAgents 的实现为样本,一层层剥开来看。 ---

一、一句话看懂 DeepAgents 的设计哲学

DeepAgents 的压缩有个很“工程师”的特点:**它没有重造轮子。** 它直接复用了 LangChain 自带的压缩内核——那套“判阈值 → 选切点 → 切分 → 生成摘要”的核心逻辑,原封不动地委托给上游。DeepAgents 自己只在外层加了「四件长任务 Agent 真正需要的增强」: - “旧历史落盘,可回溯”——压缩掉的消息不是删了,是归档了; - “压缩前先截大参数”——一层更便宜的优化,常常截一截就够,省掉一次摘要; - “撞墙了能兜底”——真超窗报错时,自动转去压缩重试; - “非破坏式压缩”——原始对话一个字不删,只记下“怎么压的”。 再加一个「手动压缩工具」,让模型能自己喊“我要清场了”。 记住这个骨架,下面逐条展开。 ---

二、主流程:进模型前的三步流水线

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` 状态字段。** 自动压的和手动压的写同一份账,互相可见、协同工作,不会出现“压了两遍”“坐标对不上”的混乱。 ---

七、把设计权衡列成一张表

DeepAgents 的每一处选择,背后都是一组取舍。一张表格看全: " 权衡点 | DeepAgents 的选择 | 换来什么 / 代价 | | --- | --- | --- | | 状态可变性 | “非破坏式”:留原稿,压缩信息记私有字段 | 可回放 / 可评测 / 可与工具共享;代价是每次要重建视图 | | 复用还是重写 | 复用 LangChain 内核,自己只做外壳 | 逻辑零重复、跟上游同步;代价是耦合上游私有方法 | | 旧历史去向 | 压缩前“先落盘”,摘要内嵌路径 | 细节可 read_file 回查、不丢 artifact;代价是多一次 I/O | | 压缩成本 | 先做“参数截断”这层廉价优化 | 常能省掉一次摘要 LLM 调用;代价是截断有损(但已落盘) | | 撞墙处理 | 不超阈值也先试,溢出再压缩重试 | 阈值估松也不报错;代价是极端情况多一次失败往返 | | 触发方式 | “自动 + 手动”两层,共享状态 | 既能兜底又能主动清场;代价是要 50% 闸门防过早压 | | 默认阈值 | 有 profile 用比例(85%)、无则用保守固定值 | 开箱即用、跨模型自适应;代价是依赖 profile 数据质量 | 一句话总结这张表:**DeepAgents 的压缩,是一套务实、克制、可观测的系统设计。** ---

八、几个还值得琢磨的问题

读源码读到后面,有几处我自己也还存疑,放在这儿供同好讨论: - **“多子袋里会不会写串档案?”** 主 Agent 和每个子 Agent 都各装了一份压缩中间件,它们的归档文件按 `thread_id` 命名。子袋里是否复用了父线程 ID?若复用,多个袋里的历史会不会追加到同一个文件里互相污染? - **“截断只认 `write_file` / `edit_file`”**:那些会吐超长输出的自定义工具(比如某些 `execute`)的大参数不会被截。长输出工具多的场景,这层优化收益就有限了。是否该开放一个工具名白名单? - **“token 计数的两套签名”**:内部对“带不带工具定义一起算 token”做了 try/except 兼容,某些计数器下工具占用没算进阈值。重工具场景,会不会低估总量、压得偏晚? 这些都不是 bug,更多是“在通用与精确之间怎么取舍”的开放问题。也欢迎读过源码的朋友一起探讨。 ---

结语:好的压缩,是“忘得优雅”

回头看,DeepAgents 的上下文压缩给我最大的启发是这句话:**“好的压缩,不是删得干净,而是忘得优雅。”** 它的答案是一组克制而周到的工程选择:复用成熟内核不炫技、先用最便宜的手段省 token、切点死守工具调用对、原稿一律留底、旧历史归档可回查、自动手动双保险。每一条单看都不惊艳,合在一起却构成了一个“能扛住长任务、又不丢信息”的压缩系统。 下次当你设计任何“需要长期运行、上下文会膨胀”的 AI 系统时,不妨借走这套思路——**“忘记,也可以很优雅。”** ---

延伸阅读

- 本文的完整技术深读(含逐段源码引用、设计权衡、待验证清单):`notes/research/agent-frameworks/2026-06-04-deepagents-context-compaction-深读.md` - 配套对比:`notes/research/agent-frameworks/2026-05-19-langchain-1.0-upgrade-深读.md`(LangChain 中间件机制总览) 涉及代码版本: - DeepAgents `2ac7d415`(`langchain-ai/deepagents`,`middleware/summarization.py`) - LangChain v1(压缩内核 `SummarizationMiddleware` 来源)