最近社区里讨论度最高的一批AI公开课里,清华这套“多Agent课堂”确实值得耐下心看。我最大的收获不是又多认识了几个Agent框架,而是里面一个非常直白的工程观点:多Agent系统真正难的不是“定义几个Agent角色”,而是怎么把控制流管住,让Agent在边界内协作、在需要的时候像工具一样被调用、在共享信息的时候不会互相污染。这套材料里用了大量篇幅讲两个东西:主从模式下把subagent视作一种另类的tool进行调用,还有多Agent共享记忆的设计边界。这两个点,基本决定了一套多Agent代码是能上生产的架构,还是一次性演示Demo。
如果你已经能做到让单个Agent接工具、写函数、跑通一条业务流水线,正准备往“多Agent编排”方向走,下面这些思路应该能帮你省掉不少弯路。我不打算复述课程目录,也不准备按框架API顺序讲,而是把多Agent工程里最容易让人困惑的判断标准、主从调度、记忆共享、踩坑排障和落地步骤拆开聊一聊。
1. 看这门课之前,先想清楚一个问题:多Agent是控制流设计,不是模型能力设计
1.1 编排失控时,模型再强也救不回来
我先说一个反直觉的现象。很多项目最初决定上多Agent,是因为觉得单个Agent在一段很长很复杂的任务里“脑子不够用”,记不住上下文、遇到分支就乱、跑几步就自己改需求。于是想当然地认为,多安排几个Agent分工合作,问题就解决了。但真正把多个Agent接进去之后,往往会出现更头疼的现象:Agent A把Agent B的上一轮指令当成了用户消息;Code Agent和Review Agent互相改对方的输出,来回折腾七八轮,代码没变好反而越改越碎片化;整个对话走廊动不动就出现“两个模型在同一个会话里激烈辩论”的场面。
问题不是出在模型身上,而是出在控制流上。多个Agent本质上就是多个并发但有依赖关系的计算步骤,只是每一步的执行体带随机性。如果这些步骤之间没有明确的先后级、派发关系和收敛条件,那它们的行为等价于几个线程不加锁地在同一块共享内存里乱写。前一个Agent输出一个中间结果,后一个Agent拿它当输入继续发挥,发挥完之后又把结果写回去,结果偏差会像滚雪球一样叠加。强模型放进坏流程,也只能把话圆得更漂亮,结论依然是错的。所以从架构视角看,多Agent系统要当成一个带状态机的分布式程序来设计,不能当成“模型能力增强器”来看。
1.2 主从模式为什么会成为默认选项
公开材料里介绍多Agent编排时,几乎都会给出主从结构(Supervisor模式):一个顶层Agent负责理解总体目标、拆解任务、派发并汇总结果;底层若干个subagent各自负责一个尽量窄的领域,执行完成之后把结果交还给顶层。
主从能够成为多Agent设计的默认方案,不是因为它在学术上最炫,而是因为它让控制流始终有一个可见的决策出口。你可以介入两个subagent之间,可以单独重试失败的worker,可以调整某个子任务的优先级而不影响其他部分。自由协商或者全对等多Agent协作当然可以做,但它的问题是出错之后你很难定位:到底是哪个Agent理解错了?哪个Agent在最后一步把结论带偏了?没有明晰控制边界的多Agent系统,线上排障会变成一场灾难。
我这里说的主从,并不特指某个框架的Supervisor节点。它就是一套原始但有效的调度思想:给你一个顶层决策者,再给你一批“随时可以被顶层调度”的执行单元。顶层决策者负责规则,执行单元负责具体领域内的多步动作。
1.3 动手前先用四道题筛一遍:你的模块真需要拆成Agent吗
结合我自己做过的好几个Agent项目,我总结出一套判断标准。任何业务模块想上多Agent,我都会先拿以下四句话过筛:
交付物是否明确。如果子任务能被定义成输出一个结构化结果,比如JSON中的几个字段,那适合做成独立Agent;如果只能定义成“让用户聊得开心一点”,先别做。拿不出客观成功标准时,上层Agent无法验收worker的产出。
能不能用单次LLM调用解决。如果一次普通函数调用加一个LLM就能得到满意结果,那它就是一个工具,不是Agent。不应该为了名词统一而强行包一层Agent。
是否需要多步工具交互。把一个子模块单独做成Agent的典型理由应当是:它可能需要先搜索,再起草,再调一个校验工具,发现不合格之后自我修正,反复直到满足条件。一个天然循环的过程才值得被封装成Agent。
是否能在子任务级别重试。如果某个worker执行失败之后可以安全地重启,只丢失该任务内部的工作内容,那就适合独立拆分。如果前面所有步骤都会被这次的失败污染,那引入多Agent只会成倍放大故障。
按这个标准过完一遍之后你会发现,能稳定产出、边界清楚、需要多轮修正的那部分任务,才真正值得做成Agent。放到整个系统里看,这套筛选思路也正对应了主从模式下subagent的定位:它是一个需要独立持有一段状态并完成一段完整动作的执行单元,而不是一个需要平等参与全局讨论的“同事”。
2. 主从模式的本质:subagent是另一种tool,不是另一个老板
多看几节课的材料之后,你会发现一个反复被强调的观点:最新的多Agent设计中,主从模式下的subagent,本质上是将子Agent作为一种另类的tool进行调用。这个说法初看有点把Agent“降维”的意思,实际上它是在纠正一种常见误区。
2.1 别用自然语言放养subagent,要用接口契约约束它
很多人刚开始设计多Agent时,喜欢给每个subagent写一段“你是资深前端工程师,精通想象力和创造力,请和我一起完成项目”这类宽泛人设,然后等着它自己发挥。这种设计在示例场景里能跑通,但在真实任务里非常难控制。它缺少的是“契约”:一个subagent任务进来时输入长什么样、任务完成后必须输出什么结构、失败时怎么上报,都没有被约束。
想让主Agent“调得动、判得准”一个subagent,就得按设计普通函数的方式设计它。每个subagent需要先定义四件事:
- 入参结构:除了用户任务本身之外,需要携带哪些上下文、文件路径、会话标识。入参要固定成一种可序列化格式,不能靠Agent猜。
- 出参结构:建议统一为状态加结果。成功时返回结构化数据,失败时返回错误码和可读错误信息。
- 最大执行步长:避免worker内部陷入无限循环。每一步都消耗tokens和时间,没有上限的Agent相当可怕。
- 终止条件:明确“什么时候算完成”,比如输出经过校验工具、所有指定动作已执行完、或者达到任务允许的循环上限。
在这些约束都定清楚以后,subagent的行为就不会飘忽不定。它变成了“一段可以接受任务、内部运行多步、稳定吐出结果的受控函数”,对上层而言,它和工具函数在形式上确实没有本质差别。
2.2 Supervisor视角:每个subagent都像一个普通function call
为了让“subagent即tool”更好落地,可以直接在调度层做一个统一抽象。顶层Supervisor看到的工具列表里,既包括普通的API查询函数、向量检索工具,也包括subagent的入口。对主Agent而言,它只需要做一件事:根据任务意图选择一个“工具”,并生成符合要求的参数。至于那个“工具”内部会不会继续调用其他模型、会不会跑好几轮检索,上层并不关心。
我用Python风格伪代码展示一个最朴素的实现思路:
WORKERS = {} def register_worker(name): def decorator(fn): WORKERS[name] = fn return fn return decorator @register_worker("researcher") def research_worker(task: dict, memory_bus): # 内部真正跑一个带多步工具的Agent循环 steps = [] for _ in range(task.get("max_steps", 5)): # 让worker模型决定查资料、写草稿还是做校验 action = worker_llm_call(task, steps) steps.append(action) if action["status"] == "done": break # 统一吐出一个结构化结果 return { "status": "succeeded", "summary": steps[-1].get("result"), "logs": steps, "usage": total_token_usage }Supervisor调度部分可以把这些worker全部注册成一个tools列表:
TOOL_SCHEMAS = [ { "type": "function", "function": { "name": "researcher", "description": "调用研究型子Agent。当需要多轮检索、对比资料并输出总结时使用。", "parameters": { "properties": { "task": {"type": "string", "description": "本次调研的任务描述"}, "max_steps": {"type": "integer", "description": "内部最大执行轮数"} } } } }, # 其他function calling工具... ]主Agent执行时看到tools列表中有一个叫researcher的函数,它判断当前任务适合调用这个函数,于是生成一段tool_call参数。真正执行这个tool_call的代码,在内部启动一个独立的subagent会话,给这个worker注入它自己的专属提示词、专属工具栈、专属记忆片段。worker内部的模型自己决定要搜索几次、要不要调整输出,直到达到出口条件后,把精简后的结果返回给主模型。对主模型来说,subagent和fetch_score之类工具的差别只是执行时间更长、输出更复杂、可能有更多内部副作用。这正是把它当tool看、而不是当“平级协商者”看的合理之处。
2.3 subagent什么时候应该“降级”成普通函数调用
如果你认同“subagent是另一种tool”,那么反向操作就是:如果一个模块的复杂度不够格当Agent,就别让它硬套Agent壳。
举个实际例子。有人会把一个“意图分类器”做成subagent,理由是分类器内部也要调用一次模型解释一下用户语义。但在我的实施经验里,这种场景更适合直接暴露成function calling或者普通LLM处理函数。意图分类通常是短任务,一次模型调用就能完成,中间不需要状态、不需要多步循环,强行包一层subagent只会增加一层tool调度时间,多了一个可能失败的节点。
什么情况下才值得把某个模块升级为真正的subagent?我的标准是:这个子任务需要独立持有上下文并自主决策多轮动作,且动作之间存在依赖关系,完成后必须产出一个可以被上层校验的结果。如果一个模块只是“调一次模型,根据输出选择分支”,那它就是普通函数;只有当一个模块需要“先取数据,再完成草稿,再用脚本校验,不过就改,直到输出能通过检查”时才需要Agent化。把普通函数当工具管,把具备内部循环能力的Agent也当工具管,可以让整个系统保持一致的调用范式,这个思路在复杂业务里尤其有价值。
3. 多Agent共享记忆:共享的从来不是“同一份聊天记录”
网络热词里好几次提到“多Agent共享记忆”。不少初学者会把它理解成:既然Agent之间要协作,那就应该让每个Agent都看到对话历史的全部内容,这样大家信息才一致。实践一遍你就会发现,这基本是在制造灾难。上下文成倍膨胀、Agent互相看到无关信息、前序任务里的污染内容被后续任务当成了新事实,问题远比“记忆太少”更严重。
共享记忆的关键不是“要不要共享”,而是共享什么、让谁看见、保留多久。我把需要共享的内容先分成三类:
- 任务上下文:当前用户正在做什么、输入是什么、目标是什么。这部分通常在会话窗口里维护,适合给执行型subagent传递窄切片。
- 中间产物:Agent A产生的结构化结果,Agent B需要在此基础上继续加工。这种情况不应该靠“共同读聊天记录”来传递,而应该作为明确的入参,由主Agent在调度时传给下一个worker。
- 长期知识:过去项目沉淀下来的模式、用户偏好、跨session的历史结论。这部分一般放到外部记忆库,而不是塞进Agent上下文。
在分析完共享内容后,再选择工程实现,你会有一种“豁然开朗”的感觉。
3.1 三种主流记忆共享方案:共享窗口、向量记忆库、事件日志
围绕上述三类内容,目前最常见的实现有三种。
共享工作区,也就是一块所有人都能读写的文本区域,所有Agent把上下文历史、中间结论、待办事项写在同一份数据里。优势是实现非常简单,只要一个全局变量或一份消息列表就能完成,信息一致性很高,适合流程比较短、Agent数量少的Demo。坏处也明显:随着任务推进,令牌会快速膨胀,所有Agent都被迫把越来越多的无关历史当成上下文接收;两个Agent同时写同一块区域还容易互相覆盖。我建议只把共享窗口用在两到三个Agent、任务总步骤不超过十几步的小闭环里。
向量记忆库,适合承载长期知识类内容。Agent需要在任务开始时检索知识片段,在任务结束后把新发现的关键信息写入向量库。它的优势是能支持超长历史,跨session经验共享效果好,Agent只加载与当前任务相关的记忆片段。缺点是需要额外维护一套索引,记忆检索本身不一定稳定——有可能召回一些噪声数据,从而降低后续决策质量。
事件日志/消息总线,让所有Agent之间的协作都以结构化事件的形式沉淀下来,像流水账一样记录完整执行链路。每个Agent按主题订阅自己关心的事件,主Agent可以看到全局事件流。这种实现的生产优势非常独特:数据可追溯,出错可以重放,测试时可以拿一段完整事件序列做回归。缺点是需要设计清晰的事件Schema,还要有配套的基础设施,不适合小项目开箱即用。
三种方案的定位差异可以用这张表快速对照:
| 实现方式 | 最大优势 | 最大短板 | 推荐场景 |
|---|---|---|---|
| 共享工作区/共享窗口 | 实现简单,上下文完全一致 | token膨胀快,互相干扰明显 | 小规模、流程短的Demo |
| 向量记忆库 | 可扩展,支持长期记忆 | 检索有噪声,需维护索引 | 知识复用、跨session场景 |
| 事件日志/消息总线 | 可追溯,可重放,利于观测 | 需要额外基础设施与Schema设计 | 追求稳定性的生产级系统 |
3.2 由谁来写记忆,比记忆放哪里更重要
写共享记忆时最忌讳的是“每个Agent都能写”。当所有worker都可以随时向共享状态里添加自己的结论时,你实际上制造了一个没有锁的并发写环境。两个Agent各自基于不同阶段的信息产生结论,又都把它们写入同一个全局状态,后写的人覆盖先写的人,系统就开始出现无法解释的“记忆漂移”。
在我自己的设计里,会刻意把共享记忆的写入权收拢。执行型subagent产生的中间结论先返回给顶层Agent,由顶层决定哪些信息值得持久化,哪些只是临时推理产物,哪些需要废弃。写入操作的入口尽量收敛