1. 先给结论:多 Agent 协作的瓶颈根本不是模型能力
这两年做 AI Agent 的人越来越多,从单 Agent 的 Demo 走向多 Agent 协作时,大家嘴上说的都是“我的模型太笨了”“推理能力不够”“需要上一个更强的模型”。但在我实际折腾过的几个项目里,真正让整个系统翻车的,几乎都不是模型智商,而是“失忆”。
所谓失忆,就是多个 Agent 之间无法共享、继承和回溯上下文——前一个 Agent 说过的话、做出的中间决策、已经完成的状态,到了后一个 Agent 那里全部归零。你让 A Agent 去检索资料,B Agent 拿着它产出的结果去写报告,结果 B 根本不知道 A 干了什么,于是又自己脑补一遍资料,最后两份内容对不上。这种事情在单 Agent 场景里几乎不会暴露,因为单 Agent 聊着聊着还能靠对话历史撑一撑,但多 Agent 一拆开,记忆链条一断,整个系统立刻露出原形。
这篇文章的读者,应该是已经在跑 Agent 项目、或者正准备从单 Agent 升级到多 Agent 架构的人。不管你是用 OpenAI 的 Assistants API、LangGraph、AutoGen,还是自己手搓了一套 Pipeline,“失忆”问题你大概率都会碰到。我把自己的踩坑过程、排查思路和最后的工程方案都写下来,希望能让你少走一两周弯路。
1.1 “失忆”不是比喻,是上下文窗口的物理局限
先聊本质。很多刚入门的人会觉得,Agent 不就是 LLM 套个循环吗?只要模型强,对话历史长,它不就“记住了”吗?
这个理解在单 Agent 场景近似成立,但在多 Agent 场景会出大问题。核心原因在于:每个 Agent 的模型上下文窗口是有限的。你不可能把所有 Agent 的历史对话、中间输出、工具调用结果全都塞进同一个上下文里,塞不下的后果有两个——
一是成本爆炸。Tokens 是按量收费的,上下文越长越贵,多 Agent 系统里如果每个节点都拷贝全量历史,那开销是呈指数级上涨的。
二是注意力稀释。模型在处理超长上下文时,对中段信息的关注度会显著下降,信息不是“没在那”,而是“看到了等于没看到”,这就是所谓的“Lost in the Middle”。当你指望 B Agent 回忆起 A Agent 在 20 轮之前埋下的关键字段时,它可能真的“忘了”——不是模型笨,是注意力机制天生有取舍。
这还只是物理层面的局限。更麻烦的是工程层面的问题:你给 Agent 之间传什么、怎么传?是传原始对话?传摘要?传结构化 JSON?传向量索引?每一步都涉及“信息损耗”,而损耗的累积最终就表现为协作系统的失忆。
1.2 “模型强”和“协作稳”完全是两码事
我见过不少团队,一开始用 GPT-4o 或者 Claude 级别的模型跑多 Agent,觉得既然每个 Agent 都那么聪明,整体肯定没问题。结果真跑起来,发现聪明的模型反而更容易“自由发挥”——因为推理能力强,它会在上下文缺失时自动脑补出合理的中间步骤,然后基于脑补的内容继续往下走,最后给出的结果看起来逻辑通顺,实际上从根上就是错的。这种错误很隐蔽,排查起来极其痛苦。
而弱一点的模型遇到信息缺失,可能直接报错、说“我不知道”,反而更早暴露问题。
所以要清楚:多 Agent 协作的稳定性,严重依赖记忆机制——谁来存、谁来取、什么时候失效、失效了怎么办。模型只是执行机构,记忆系统才是整个协作网络的地基。地基没打牢,你换再强的执行机构,它也只是在废墟上跳舞。
2. 多 Agent 协作里,失忆的三种典型形态
为了让你对“失忆”有更具体的感知,我把踩过的坑分成三种形态:链路型失忆、并行型失忆和动态拓扑型失忆。它们成因不同、表现不同、排查方法也不同。
2.1 链路型失忆:后一个 Agent 不知道前一个 Agent 说了什么
这是最常见的一种,也是大部分人的“第一个多 Agent 项目”会遇到的情况。比如你搭了一条流水线:Planner -> Researcher -> Writer -> Reviewer。Planner 定好大纲,Researcher 根据大纲查资料,Writer 根据资料写文章,Reviewer 最后审核。
表面上这是合理的流程设计,但实际运行时,问题一个接一个地冒出来:
- Researcher 可能没有拿到 Planner 的完整大纲,只拿到了一个方向性关键词,于是搜出来的资料和计划完全无关。
- Writer 看到的是 Researcher 返回的一堆原始 URL 和摘要,却不知道哪些是重点、哪些是排除项,只能凭感觉写。
- Reviewer 看到的是一篇已经写好的文章,却不知道最初的用户需求是什么,只能基于“文章本身的合理性”来评审,等于没审。
你说“那我在调用下一个 Agent 时把前一个 Agent 的输出传进去不就行了吗?”——这是最朴素的传参思路,但它有个致命缺陷:你需要手动定义每一跳的参数映射。项目一复杂,比如 Planncr 产出 30 个字段,Researcher 产出 80 条资料,Writer 只用到其中 12 条,Reviewer 又需要原始用户诉求加上 Writer 的自我说明……你会发现信息链越来越臃肿,每个环节都在“精简-失真-再精简-再失真”,等到最后一个 Agent,它拿到的东西可能只剩第一跳的 20%。
我在一个实际项目里就碰到过这种状况:系统生成了看起来很专业的行业研究报告,但里面引用的数据来源和最初用户指定的数据范围完全对不上。后来一查日志,发现是第三跳的 Agent 在传参时把“用户指定年份”这一个字段丢了,而后面的 Agent 全都没有校验,直接在错误年份上继续查、继续写,最后产出了一个“看起来很美、实则全错”的结果。这个还不是模型能力问题,就是我说的链路型失忆。
2.2 并行型失忆:各写各的,谁也不知道别人改了啥
比链路更坑的是并行场景。多 Agent 协作里经常要并行处理多个子任务,比如同时让几个 Agent 分别调研不同方向,然后汇总。
并行带来一个严重问题:子任务之间天然是隔离的,彼此看不到对方的中间状态。
举个例子。我做过一个内容聚合系统,三个 Agent 分别负责写“行业趋势”“竞品动态”“技术方案”三个章节,最后有一个 Summarizer 汇总。刚开始觉得很简单,跑起来才发现:
竞品动态 Agent 引用了“某厂商在 6 月份发布了新版本”这个信息,这个信息其实来自行业趋势 Agent 的某条资料,但因为两个 Agent 并行跑,竞品动态 Agent 只能靠自己的检索能力去猜,猜错了就自己编了一个日期。
技术方案 Agent 在写建议时,会自然地引用趋势章节的结论,但由于它在并行环境下没有拿到趋势章节的最终结果,只能基于一个早期草稿来引用,结果汇总时发现方案和趋势内容打架了。
这种并行型失忆的本质,是缺乏共享状态。单个 Agent 的成功不意味着系统的成功,你需要的是一个公共的“白板”——每个 Agent 在上面写自己做了什么、产出了什么、依赖了什么,其他 Agent 可以去读,也可以在冲突时回溯。
如果这块白板不存在,你面临的就不是“某个 Agent 能力不足”,而是整个系统的信息孤岛化。每个 Agent 都在局部最优解上努力,但因为看不到全局,最后拼出来的整体反而是矛盾百出的。
2.3 动态拓扑型失忆:Agent 数量一变,记忆全乱
还有一种更隐蔽的情况,发生在动态 Agent 拓扑的系统里。比如你用 Agent 来动态判定子任务该交给谁处理,或者在多轮对话中动态创建新的 Agent 去完成临时任务。
动态拓扑的难点在于:你不仅要维护每个 Agent 自己的记忆,还要维护 Agent 之间的“关系记忆”——谁是谁的上游、谁消费了谁的结果、哪个 Agent 是被临时创建出来处理某个子问题的。
我有一次做“智能招聘助手”的 MVP,用了一个动态规划的思路:第一轮让 HR Agent 解析招聘需求,然后动态决定要不要创建“技术面 Agent”“人力面 Agent”“背景调查 Agent”。测试初期没有问题,因为流程都是顺序执行的。但当我加了一个“补充面试 Agent”——因为技术面 Agent 觉得候选人的项目经验不够清晰,需要进一步追问——问题立刻来了。
补充面试 Agent 结束后,它需要把新信息并入原来的候选人评价,但主 Agent 已经基于早期的评价给候选人打了分。新信息来了之后,主 Agent 完全不知道该更新哪些分数、更新后需不需要重新走一遍后续流程。于是系统里出现了一个诡异的现象:候选人最开始被评价为“不推荐”,但补充面试完又被标注为“强烈推荐”,而终态报告里两个结论同时存在,没有任何冲突检测。这就是动态拓扑失忆——新增节点没有妥善接入既有记忆网络,导致信息在结构层面发生了断裂。
3. 一次印象深刻的失忆大排查:根因居然在缓存层
前面讲的都是形态,这一章我完整复盘一次真实排查过程。这个案例特别典型,因为最终根因既不是模型弱,也不是逻辑设计失误,而是出在很多人根本不会多看一眼的中间缓存层。
3.1 业务背景和故障现象
当时我们在做一个多 Agent 的智能客服系统。结构是:一个意图识别 Agent(负责判断用户要什么),一个知识库检索 Agent(负责找答案),一个回答生成 Agent(负责组织话术),一个转人工判定 Agent(负责判断要不要把用户转到真人客服)。
上线头几天效果不错,用户满意度也挺高。但一周后运营反馈:用户经常问“我刚刚说了什么”之类的问题,客服系统回答得驴唇不对马嘴。比如用户问了一句“订单怎么还没到”,意图识别判定为“物流查询”,检索 Agent 查了物流信息,回答 Agent 给了标准的“您的包裹正在运输中”话术。但当用户在下一轮继续追问“那退款呢?”,系统居然还回复物流信息,完全没有识别出“退款”是新意图。
一开始我们怀疑是意图识别模型的问题,换了几种模型,现象还在。又怀疑是链路编排问题,但人工回放日志,每一步的输入输出都是按流程走的,逻辑上没有任何跳步。后来才发现,问题真正出在缓存。
3.2 排查链路:从模型到 Prompt 再到缓存
我们当时的排查分了三步。
第一步,对比单轮与多轮表现。把同样的用户对话喂给一个“不经过多 Agent 编排”的裸模型,发现裸模型在多轮语境下能正确识别“退款”意图。这说明基础模型的上下文能力没有问题,问题出在多 Agent 编排过程中的信息传递。
第二步,导出各 Agent 的实际输入。我们发现检索 Agent 的输入里只包含当前用户问题,完全没有上一轮用户的原始诉求。这不是设计图上的样子,设计图上明确写了“需要传入最近三轮对话上下文”。那上下文去哪了?
第三步,追踪上下文的来源。发现上下文是由一个叫 ConversationMemory 的模块提供的,而这个模块为了“提高性能”,在内存里做了一层 LRU 缓存,只保留高频命中的对话片段。由于“物流查询”相关的历史对话比较容易命中缓存,而“退款”相关的上下文片段在用户第一次提出时根本没有被写进缓存(当时系统判定暂不需要退款能力),于是一个新意图出现时,缓存里没有它需要的上下文,ConversationMemory 返回了一堆无关的历史内容,检索 Agent 也就跟着跑偏了。
现在看这个根因很搞笑——缓存层的淘汰策略和多 Agent 的上下文需求完全错配。缓存关心的是“哪些内容被频繁访问”,而 Agent 需要的是“哪些内容属于当前逻辑链路”。这两者根本不是一个维度。我们用了很多精力去优化模型、优化 Prompt,最后却栽在了一个表现层为了性能而做的缓存上。
3.3 从这次排查中得到的三个判断
这件事给我带来三个后续一直受用的判断:
第一,排查多 Agent 问题时,先看“信息是否忠实传递”,而不是先怀疑模型。很多表层的错误,往深处挖都是某个中间环节把信息丢掉了。大部分 Agent 框架自带的日志,默认记录每个节点的输入输出,但不会告诉你“某个输入字段是否被意外丢弃”,你需要自己去拼链路。
第二,任何中间状态管理模块(缓存、向量库、内存存储)都必须知道自己在为谁服务。缓存不是越强越好,而是要能理解 Agent 协作的语义边界。简单说,你在缓存设计里必须允许“按会话 ID + 任务 ID 绑定上下文”,而不是只按内容相似度匹配。
第三,人肉复现成本很高,但不可跳过。我们当时为了定位这个问题,连续人工跑了三天同一个用户场景,每一步都截图保存,才终于确定了丢失发生在哪一环。虽然事后看如果能更早跟踪缓存,肯定能省时间,但如果你不做这种“土法复现”,很多问题是根本定位不到的。
4. 工程上到底怎么解决“失忆”:我的分层记忆架构
聊了这么多问题,肯定要给方案。经过好几个项目迭代,我把自己的实践整理成了一套自认为目前最稳的架构——分层记忆服务,加两条共享链路规则。不需要引入特别复杂的外部组件,用常见的 Redis、向量库和消息队列就能搭起来。
4.1 工作记忆、情景记忆、语义记忆,三者分开存
我先把记忆空间分成三层,对应不同生命力周期的数据:
工作记忆:服务于当前正在进行的任务链。比如“用户当前这个问题的原始诉求”“上一个 Agent 返回的中间结果”“正在等待哪个 Agent 的回复”。这类数据生命周期极短,任务一结束就可以清掉,但读写频率最高。我通常用 Redis 的 Hash 结构存储,键格式为
session:{会话ID}:task:{任务ID}:memory,字段按照 Agent 名称命名。情景记忆:记录“这个会话发生了什么事”。比如用户在第 3 轮问过物流,第 4 轮问退款,检索 Agent 当时返回了什么结果。这类数据需要跨任务保留,但不需要像向量库那样做复杂语义检索,直接用结构化日志存储就行。我习惯用 MongoDB 或者 PostgreSQL JSONB 存,按会话 ID 和轮次索引。
语义记忆:沉淀到全局的、可以复用的事实知识。比如“这个店铺的退款政策是什么”“这个知识库里的某个知识点最近被高频命中”。这类数据适合放向量库,比如用 pgvector、Qdrant 或者 Chroma,用来支撑“跨用户、跨会话的知识召回”。
为什么要分层?因为不同生命周期的数据,它的读写模式、容量需求和一致性要求完全不同。如果你把工作记忆和语义记忆都塞进同一个向量库,结果就是:短时任务里的临时状态被向量相似度匹配污染,需要精确读取时拿不到完整字段;而长时知识又因为夹杂太多临时噪声,召回质量变差。分层之后,你还可以针对每一层单独做性能调优和容量规划。
4.2 写入比读取更重要:别让记忆变成垃圾桶
很多刚开始做记忆系统的团队,把大把精力花在“怎么优雅地读取记忆”,却很少设计“怎么写入记忆”。结果就是什么数据都往记忆里塞,过两天检索出来的全是垃圾,于是得出结论:记忆没用。
我的经验是,写入阶段就要做取舍。有三个基本规则:
每条记忆必须带来源标签。谁产生、在哪个任务、什么时间,缺失这三项的直接过滤掉。这不仅能帮你做审计回溯,也能让后续 Agent 在读取时知道“这条记忆的置信度该打几分”。比如带 Reviewer 确认标签的记忆,优先级就应该高于某个 Agent 自己写的草稿。
高价值信息做强结构化,低价值信息做摘要。拿多 Agent 协作来说,不是所有中间字段都需要进记忆。我们把“每个 Agent 的输入、输出、调用的工具列表”这些核心信息,以结构化 JSON 方式原样写入;而“中间推理过程”“被剪掉的草稿片段”这些冗余信息,则用摘要方式写入,摘要丢掉细节可以接受。
删除与更新要显式触发。多 Agent 系统里最怕的就是一个 Agent 更新了自己记忆,但不知道下游 Agent 已经读取了旧版本。所以我们的记忆服务必须支持版本号,每次写入都会产生新版本,下游 Agent 读到的记忆都会带上版本标记。一旦发现版本落后,可以触发级联刷新或者至少发起冲突提醒。
4.3 用事件总线做同步:而不是让 Agent 互相直连
另一个关键设计是:不要让 Agent 之间直接互相读取记忆,而是通过一个事件总线来同步。为什么要绕一层?是因为直接互连会形成网状的强耦合,链路编排一动就散。
我的做法是,每个 Agent 在完成任务后,把结果发布为一个领域事件。比如ArticleWritten、SearchCompleted、ScoreUpdated。事件总线负责把事件写入情景记忆层,并完成一次语义索引更新。下游 Agent 在需要时,向记忆服务发起查询,而不是直接去拿上游 Agent 的内存对象。
这样做的好处至少有四点:
解耦。上游 Agent 挂了、换版本了,下游 Agent 感知不到,它只依赖记忆服务里的数据。
可重放。事件总线支持按时间窗口重放事件,这意味着如果某次协作中 Agent A 的执行结果有误,你可以修正后重放 A 的事件,下游 B、C 就能基于修正后的记忆自动重新产出,不需要人工手动改。
审计友好。任何一次跨 Agent 信息流转,在事件总线里都有记录,出了问题可以沿着事件链路回溯,而不是靠猜。
并发控制。并行 Agent 同时写同一记忆条目时,可以通过事件总线的顺序化来避免互相覆盖。
4.4 每个 Agent 还必须有自己的“记忆接口层”
除了中央记忆服务,每个 Agent 内部也建议封装一个轻量的 memory 适配器,而不是直接调用模型 API。这个适配器的职责只有一个:在上文构造时,从中央记忆服务拉取当前任务需要的记忆片段,然后拼装成符合当前模型上下文长度的输入。
这个适配器至少要实现三个函数:
remember():从记忆服务拉取指定维度的上下文,支持白名单字段。forget():主动丢弃不再相关的记忆片段,减少上下文噪声。review():在 Agent 输出之后做一次一致性检查,比对关键事实与记忆里的记录是否冲突。
有了这层,你在换模型、调 Prompt、增删 Agent 时,大部分改动都会集中在这个适配器里,而不是重写整个编排逻辑。这也算是我踩过那么多坑之后比较推荐的“反脆弱设计”。
5. 一份可以直接抄的失忆排查清单
前面讲了很多原理和架构,最后给你一份我每次遇到多 Agent 行为异常时都会跑的排查清单。这个清单是按顺序执行的,每查一步都可以直接操作,不用绕弯子。
5.1 五步定位法
确认当前 Agent 实际拿到的上下文内容。这一步最容易被忽略。不要看代码里传了什么,要看运行时真正到达 Agent 的 Prompt 长什么样。大部分框架支持在调试模式打印完整 Prompt,如果框架不支持,直接在 Agent 入口打一行日志输出收到的上下文。很多人这一步一做,问题就解决了一半——因为绝大多数“失忆”都发生在传输层,不是你逻辑写错了,而是某个字段没传过来。
检查记忆写入的时机。如果 Agent 读取时发现缺少某个信息,先问:这条信息在源头上有没有被写入记忆?很多时候,不是因为读取逻辑有问题,而是因为写入逻辑压根没触发。比如上一轮 Agent 结果只在内存里被下游直接消费,而没有同步进记忆服务,那一旦链路重来,这个信息就永久丢失了。
对比单 Agent 与多 Agent 的行为差异。用同样的输入跑一遍裸模型(或单个 Agent),看它是否产出正确答案。如果裸模型能答对而多 Agent 答错,那和模型智商无关,九成是信息传递问题;如果裸模型也答错,那才回去检查模型和 Prompt。
引入版本号追踪。在所有记忆读写接口加一个
version字段。当系统出现“下游用了旧数据”的迹象时,通过版本号可以快速判断是哪一环更新了数据却未通知下游。做一次全链路回放。把事件总线的所有事件按时间排个序,检查有没有逻辑断裂——某个 Agent 引用了另一个 Agent 根本未发布的事件类型,这种情况在动态拓扑里特别常见。
5.2 一些容易踩的隐藏坑
再说几个你以为你知道了,但真到项目里还是会踩的隐藏坑。
第一,摘要型记忆千万别太“贪心”。很多团队会把上一层 Agent 的完整输出直接硬塞给模型去生成摘要,然后把摘要存进记忆。问题在于模型摘要终究有信息损耗,特别是一些数字、日期、ID 类的字段,模型会为了“流畅”而改写。后来我们咬咬牙,把结构化关键字段一律按原始文本存取,只有纯文本叙述性内容才走摘要。
第二,别让 Agent 之间的“礼貌社交”占用宝贵上下文。多 Agent 协作时,上游 Agent 的输出经常会包含“好的,我来帮你查一下”“根据您的需求,以下是我找到的信息”之类的对用户说话的话术。这些花边内容完全不适合作为下游 Agent 的输入,我们的做法是定义中间消息格式:{role: "assistant", content: "纯数据结果", meta: {结构化字段}},每个 Agent 输出前统一做一次“脱壳”,把面向用户的话术剥离,只留纯数据和分析。
第三,测并发时一定不要只看最终结果对不对,要看中间产物是否一致。并行 Agent 最容易出现的一种情况是,每个 Agent 最终输出都正常,但中间互相引用的数据来自不同的时间快照,导致汇总时出现“左右互搏”。我们的解决方案是在中央记忆服务里加入快照隔离:每个任务开始执行前,给它固定一个记忆版本,这个版本在整个任务生命周期内不发生变化,保证所有读取都基于同一份数据。
收尾:一个关于“记忆”项目的个人体会
最后说点比较个人化的东西。我做了几个多 Agent 项目之后最大的感受是:Agent 系统的复杂度,并不体现在模型调用上,而体现在状态管理上。每一个 Agent 都是一个有行为的小单元,但它们能否协同工作,取决于整个网络有没有一套可靠的“记忆神经系统”。而这套神经系统的构建,远比选哪个模型、调什么温度参数要费心思。
如果你现在正准备启动多 Agent 项目,我的建议是:先把核心流程跑通,哪怕用最丑的方式传参,然后把所有中间状态打印出来,对着日志梳理一遍信息流。梳理完之后,你自然会发现哪些信息是必须共享的、哪些是可有可无的、哪些环节需要记忆服务介入。到这一步,再上分层记忆也好、事件总线也好都会觉得顺理成章。
以后再遇到“Agent 行为很奇怪”的问题,先别急着甩锅给模型。给记忆系统做个体检,大概率你会发现,真正该升级的不是模型卡,而是你们的记忆线路。