1. 设计多Agent之前,先想清楚这四个问题
多Agent最近的热度确实高,随便打开一个技术社区的AI板块,都在聊多Agent框架、Agent编排、角色扮演协作这些概念。但我的态度一直是:多Agent是手段,不是目的。如果你只是想处理一个简单的问答任务、做一次文档总结,单Agent的链式提示完全可以搞定,硬上多Agent只会引入成倍的调试痛苦和token消耗。
真正值得上多Agent的场景,通常具备下面几个特征:任务本身可以被切分成多个专业方向,每个方向需要不同的知识背景或工具能力;单一模型在长流程中容易丢失上下文,需要多个阶段性决策点来保持方向感;任务结果需要多方校验、多路并行提速,或者需要模拟多个利益相关方之间的交互博弈。如果你只是想做“阅读理解”,没必要造一个议会出来。
在我开始任何多Agent项目之前,都会先逼自己回答四个问题:
- 拆分维度是什么?是按子任务拆、按角色拆,还是按数据域拆?拆错了,后面全部白搭。
- Agent之间共享什么、隔离什么?共享上下文太多会互相污染,隔离太干净又无法协作。
- 谁负责“拍板”?并行产出的结果如何汇总、冲突如何裁决?没有决策机制的多Agent就是一群无头苍蝇。
- 失败怎么兜底?某一个Agent超时、返回垃圾结果,系统是重试、降级,还是让另一个Agent补位?
这四个问题答不上来,架构图画得再漂亮都没用。接下来我就按这个思路,把多Agent设计里真正核心的环节拆开讲。
2. 先搞清楚多Agent拆分的两种底层逻辑
2.1 按角色拆:让每个Agent成为“领域专家”
按角色拆分是最符合直觉的方式。比如做一个行业研报分析系统,你拆出“数据采集Agent”“财务分析Agent”“行业趋势Agent”“合规审查Agent”,每个Agent专注一条流水线的一段。这种拆法的核心优势是职责单一、提示词可以写得非常聚焦,每个Agent内部的工具集和知识库也可以单独配置。
角色拆分的难点在于:角色的边界很容易模糊。比如“财务分析Agent”和“行业趋势Agent”都要看一份公司公告,结果两个Agent分别用各自的工具去抓取、各自总结,最后汇总时你会发现信息大量重复,甚至结论互相打架。我的经验是,角色拆分时一定要明确“信息的所有权”:一种类型的信息只能由一个Agent做主负责解析,其他Agent只能从共享数据区读取加工后的结果,不能自行去抓同源数据。
2.2 按阶段拆:用流水线思维降低单点复杂度
另一种拆法是把任务切成“先做什么、再做什么”的阶段。典型就是一个“需求文档生成系统”:先用“需求采集Agent”对话用户,产出结构化需求条目;再由“方案Agent”根据条目生成技术方案;最后由“评审Agent”挑毛病并给出修改意见。这种流水线式拆法,本质上是把一个大任务的推理深度分摊到多个节点上,每个节点只做一类推理。
按阶段拆的关键是节点间的交付物必须非常明确。我的习惯是给每个阶段定义一个“产出Schema”,比如需求采集Agent必须输出一个JSON,包含需求编号、类型、优先级、验收标准。后面所有阶段只认这份结构化产物,不认自然语言段落。这样做的目的有两个:一是让交付物可以程序化校验,字段缺失直接报错重跑,而不是等下游Agent读了半天才懵;二是保留中间产物,调试时可以逐段复盘到底哪一个节点引入了错误。
2.3 拆分的“粒度”到底怎么定
粒度问题比大多数人想象得重要。拆得太粗,每个Agent要处理的子任务仍然很复杂,提示词会变得巨大,上下文窗口压力大,效果等同于没拆;拆得太细,Agent之间的通信开销、框架调度开销、token重复消耗都会指数级增长,而且一个Agent出错会像多米诺骨牌一样传给下一个。
我的判断标准是两个“不超过”:单个Agent的核心职责说明超过四句话,说明拆得太粗;被负责的Agent超过十个,或者存在三个以上需要共享同一个状态的Agent,说明拆得太细。正常的中小型多Agent系统,控制在五到七个Agent以内是最舒服的,既能体现分工价值,又不会让调度复杂度失控。
3. 角色设计与提示词:多Agent的灵魂是边界感
3.1 每个Agent的提示词必须是一份“岗位说明书”
很多新手写多Agent提示词,就是把单Agent的提示词复制几份,改个名字,然后指望它们自动协作。这是多Agent系统失败的第一大原因。千万不要忘了:单Agent是给模型一套完整指令,让它全程负责;多Agent是给模型一套受限指令,让它在一个岗位内做深。
一份合格的角色提示词,应该包含五个部分:
- 身份与目标:你是谁,你最终要交付什么。
- 职责范围:哪些事情归你管,哪些事情明确不归你管。
- 输入输出格式:你接收什么格式,必须产出什么格式。
- 协作关系:你的上游是谁,下游是谁,和谁并行、和谁互斥。
- 能力边界:遇到超出范围的任务时,你是转交、拒绝还是上报。
其中“明确不归你管”和“遇到超范围任务怎么办”这两点,我见过的90%的团队会漏掉。漏掉的后果很典型:你让“代码审查Agent”检查一段代码,它发现技术选型不太合理,于是开始长篇大论重写方案——它发出了一个原本不属于它的输出,把下游Agent或者编排器全带偏了。给Agent划定边界,和给它目标同等重要。
3.2 用功能块描述替代“专业词汇咒语”
写角色提示词时,可以参考这个思路——感觉像在写产品需求文档,而不是在给模型念紧箍咒。与其写“你是一个经验丰富的资深数据科学家”,不如写“你负责对输入的数据表执行缺失值统计,输出包含缺失率大于30%的字段清单”。前者是在给模型找“感觉”,后者是在给模型讲“任务”。经验上,功能描述越具体、越接近可执行动作,Agent的行为就越稳定。
当然,“资深数据科学家”这种身份前缀并不是完全没用。在某些需要风格化输出、需要判断力的场景下,身份设定确实能影响语气和逻辑严密程度。我的做法是身份设定保留,但永远放在功能描述之后,作为辅助风格引导,而不是作为唯一的指令来源。
3.3 上下文隔离策略:防止Agent之间互相“污染”
多Agent的另一个大问题是上下文污染。最典型的情况是:Agent A在任务过程中发现了一个新线索,把它写进了公共状态区;Agent B读到这个线索后开始发散,跑去看了一堆相关但无关的资料,然后给下游输出了一堆噪音。一个Agent"越界发挥",往往始于它对共享上下文的过度解读。
我的解决方案是设置“信息分级”:共享区只放事实型信息,比如关键实体、结构化字段、任务进度状态;不放推理型信息,比如“我怀疑xxx”“这里可以延伸分析”。各Agent自己的推理链只保留在私有上下文里,绝不写入公共区。判断一条信息是否应该共享,就问一个问题:它是否能让另一个Agent直接做出更准确的决策?如果不能,那就没必要共享。
4. 通信机制:别让Agent自由对话,要让他们交换“数据包”
4.1 自由对话是最大的坑
有一类多Agent系统,各Agent可以像人类群聊一样互相发消息、自由讨论。演示视频看着很惊艳,但生产环境里我用过几次之后就彻底放弃了。原因是:大模型Agent的自由对话,本质上是多个概率模型在互相生成对方想看到的文本,很容易出现“回声室效应”——互相肯定、互相补充,聊得特别热闹,但离原始任务越来越远,token消耗还是普通单Agent的数倍。
我坚持的一个原则是:Agent之间不要自由对话,要交换“数据包”。每一个Agent的输出必须是结构化的、符合Schema的、经过必要校验的数据。所有的“讨论”,如果确实需要,也应该在编排器的控制下进行,而不是让Agent自行启动讨论线程。这会让系统看起来“不太聪明”,但会换来可控和稳定,这才是生产环境真正需要的东西。
4.2 消息结构的标准化是通信的基石
在项目一开始就要定义一套统一的消息协议。我常用的消息结构大致包含这几类字段:消息ID、发送者、接收者、消息类型(任务分配、结果提交、状态更新、错误上报)、关联任务ID、时间戳、业务负载。业务负载可以是一个JSON对象,Schema由阶段产出物定义。
这样的结构化消息有几个明显好处:第一是可以做路由和过滤,编排器不需要理解自然语言就能把结果分发到对应下游;第二是可以做追踪和审计,任意一条消息都能查到来龙去脉,出问题时能直接定位;第三是可以做版本管理,Schema升级时能识别出哪些Agent还没有适配新协议。这里面最容易被忽视的是“关联任务ID”,没有它,所有消息一旦异步化就会变成一锅粥。
4.3 传递内容与传递引用的选择
信息共享有一个高频设计选择题:直接把内容传给下游,还是传一个可访问的引用。早期我的习惯是直接把上文输出塞给下文,图省事。但后来发现,在多轮迭代场景里,同一个数据会在多个Agent上下文里反复复制,最后上下文窗口被塞爆。
现在我的做法是分场景:如果数据是下游Agent做决策的必需输入、且只被一个下游使用,就直接在消息里传递;如果数据要支撑多个下游、或者需要在多轮任务中反复更新,就放进一个共享的存储区,消息里只携带引用地址和摘要。这样既保证了通信的即时性,也避免了重复数据在各Agent的上下文里膨胀。
5. 决策机制:谁来拍板,谁给资源
5.1 编排器还是议会
多Agent系统的决策模式可以分为两大类:强中心化和弱中心化。强中心化由一个编排器统一调度,它负责任务分解、分配、回收、裁决;各个Agent只干活,不对全局负责。弱中心化则是多个Agent通过协商做决策,常见形式是市场机制或者投票机制。
强中心化适合目标明确、流程稳定的任务。它的优点是可控、可预测、调优方便;缺点是编排器本身可能成为瓶颈,而且编排器的逻辑复杂度会随系统规模快速上升。弱中心化适合探索性强、不存在绝对正确答案的任务,比如内容创意生成、多方模拟博弈;它的优点是可扩展性强,但也有明显缺点——不可预测、消耗大、难对齐。
我的建议是:默认先选强中心化,只有当某个核心流程反复成为瓶颈、且拆分逻辑已经被验证稳定之后,才在局部引入协商式决策。千万别一上来就搞“Agent议会”,那是在为自己的调试能力上强度。
5.2 冲突消解与置信度评估
多Agent系统里,冲突几乎是必然的。两个Agent对同一个问题给出了不同结论,谁说了算?常见的消解方式有:优先级仲裁,编排器里预置了规则,比如数据类结论优先于推测类结论;置信度加权,Agent在输出时为结论附带置信度分数,系统取加权平均或最高分;交叉验证,引入第三个评审Agent对冲突两边进行再评估。
在落地时,我经常会采取一个更保守的“默认信任主流程”策略,主干链路Agent的结论不被轻易推翻,旁路Agent的异议必须附带充足的证据才能触发重算。否则所有Agent都有“质疑权”的话,系统会陷入无限返工。
5.3 失败重试与降级策略
多Agent系统里,“某个Agent失败”是常态而非异常。设计时就要想清楚三层兜底:第一层是单Agent内部重试,比如调用模型接口超时,换个温度参数或者换个模型版本再试一次;第二层是编排器层面的降级,当某个非关键Agent持续失败时,跳过它的输出并用默认值或简单规则代替;第三层是人机协同,当关键Agent连续失败达到阈值,停止自动流程,把中间产物打包,转交给人类决策。
这里有一个很重要的认知:不是所有失败都值得重试。如果Agent的失败是因为模型输出格式不符合Schema,重试有帮助;但如果是因为推理本身跑偏、输出质量不达标,重试只会重复同样的错误。后者触发的是降级策略,而不是无脑重跑。把这两类失败分开处理,是系统稳定性的关键分水岭。
具体到一个Agent内部,要多加一步“自我保护”。如果输入数据的质量不合格,应该直接抛出格式化的错误码,而不是硬着头皮往下推理,给下游一个貌似合理、实则全错的结果。一个质量低的垃圾答案,流经多Agent链条后会被放大成致命问题,而这个阶段再追责已经晚了。
6. 实操:从零开始搭一个“选题策划多Agent”系统
6.1 目标拆解与Agent划分
为了更直观地讲清楚,我用一个实际做过的项目来演示整体流程,针对的是“内容选题策划”场景。
任务目标是从用户给定的一个泛主题出发,产出三个可落地的具体选题方案,每个方案包含标题、核心论点、目标读者、内容结构和参考素材。
我把这个任务拆成了四个Agent:
- 需求理解Agent:对用户输入做解析,补全目标读者的画像、内容频道的定位、可用素材的边界;
- 调研分析Agent:根据需求理解结果,检索提供相关资料并提炼出三个细分方向;
- 选题策划Agent:基于细分方向,生成完整的三个选题方案;
- 质量评审Agent:对前者的方案做合规性、新颖性、可行性的评估,并生成修改建议。
编排器统一收口,将需求理解Agent的输出作为上下文分发给“调研分析Agent”,再将所有结果聚合给“选题策划Agent”,最后单独调用质量评审Agent进行一轮校验。这是一个典型的星型+流水线混合架构,核心节点是编排器。
6.2 用代码框架实现编排核心
下面把这个系统的骨架逻辑写出来,供参考。这里不绑定某一具体的Agent开发框架,用伪代码的方式把编排过程表达清楚:
from typing import List, Dict from dataclasses import dataclass, field import json # 定义统一的Agent基类 class BaseAgent: def run(self, context: Dict) -> Dict: raise NotImplementedError # 定义Agent的运行接口规范 class AgentNode(BaseAgent): def __init__(self, name, delegate_func, input_keys, output_keys): self.name = name self.delegate_func = delegate_func self.input_keys = input_keys self.output_keys = output_keys def run(self, context: Dict) -> Dict: # 只取自己需要的输入字段 local_input = {k: context.get(k) for k in self.input_keys} result = self.delegate_func(local_input) # 校验必须返回的字段,避免下游拿到残缺数据 for key in self.output_keys: if key not in result: raise ValueError(f"Agent {self.name} 缺少输出字段: {key}") # 将结果写回共享上下文(只写自己负责的字段) context.update(result) return context class Orchestrator: def __init__(self): self.agents: List[AgentNode] = [] self.max_retries = 2 def add(self, agent: AgentNode): self.agents.append(agent) def run(self, initial_context: Dict) -> Dict: context = initial_context.copy() for agent in self.agents: for attempt in range(self.max_retries + 1): try: context = agent.run(context) break except Exception as e: print(f"[{agent.name}] 第{attempt + 1}次执行失败: {e}") if attempt == self.max_retries: # 走降级逻辑:这里可以替换成一个简单的兜底函数 context[agent.output_keys[0]] = None return context # 各个Agent的实际调用函数(示例) def parse_requirement(inputs: Dict) -> Dict: # 这里实际调用LLM,做输入解析 return { "parsed_intent": "用户想做一个关于'远程办公效率'的选题,目标读者是中层管理者,内容渠道为公司博客" } def research_directions(inputs: Dict) -> Dict: # 调用搜索工具 + LLM总结 return { "candidates": ["远程办公的会议疲劳", "异步协作的落地方法", "混合办公的绩效评估"] } def create_proposals(inputs: Dict) -> Dict: # 调用LLM生成三项完整方案 return { "proposals": [ {"title": "会议疲劳怎么破", "points": [], "audience": "中层管理者"}, # ... ] } def review_proposals(inputs: Dict) -> Dict: # 让评审Agent挑毛病 return { "review_comments": ["第三项方案可行性不足,需要补充数据支撑"] } if __name__ == "__main__": orch = Orchestrator() orch.add(AgentNode("需求理解", parse_requirement, input_keys=["user_input"], output_keys=["parsed_intent"])) orch.add(AgentNode("调研分析", research_directions, input_keys=["parsed_intent"], output_keys=["candidates"])) orch.add(AgentNode("选题策划", create_proposals, input_keys=["parsed_intent", "candidates"], output_keys=["proposals"])) orch.add(AgentNode("质量评审", review_proposals, input_keys=["proposals"], output_keys=["review_comments"])) context = {"user_input": "远程办公效率"} result = orch.run(context) print(json.dumps(result, ensure_ascii=False, indent=2))这个编排器的核心逻辑是:每个Agent只从共享上下文中读取它声明需要的字段,只向上下文写入它声明负责的字段。字段名由字符串映射,强约束由异常捕获和降级策略提供。
6.3 实测数据与效果观察
拿“远程办公效率”这个主题实际跑了一次,记录如下。
按时间顺序观察到的产出:需求理解Agent输出了目标读者是中层管理者;调研分析Agent检索了六份资料,提炼出会议疲劳、异步协作、绩效评估三个方向;选题策划Agent生成了三个方案,其中前两个方向写得很扎实,第三个方向也开始展开描述,不过案例明显不足;质量评审Agent给出的核心建议是“第三方案泛化严重,引用数据陈旧,建议重写或下沉为子话题”。
整体耗时比单Agent系统多了大概两倍的接口调用时间,但产出质量确实高于单Agent直接生成。最重要的差异在于:单Agent写到第三个选题时会明显表现出“凑数感”,而多Agent流水线里质量评审Agent直接把这个凑数方案拦下并给出了修改建议——这个反馈闭环,是单Agent体系很难做到的。
6.4 调整技巧:如何用上一轮结果迭代下一轮
跑完一轮之后,是否要立刻把评审意见合并回主流程再跑一轮?我的经验是分情况。如果评审意见是修正性的,比如指出的具体错误,就直接在共享上下文里追加“修改指令”,再触发一轮选题策划Agent的重跑;如果评审意见是战略性的,比如认为整个方向选错了,那应该回到调研分析阶段去重构,只重跑下游Agent没有意义。
为了支持这种迭代,编排器要记录每个Agent每一轮执行的输入输出明细。很多框架自带追踪能力,但如果你是自己写编排逻辑,至少要给每个Agent的输出打上“轮次号”和“检查点ID”。这个设计上的提前量,会在你调试多Agent系统时节约大量时间。
7. 避坑指南:多Agent项目里我踩过的那些坑
7.1 问题一:模型输出了“看似合理”的错误格式
现象是Agent返回了一段格式错误的JSON或者缺失关键字段,框架在做下游解析时直接崩溃。这类问题最常被归咎于“模型能力不行”,但根因往往是提示词里对输出格式的描述不够强硬,或者允许多种格式导致模型二选一。
排查方向和解决方案是:在提示词里只给一种明确的输出格式范例,并给出反例说明什么是不被允许的;在代码层对Agent输出做严格校验,不合格就自动重试一次,并附上具体的报错信息指导模型修正;考虑使用约束解码方案,让模型输出模式更确定。顺序上,先查提示词,再查数据,最后才换模型。
7.2 问题二:上下文太长,钱烧得飞快
多Agent的token消耗通常不是某个Agent的问题,而是多个Agent的输入里包含了大量重复的历史信息。最典型的情况是每个Agent都把原始任务说明重新读了一遍,把上游所有输出全文复制了一遍。
排查方向和解决方案是:Agent只读取自己input_keys中声明的字段,不给它“全量上下文”的入口;中间产物必须做摘要化处理,长篇原始文本交给工具去处理,Agent接收的是摘要;历史消息进入上下文前做压缩,旧轮次的消息只保留关键结论。
这里要特别强调一个容易忽视的点:不要只按轮次清理上下文,要按依赖关系清理。由于Agent B只需要依赖Agent A的某个字段,所以当A的其他输出已经落库后,那些内容就应该从B的执行上下文里删掉。
7.3 问题三:Agent开始“自行其是”
现象是某一个Agent为了“更好地完成任务”,自己给自己追加了额外的行动。比如调研分析Agent,本来只需要检索资料、提供方向,但它自己跑到下游去把选题方案也写了。表象是“它很主动”,实际结果是下游Agent拿到一份半成品,整个流程的输出风格偏离预期。
排查方向和解决方案是:把提示词里“你还可以……”这类开放句式全部删掉;加一道“输出前检查”的隐式指令,让Agent在输出前自问是否超出了职责范围;当问题反复出现时,用一个简短的“后置规则Agent”或规则过滤器检查输出是否偏离Schema定义的范围。
在我实测经验里,确认最终能稳定跑通的多Agent系统,大部分时间不是靠一个聪明的提示词,而是靠一套严密的约束和清晰的职责边界。不要让Agent“自由发挥”,要让他们在轨道上运行。
7.4 问题四:结论处处冲突,无法收口
随着Agent数量增加,结论冲突的概率也增加。处理这类问题要有一个明确的原则:给每个Agent的输出加上置信度字段。置信度我一般要求Agent结合事实依据充分度来给,而不是拍脑袋。编排器综合决策时,优先采纳置信度最高且附带原始依据的那条结论。
另一个有效的方式是“责任到人”式评审安排:让与某方向相关的Agent互相评审,而不是反方向交叉评审。比如财务分析Agent的结论让行业趋势Agent提意见就不太合理,要让合规Agent来审。别把“多角色讨论”变成“多角色互喷”,那样的讨论没有收敛价值,只有失控风险。
7.5 问题五:一切正常,但结果比单Agent还差
这是最难排查的问题——系统没有报错,Agent都在认真工作,但最终效果就是不如单Agent。遇到这种情况,先别怀疑模型能力,而是检查流程设计是否在反复“削弱”原始信息。每经过一个Agent,原始信息都被摘要化处理一次,摘要再被加工成新摘要,信息熵不可避免地在丢失。
排查方向和解决方案:对比一下第一个Agent的输入和最后一个Agent的输入,看看原始关键信息是否还保留在里面;保留一份未摘要的原始资料库,让关键决策Agent可以直接从原始资料里读取自己想看的段落;允许“信息回流”,下游Agent如果觉得上游摘要缺信息,可以主动发起需求,要求重读原始资料的某个部分,而不是默默基于不完整摘要继续往下做。
8. 我常用来衡量多Agent系统好坏的几个指标
有些读者可能会问,多Agent系统怎么量化评估?单纯“看起来效果好”是不够的。我会在每一轮迭代后固定看三个指标。
第一个指标是单轮任务成功率,指的是从用户输入到最终输出,整个编排流程是否在预期轮数内完成、且产出物满足Schema校验。这个指标反映的是流程的通畅度,出现下滑时优先关注Agent之间的字段匹配问题和超时重试逻辑。
第二个指标是下游修正率,统计下游Agent明确要求上游返工或者自行纠正上游结论的次数。它和单轮成功率是一体两面。一个运行良好的多Agent系统,理论上修正率应该很低,因为上游已经把该做的事做对了;如果修正率偏高,说明上一个Agent的产出质量没有达到预期,或者边界切分本身就切错了。
第三个指标是单位有效输出成本。计算方式是总token消耗除以最终被采纳的有效内容长度。嘴上说多Agent质量高没用,价格也得能接受。很多项目做到后期会为了那5%的质量提升,付出三倍以上的资源消耗,这时候就要对架构进行有选择地做减法。
设计多Agent系统,我的体会是:它像搭积木,但每一块积木都要明确知道自己在整个结构里的受力点在哪里。多一个Agent不是多一份智慧,而是多一份不确定性;减一个Agent也不是损失,而是少一个需要守护的边界。与其被“多”这个字诱惑,不如先把通信、边界、决策、容错这些基本功打扎实。
最后分享一个收藏了很久的判断方法:设计阶段先把Agent之间的交互画成一张表,纵轴是发送者,横轴是接收者,一格写一条消息,这一格需要传输什么数据、数据格式是什么、接收方用完如何处理,都要写得清清楚楚。格子越多的方案越需要谨慎;能删掉的通信连接,尽量删掉。等你把这张表缩到不能再缩,系统的复杂度也就降到了可控范围。