首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Agent 聊着聊着就"失忆"?拆解 DeepAgents 的上下文压缩黑科技

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

作者头像
windealli
发布2026-06-05 19:59:29
发布2026-06-05 19:59:29
70
举报
文章被收录于专栏:windealliwindealli

❝让 Agent 干一件长活——写一个项目、做一轮深度研究——它会越聊越"重",对话历史像滚雪球一样越滚越大,直到把模型的上下文窗口撑爆。这时候要么报错、要么烧钱、要么粗暴丢掉早期信息。DeepAgents 给出的解法既聪明又克制:「压缩,但不删原稿;瘦身,但留得回得来。」 这篇文章用 6 张图,把它的实现拆给你看。 ❞

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

大模型有个硬约束——「上下文窗口」:一次能"看"的 token 数是有限的。

短对话无所谓。但当你让 Agent 干长活时,每一轮"调用工具 → 拿到结果 → 再思考"都会往历史里追加一大坨内容:读了哪些文件、跑了哪些命令、命令吐了多长的日志……几十轮下来,历史轻松膨胀到几十万 token。

撞上窗口上限会发生三件糟心事:

  • 「请求被拒」:provider 直接报 context overflow;
  • 「费用飙升」:每轮都把全部历史重发一遍,token 越多越贵;
  • 「早期信息丢失」:粗暴截断会把最初的任务目标也一起截掉,Agent 当场"失忆"。

所以长任务型 Agent 必须有一套**上下文压缩(Summarization / Compaction)**机制:把"旧的、暂时用不上的"折叠成摘要,腾出空间继续干活。

这件事说起来简单——"把旧消息总结一下嘛"——但真要做对、做稳、做得不丢信息,里头的讲究比你想的多。我们就以 DeepAgents 的实现为样本,一层层剥开。


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

DeepAgents 的压缩有个很"工程师"的特点:「它没有重造轮子。」

它直接复用了 LangChain 自带的压缩内核——"判阈值 → 选切点 → 切分 → 生成摘要"这套核心逻辑原封不动地委托给上游。DeepAgents 自己只在外层加了「四件长任务 Agent 真正需要的增强」

  1. 「旧历史落盘,可回溯」 —— 压缩掉的消息不是删了,是归档了;
  2. 「压缩前先截大参数」 —— 一层更便宜的优化,常常截一截就够,省掉一次摘要;
  3. 「撞墙了能兜底」 —— 真超窗报错时自动转去压缩重试;
  4. 「非破坏式压缩」 —— 原始对话一个字不删,只记"怎么压的"。

再加一个「手动压缩工具」,让模型能自己喊"我要清场"。

记住这个骨架,下面逐条展开。


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

DeepAgents 把压缩逻辑挂在"每次调用大模型之前"这个时机上。每次要调模型,它都会先跑一条三步流水线:

「第 0 步|重建有效消息」:先看看上一次有没有压缩过,如果有,就把"摘要 + 后面的新消息"拼成这次实际要用的视图(原稿不动,这点很关键,第四节细说)。

「第 1 步|截断大工具参数」:这是最便宜的一招。把历史里那些超长的 write_file / edit_file 参数(比如一次写了几千行的文件内容)裁短成"前 20 个字符 + (已截断)"。很多时候光这一步省下的 token 就够了,根本不用惊动大模型。

「第 2 步|判阈值」:算一下当前 token,看有没有超过触发线。「没超就直接发给模型」——但这里藏了个巧思:万一模型还是返回了"上下文溢出"错误,它不会把错误抛给你,而是「就地转入压缩路径重试」。相当于给阈值估算留了个安全气囊。

「第 3 步|真正压缩」:超阈值了,才走完整压缩。顺序很讲究——「先选安全切点 → 再把旧历史落盘 → 最后才让 LLM 生成摘要」,然后用"摘要 + 近期消息"替代原来那一长串发给模型。

❝小彩蛋:异步版本里,"落盘"和"生成摘要"这两件互不依赖的事是「并发」跑的(asyncio.gather),能省一点墙上时间。细节控的快乐。 ❞


三、魔鬼在细节①:切点不能乱切

"把旧消息总结掉"听着简单,但「从哪儿下刀」是个技术活。

Agent 的历史里有大量"工具调用对":AI 说"我要调用 bash 跑个命令"(一条 AI 消息),紧跟着是"命令的返回结果"(一条 Tool 消息)。这俩是一对,「谁也离不开谁」

如果切点不小心落在它们中间,就会留下一条"没有提问的答案"——一个孤儿 Tool 消息。绝大多数模型看到这种残缺结构会「直接报错」

DeepAgents(借助 LangChain 内核)的处理很优雅:如果发现切点正好压在一条 Tool 消息上,就「向前回溯」,找到发起这次调用的那条 AI 消息,把切点提前,让整对要么一起被摘要、要么一起被保留。token 预算用二分查找精确逼近,但「绝不以切断工具对为代价」

❝这就是为什么"保留最近 N 条"听着是个简单的数组切片,实际实现却有几十行——它得时刻护着这些成对结构。 ❞


四、魔鬼在细节②:压缩了,但原稿一个字不删

这是 DeepAgents 相比 LangChain 原生实现「最本质的一处分歧」,也是最值得记一笔的设计。

LangChain 原生怎么做?简单粗暴:在压缩节点里「直接重写对话状态」——把原始消息全删掉(RemoveMessage(REMOVE_ALL_MESSAGES)),换成"摘要 + 近期消息"。干净,但原稿没了。

DeepAgents 偏不。它「一个字都不删」原始消息,只在一个私有字段 _summarization_event 里记三样东西:「在哪儿切的、摘要内容是什么、旧历史归档到哪个文件了」。每次要调模型时,再根据这个"事件"临时算出精简后的视图喂给模型。

为什么要这么"轴"?因为保留原稿能换来一堆好处:

  • 「可回放(replay)」:原始对话还在,随时能重跑、复盘;
  • 「可做评测(evals)」:拿真实完整轨迹去跑评估,不失真;
  • 「能和手动压缩工具共享状态」:两条压缩路径读写同一份原稿,不会互相打架(见第六节)。

代价当然有:每次调用都要"临时重建视图",还要做一点切点坐标的换算。但对一个要长期、可观测、可调试运行的 Agent 平台来说,「留住原稿的价值远大于这点开销」


五、魔鬼在细节③:压缩 = 折叠 + 归档,不是删除

很多人以为"压缩"就是"把旧的扔了换个摘要"。DeepAgents 的理解更高级一层:「压缩是折叠,旧内容要归档,还要让 Agent 知道去哪儿找。」

正式压缩前,它会先把要丢弃的那批消息「追加写入一个 Markdown 存档文件」(按线程一个文件,每次压缩追加一节,带时间戳)。然后——这步最妙——「让摘要消息里带上这个存档文件的路径」

于是模型收到的摘要长这样(大意):

❝"你正处在一段已被压缩的对话中。完整历史已保存到 /conversation_history/abc.md,需要细节时可以去翻。下面是浓缩摘要:……" ❞

这意味着:上下文瘦下来了,但「任何被折叠掉的细节,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 和每个子 Agent 都各装了一份压缩中间件,它们的归档文件按 thread_id 命名——子代理是否复用了父线程 ID?若复用,多个代理的历史会不会追加到同一个文件里互相污染?
  • 「截断只认 write_file / edit_file:那些会吐超长输出的自定义工具(比如某些 execute)的大参数不会被截,长输出工具多的场景这层优化收益就有限了,是否该开放工具名白名单?
  • 「token 计数的两套签名」:内部对"带不带工具定义一起算 token"做了 try/except 兼容,某些计数器下工具占用没算进阈值——重工具场景会不会低估总量、压得偏晚?

这些都不是 bug,更多是"在通用与精确之间怎么取舍"的开放问题。也欢迎读过源码的朋友评论区开聊。


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

回头看,DeepAgents 的上下文压缩给我最大的启发是这句话:

「真正难的不是"怎么压缩",而是"怎么压得让 Agent 既轻装上阵、又随时能找回过去"。」

它的答案是一组克制而周到的工程选择:复用成熟内核不炫技、先用最便宜的手段省 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 2ac7d415langchain-ai/deepagentsmiddleware/summarization.py
  • LangChain v1(压缩内核 SummarizationMiddleware 来源)
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-06-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 海天二路搬砖工 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 开场:为什么"上下文"会变成负担?
  • 一、一句话看懂 DeepAgents 的设计哲学
  • 二、主流程:进模型前的三步流水线
  • 三、魔鬼在细节①:切点不能乱切
  • 四、魔鬼在细节②:压缩了,但原稿一个字不删
  • 五、魔鬼在细节③:压缩 = 折叠 + 归档,不是删除
  • 六、自动 + 手动:两层触发,一份状态
  • 七、把设计权衡列成一张表
  • 八、几个还值得琢磨的问题
  • 结语:好的压缩,是"忘得优雅"
  • 延伸阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档