news 2026/10/21 10:15:29

从单Agent到多智能体协作:搭建稳定可靠的AI代理团队

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从单Agent到多智能体协作:搭建稳定可靠的AI代理团队

前阵子接了个自动化调研的活儿,需求看起来很简单:让AI定期帮我产出竞品动态周报。我一开始也是老思路,单Agent加一段超长Prompt,把搜索、分析、总结全塞进去。跑了两周就发现不对劲,上下文越堆越长,输出质量忽上忽下,有时候连数据来源都开始编。后来我把这套东西彻底推倒,重新按"agency-agents"的思路组织——说白了,就是不再养一个全能选手,而是养一支各司其职的AI小团队,有做研究的、有做分析的、有做质检的,再由一个调度角色统一分派任务。这篇文章就聊聊我完整搭建这套多智能体协作体系的过程,从架构设计到核心代码,再到实测数据和一个又一个的坑。适合那些已经跑通过单Agent、正打算往多Agent编排方向走的开发者,也适合想搞清楚Agent协作到底怎么落地的技术决策者。

1. 为什么我会自建"agency-agents"而不是直接堆Prompt

先说结论:单Agent不是不能用,但凡是需要"多步骤、多角色、可复现"的任务,硬塞给一个Agent就是在给自己埋雷。

1.1 单Agent模式的三个致命瓶颈

我那段时间天天跟一个超长Prompt较劲,网上的、文档里的、别人分享的各种结构都试过。表面看是提示词工程问题,实际是架构问题。具体来说有三个瓶颈特别明显:

第一是上下文膨胀。任务链条一长,前面搜到的资料、中间的分析草稿、后面的总结措辞,全堆在同一个上下文窗口里。Token占用指数级上涨不说,模型越往后越容易"忘记"开头的要求,经常出现前面说A方案、后面又按B方案总结的情况。

第二是职责混乱。既让Agent做事实检索,又让它做观点提炼,还让它做格式排版——这等于让一个人同时当记者、编辑和美术设计。每切换一次角色,模型就要做一次"思维转换",转换不彻底就会串味。我实测下来,串味最典型的症状是:分析部分里忽然冒出一段"以下是搜索结果",或者总结部分开始重复搜索关键词。

第三是返工成本。单Agent模式下如果中间某一步质量不行,你没法精准定位是哪一环出了问题,只能整体重跑。而整体重跑又意味着从头搜资料、从头生成,费时费力费钱,最后还不一定比上次好。

1.2 一个后厨团队的类比

想明白这个事之后我换了个思路。你看一个餐厅后厨,不可能让一个厨师既配菜又掌勺又洗碗——配菜师只负责切配,炒锅师傅只负责调味,洗碗工只负责清洁,各干各的,最后通过传菜口协作。放到AI这边一模一样:检索Agent只负责搜集和整理原始素材,分析Agent只负责在素材基础上提炼观点,编审Agent只负责核查前后矛盾和事实错误。每个Agent的Prompt都短而专,上下文干净,角色不会串,出了问题也能精准定位是哪一环。

这就是"agency-agents"的核心思想:把一群各有所长的Agent组织成一个"代理机构",有管理岗、有执行岗、有质检岗,通过明确的通信协议协作。

1.3 这种模式适合什么场景

不是所有任务都必须上多Agent。我梳理了一下,适合"agency-agents"的任务有几个特征:子任务之间有明确的边界、不同子任务需要不同的技能或知识侧重、任务结果需要多次检查迭代。比如周期性的行业报告、数据处理流水线、需要调用多个外部工具的复杂业务流程。

反之,如果是单轮问答、简单的文本改写、一次性小任务,直接单Agent反而更快更省。多Agent不是银弹,它解决的是"复杂任务的可控性",代价是更高的系统复杂度和推理成本。我后面会详细算这笔账。

2. 体系架构拆解:角色注册、消息总线与任务编排是怎么串起来的

想清楚了"要组建AI团队",下一步就是搭骨架。我把这套体系拆成了五个核心组件,每个组件只干一件事,彼此之间通过标准化的消息格式通信。

组件职责对照表:

组件职责类比
RoleRegistryAgent的注册、查找和健康检查公司花名册
MessageBus任务消息的分发与回传公司内部邮箱系统
TaskScheduler任务分解、依赖管理和调度执行项目经理
SharedContext跨Agent共享中间产物与状态共享文档库
MemoryStore长期记忆与历史结果沉淀档案室

这五个组件的分工逻辑我展开说一下。RoleRegistry是基础,所有Agent启动时都要在这里登记,说明自己是谁、擅长什么、接收什么格式的消息。TaskScheduler是大脑,接到用户请求后先做任务分解,拆成若干子任务,再根据依赖关系决定哪些能并行、哪些必须串行。MessageBus不承载业务逻辑,只负责把消息从A传到B,这样Agent之间不会互相直接调用,降低耦合。SharedContext用来传递中间产物,比如检索Agent找了30条资料,分析Agent需要读这30条资料,就通过SharedContext拿,不用重新搜索一遍。MemoryStore则是超长期记忆,比如上周的周报结论、上个月的错误教训,下次任务可以直接引用。

2.1 数据流长什么样

我按一次完整的任务走一遍数据流,这样更容易理解各个组件是怎么配合的:

用户请求先进入入口Agent。入口Agent不干活,只做意图识别,判断任务类型后交给TaskScheduler。TaskScheduler把任务拆成"调研-分析-编审-成稿"四个子任务,按依赖关系排好顺序。调研类子任务交给专门负责检索的Agent,该Agent从MessageBus拿到任务,干完活把结果写进SharedContext,再通过MessageBus回传一个"完成"信号。TaskScheduler收到信号后,把分析子任务投递给分析Agent。依此类推,最后一个Agent产出最终结果,回传给用户。

2.2 为什么消息不能直接点对点传

我刚开始偷懒,让TaskScheduler直接拿到每个Agent的对象引用,然后一个个调用。跑通是能跑通,但很快发现三个问题:第一,新增一个Agent要改调度器代码,违背开闭原则;第二,Agent之间如果产生间接依赖,代码里查都查不出来;第三,想加一个"任务日志审计"的功能,发现无从下手,因为调用散落在各处。

改成MessageBus之后这些问题迎刃而解。所有通信走统一通道,调度器只需要知道"发什么消息给谁",不需要知道"谁在什么时候具体怎么处理"。日志审计也简单,在MessageBus挂一个全局监听器,所有消息记录一目了然。这就是为什么我宁可多写几百行基础设施代码,也不省这个事。

3. 核心代码路径:从Agent基类到Director调度器的落地实现

这套体系我用Python实现,核心依赖只有ASGI和Pydantic,没上重量级框架——后面会解释为什么。先看最底层的BaseAgent。

3.1 Agent基类设计

from abc import ABC, abstractmethod from dataclasses import dataclass, field from typing import Optional @dataclass class AgentRequest: task_id: str role: str payload: dict context_id: Optional[str] = None max_retries: int = 2 @dataclass class AgentResult: task_id: str role: str success: bool output: dict error: Optional[str] = None iterations: int = 1 cost: float = 0.0 class BaseAgent(ABC): """所有Agent的基类:声明角色、定义能力边界、实现统一入口""" role: str = "general" description: str = "" def __init__(self, model_name: str = "qwen-plus", temperature: float = 0.3): self.model_name = model_name self.temperature = temperature self.system_prompt = self.build_system_prompt() @abstractmethod def build_system_prompt(self) -> str: """每个Agent必须定义自己的系统提示词,这是角色边界的核心""" pass @abstractmethod def process(self, context: dict) -> dict: """核心业务逻辑:接收上下文,产出结果""" pass def run(self, request: AgentRequest, context_store) -> AgentResult: """统一执行入口,处理重试、超时、成本统计""" for attempt in range(request.max_retries + 1): try: context = context_store.get(request.context_id) if request.context_id else {} context.update(request.payload) output = self.process(context) result = AgentResult( task_id=request.task_id, role=self.role, success=True, output=output, iterations=attempt + 1 ) return result except Exception as e: if attempt >= request.max_retries: return AgentResult( task_id=request.task_id, role=self.role, success=False, error=str(e), iterations=attempt + 1 )

这个基类有几个设计点值得说。role字段是Agent的身份标识,调度器靠它路由任务;build_system_prompt是强制要求每个Agent自己定义角色Prompt,不允许在实例化的时候临时传一个,这样能保证角色边界在设计期就被"焊死";run方法内部统一处理了重试和成本统计,业务子类只需要实现process,不用关心这些横切逻辑。

3.2 Director调度器:任务分解与路由

Director是整个体系里最关键的组件,它的职责是:把用户请求翻译成子任务序列,再按Agent能力路由出去。

class DirectorAgent: def __init__(self, registry: dict[str, BaseAgent], model): self.registry = registry self.model = model def decompose(self, user_request: str) -> list[dict]: """将用户请求拆解为子任务序列,返回[{"role": "...", "prompt": "..."}]""" # 这里走一次LLM调用,让模型基于当前注册的Agent列表做任务规划 agent_roles = ", ".join(self.registry.keys()) planning_prompt = f""" 你是任务规划器。当前可用的Agent角色有:{agent_roles}。 请将这个用户请求拆解为不超过5个子任务,每个子任务指定一个最适合的角色。 用户请求:{user_request} 只输出JSON数组,格式: [{{"role": "researcher", "prompt": "具体指令"}}, ...] """ raw = self.model.chat(planning_prompt) return self._parse_json_safe(raw) def dispatch(self, request: AgentRequest, subtasks: list[dict], context_store): """按依赖顺序执行子任务,允许并行""" # 简化版:按顺序执行,实际可以按依赖拓扑并行 for st in subtasks: agent = self.registry[st["role"]] sub_request = AgentRequest( task_id=f"{request.task_id}:{st['role']}", role=st["role"], payload={"task": st["prompt"], "context_id": request.context_id}, context_id=request.context_id ) result = agent.run(sub_request, context_store) # 把每个子任务的输出写回共享上下文,供后续任务读取 context_store.append(request.context_id, st["role"], result) final_result = context_store.get(request.context_id) return final_result

Director的核心是一个规划-执行循环。每次拿到用户请求,先让LLM基于"当前注册了哪些角色"来拆分任务,这一步很关键——任务分解不是硬编码规则,而是动态适配的。你新增了一个"数据可视化Agent",下次用户请求里涉及图表需求时,规划器就会自动把"生成图表"拆成一个子任务,无需改代码。

3.3 注册机制与消息规范化

Agent注册我用了一个简单的装饰器实现,这样新增能力只需要写一个类加一行注册语句,Director那边不用动:

AGENT_REGISTRY = {} def register_agent(cls): AGENT_REGISTRY[cls.role] = cls() return cls @register_agent class ResearcherAgent(BaseAgent): role = "researcher" def build_system_prompt(self): return "你是一名研究员,负责检索并整理事实性资料,只输出原文摘录和来源链接,不做主观判断。" def process(self, context): # 调搜索API,把结果去重、提取摘要 query = context["task"] results = search_web(query, top_k=10) return {"materials": dedupe(results)}

每个Agent通过@register_agent把自己登记进AGENT_REGISTRY,Director启动时加载这个字典。这套做法看起来不起眼,但它保证了两件事:Agent列表是运行时可见的,规划器能实时感知可用能力;新增Agent不影响任何既有代码路径。

消息格式我全部用Pydantic的dataclass约束,字段不可随意增删。这么做看起来啰嗦,实际好处是:每个Agent收到的都是结构一致的任务包,不会出现"A传了b字段、B期待c字段"的对接错位;后续加消息追踪、审计、重放,全都基于这套格式做,不用返工。

4. 一次真实任务的完整旅程:从任务拆解到报告产出

架构讲得再多,不如看一次实际执行过程。我带了一个具体的业务场景走完整流程。

4.1 场景设定:竞品动态周报

某团队需要每周产出一份竞品动态周报,内容包括:主要竞品的产品发布动态、价格或策略调整、行业趋势信号、以及对自家产品的潜在影响评估。之前这个活靠人工,每人每周要花半天。我的目标是用"agency-agents"把这套流程跑起来。

4.2 四个Agent的职责定义

我注册了四个Agent,角色分别是:researcher(研究员)、analyst(分析师)、reviewer(编审)、writer(撰稿人)。

  • researcher:对接搜索API,搜集一周内目标竞品的公开动态,输出事实清单,附带原始链接和时间戳。
  • analyst:接收研究员的事实清单,结合产品背景,提炼出"哪些动态值得关注"以及"可能的影响",输出分析要点。
  • reviewer:核查分析师输出的合理性,检查是否有夸大、前后矛盾、事实缺乏支撑,输出核查意见。
  • writer:把分析要点和核查意见整合成一篇结构完整的周报,输出Markdown正文。

这四个角色的顺序是严格依赖的:researcher干完,analyst才能干;analyst干完,reviewer和writer才有输入。我把前三个当作串行链,writer放在最后。

4.3 实际执行数据

跑了三轮完整测试,其中一轮是模拟突发竞品发布会当天的数据采集。执行情况如下表:

环节平均耗时平均Token消耗质量自评
任务规划(Director)3.5s约800稳定
researcher22s约6000事实清单较全,偶有重复项
analyst18s约4500洞察质量高,偶尔出现跨领域脑补
reviewer12s约3500能抓住大部分问题,有少量误报
writer15s约5000成稿稳定,格式统一

整个链路跑下来约70秒。对比人工方式需要半天,效率提升接近两个数量级。Token消耗约2万左右,按通用模型的成本折算,单次生成成本在几毛钱级别。这个成本换一个团队半天的工时,性价比相当可观。

4.4 为什么坚持这个顺序而不是让一个Agent全干

我在跑测试的过程中试过"合并角色"的偷懒做法:让analyst同时兼任reviewer,也就是自己分析、自己检查。结果发现一个问题,分析Agent给自己挑错的时候,挑错意愿明显下降,很多明显的前后矛盾它自己看不出来。后来我专门查了一下,这跟模型的自我纠错能力限制有关,模型在自己生成的内容上做批判性审视,效果远不如让它去审视另一个角色的输出。

这也是"agency-agents"为什么强调角色分离的一个具体原因——不只是为了让Prompt短,更是为了利用"不同视角"带来的制衡效应。reviewer Agent的Prompt里有一句关键设定:"你负责代表用户质疑分析师的观点,不要轻易认同。"这句话让它在大概率上保持挑剔姿态。

4.5 最终产出效果

成稿一篇周报大约800到1200字,结构固定为:概览、竞品动态要点、影响分析、下周关注。人工审阅后,初稿可用率在八成左右,剩余两成主要是有些动态抓取不全或者影响判断过于保守。相比纯单Agent生成,这套体系的优势在于:事实核查环节能把"编造数据"的概率压到很低,因为reviewer会要求每个结论给出支撑来源;成稿格式始终稳定,writer不像单Agent那样隔几篇就突然改变排版风格。

5. 跑通之后的排障实录:上下文污染、任务死锁与格式翻车

任何系统只要跑起来,问题都会冒出来。我集中说三个最典型的,附带完整排查链路。

5.1 上下文污染:Agent串味是怎么回事

现象:analyst的输出里出现researcher风格的句子,比如直接输出"以下是搜索到的原始URL列表",但它根本没搜过网。

排查过程:我先看MessageBus日志,确认消息路由没有问题,analyst确实只收到了researcher的产出摘要。然后又检查了SharedContext,发现我一开始设计的是所有子任务共享同一个上下文对象,researcher把30条原始资料全塞了进去,analyst拿到的上下文里既有"整理好的事实清单",也有"未清洗的搜索摘要"。我给analyst的Prompt说"基于研究资料进行分析",模型一看上下文里有一大堆URL原文,就倾向于直接引用它们。根因找到了:Sharing Context时没做内容隔离,中间产物的形态不合格。

修复:每次子任务之间传递时,不直接传原始上下文对象,而是通过一个extract_for_role(context, role)函数,该函数按照目标角色的需求把上下文裁剪成"该角色需要的最小集合"。比如传给analyst的只保留researcher整理后的"fact_list",丢弃所有原始搜索摘要和中间草稿。修完后串味现象彻底消失。

这个教训后来让我养成了一个习惯:SharedContext里必须显式声明"谁写的、给谁读的",并加一条隔离规则——默认情况下,其他Agent只能读取指定字段,看不到完整内容。

5.2 任务死循环:两个Agent互相甩锅

现象:有一轮测试迟迟不返回结果,查日志发现Director一直在循环调度,任务卡了快10分钟。

排查:MessageBus日志显示,reviewer在核查时发现了analyst的一个疑点,于是它把"需要补充分析"的信令写回了SharedContext。我那个版本的调度器逻辑是"只要上下文里有未处理信令就继续投递",于是analyst又收到"补充分析"的任务,补完之后reviewer又发现新的疑点……两人来回互相派活,形成死循环。

修复:我在Director里加了两道保险。第一道是每轮调度检查该子任务的最大执行次数上限,超了就强行终止并标记"需人工介入";第二道是引入超时熔断机制,整个任务的执行时间超过预设阈值(我设的5分钟)就自动终止,避免无声无息地耗着。

这个坑本质上是我在设计任务依赖时没有考虑"回环"的情况。任务有向无环图(DAG)不是口头说说,要真正在做调度的时候校验依赖关系,哪怕用最简单的方式——每轮执行前检查"这个子任务是否已经被执行过N次"——也能挡住绝大部分死循环。

5.3 模型输出格式翻车:让Agent输出JSON它非要加Markdown

现象:Director的_parse_json_safe函数频繁报错,一看原始返回,模型偶尔会在JSON外面加一段解释性文字,比如"以下是JSON格式的结果:",导致解析失败。

排查:不是Prompt问题。我试过在提示词里强调"只输出JSON,不要其他内容",正确率从70%涨到85%,但尾部仍有15%的概率翻车。再试了降低temperature从0.7降到0.3,正确率继续提升到92%,但仍然有漏网。这说明单靠提示词和参数控制解决不了这个随机性问题,必须靠代码兜底。

修复:_parse_json_safe里做三层兜底。第一层尝试直接json.loads;第二层用正则提取第一个[和最后一个]之间的内容再解析;第三层如果还是失败,就把这个子任务标记为"格式异常",重新提交给同一个Agent并附带"上次你返回的内容格式不对,请重新输出严格JSON"的错误反馈。这三层下来正确率逼近100%。

这条经验让我对Agent系统有个新认识:不要把稳定性押注在模型的输出纪律上,而是要让系统能容忍模型偶尔的调皮,用解析兜底和自动重试去吸收这部分不确定性。

6. 事后复盘:这套体系到底值不值,我用下来的三点建议

跑通这套"agency-agents",我最大的感受不是"AI真厉害",而是"工程化真重要"。模型能力强只是一个前提,真正让系统稳定可依赖的,是角色边界、消息规范、重试机制和日志追踪这一整套工程手段。

6.1 什么时候不要用多Agent

如果只是单轮问答,或者任务链条很短且不需要交叉验证,直接单Agent就好,省事省钱还快。多Agent的价值只有在任务复杂到"单Agent无法可靠完成"时才体现。你可以用这个标准判断:如果任务链条超过三个步骤,且中间有需要不同技能的环节,且结果需要人工审核——这时候才值得上"agency-agents"。达不到这个复杂度,所有的编排代码都是负担。

6.2 三条实操建议

第一,先把流程写死再上调度器。我第一次做就想让Director完全动态规划任务,结果每次执行路径都不一样,排障全靠猜。后来老老实实先写好固定的执行链(比如固定的搜索、分析、核查、成稿四步骤),跑熟之后,再让Director做局部动态调整。稳定的主干加上有限的灵活性,远比全动态可靠。

第二,把每个Agent的能力边界钉死。这体现在Prompt设计上,也体现在输入输出格式上。我给每个Agent都规定死了输入类型和输出字段,researcher只能输出事实清单,analyst只能输出分析要点,谁也不能越界。边界一旦模糊,系统的不确定性就会指数上升——因为模型会自动"补全"你没说明的部分,而补全的方向你不可控。

第三,日志必须能看清谁在什么时候干了什么。我建议在MessageBus挂一个全局日志监听,记录每一次消息的发起角色、接收角色、耗时、成本、成功失败状态。这套日志平时不显眼,一旦要排查问题,它就是救命的。我排上下文污染和死循环的两次经历,全靠日志一步步反推,没有日志的话这两个问题几乎没法定位。

6.3 后续可以怎么玩

这套体系跑顺之后,我下一步打算做两件事:一是把MemoryStore用起来,让每周周报自动参考上个月的结论,实现跨任务的连续性;二是加上并行调度,目前子任务还是顺序执行,实际上researcher可以拆成多个并行搜索任务来压缩耗时。这个方向做下来,可能会让整条链路从70秒压到40秒以内,我到时候有新发现再回来分享。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 0:09:48

多代理编排实战:从单智能体到协同团队的全方位指南

如果你最近在折腾大模型应用,应该对“多代理编排”这个词不陌生。我去年花了不少时间打磨一个内部工具,项目代号就叫agency-agents,目的很简单:把单一智能体干不好的复杂任务,拆给一组各司其职的代理去协作完成。实测下…

作者头像 李华
网站建设 2026/10/17 21:13:54

企业智能体项目成本真相:开发只占三成,真正的投入在哪?

企业智能体项目,最贵的从来不是开发。这句话我在好几个项目复盘会上说过,每次都有人愣一下,然后点头。上周跟一个做制造业数字化转型的团队聊,他们刚上线一个供应链智能体,开发周期两个月,开发人力成本算下…

作者头像 李华
网站建设 2026/10/16 8:15:47

AnyPS5串流实战:跨设备低延迟远程玩PS5全解析

1. 从“AnyPS5”这个标题说起:它到底想解决什么问题第一次看到“AnyPS5”这个标题,我脑子里蹦出来的第一个念头是:这大概率是一个围绕“跨平台远程串流”或者“多设备兼容”方向的项目。为什么这么判断?因为“Any”这个前缀在技术…

作者头像 李华
网站建设 2026/10/13 12:22:11

基于QT5与WinPcap的仿Wireshark抓包器:架构、解析与避坑

简介:这是一份仿WireShark的网络抓包程序完整源码,基于QT5和WinPcap实现,面向网络工程师、C开发者及网络安全学习者,可用于捕获、过滤与分析网络数据包,辅助网络故障排查、协议分析与应用调试。压缩包共392个文件&…

作者头像 李华
网站建设 2026/10/14 19:54:32

Python逆向新番时间表接口,搭建追番数据监控系统

新番时间表这东西,追番党都知道,每周最怕的就是"今天周几、更新了没"这种灵魂拷问。我一开始用的是土办法,手动把每部番的播出时间抄进表格里,定好闹钟去刷。坚持了三个星期就崩了——不是忘了写,是根本刷不…

作者头像 李华
网站建设 2026/10/12 4:22:46

Jump Game V:从动态规划到拓扑排序的优化之路

前阵子在一家在线刷题平台上碰到一道困难题,题号 1340,名字叫 Jump Game V。说真的,第一次读完题目,我完全没搞清楚它和普通跳跃游戏有什么联系。题目标签写着动态规划,可如果直接套经典跳跃问题的思路,会掉…

作者头像 李华