先说一个判断:标题里的“群体智能”不是噱头,而是要把多个 LLM 组成的 AI Agent 集群当成一个真正的分布式系统来设计。很多人看到“多个 LLM 协作”就开始堆 API 调用,把几个大模型轮流问一遍,那不叫群体智能,叫串行试错。真正的群体智能,是让多个 Agent 各自保持独立判断,通过明确的消息协议传递中间结果,在协调层统一调度下,形成 1+1 大于 2 的复合解题能力。
这个项目要解决的核心问题也很直白:单个大模型能力再强,也有上下文窗口的上限、知识时效的盲区、工具调用的边界,以及长链路任务中的“一错到底”。与其指望一个模型变全能,不如把任务拆开,交给一组各有专长的 Agent 并行处理,再由评审和聚合机制把结果收敛成最终答案。这篇文章就是我从零搭建这套系统时的一些设计思路、工程取舍和踩坑记录,适合正在做 Agent 应用、微服务架构、或者对多层 LLM 编排感兴趣的开发者参考。
1. 为什么是“群体智能”而不是“一个更强的模型”
1.1 单一 Agent 的能力瓶颈
先泼一盆冷水:如果任务足够简单,比如“把这段文字翻译成英文”,单 Agent 完全够用,强行上多模型协作只会引入延迟和成本。真正需要群体智能的,是那些单模型已经明显撑不住的任务,我总结下来大致有三类。
第一类是长链条任务。比如“帮用户查资料、做对比分析、生成购买建议”,中间涉及检索、筛选、推理、文案组织,任何一步出错都会传导到最终结果。单个 Agent 在长上下文里很容易迷失重点,前面回忆错了,后面全跟着错。第二类是多视角任务。比如技术方案评审、产品需求分析,天然需要“从成本角度”“从风险角度”“从用户体验角度”分开评估,这些视角塞进一个 prompt 里会互相干扰,分开给不同 Agent 反而干净。第三类是吞吐密集型任务。比如批量处理几百条工单,明显需要并行执行而不是串行排队。
这时你会发现,问题不再是一个模型有多大,而是系统能把多少模型组织得有多好。群体智能的底层逻辑,和蚁群找食物、蜂群选址很像:没有一只蚂蚁知道全局路线,但通过局部信息交换和简单规则,整个群体能涌现出接近最优的路径。落到 AI Agent 上,就是每个 Agent 只管好自己的子任务,通过消息传递交换中间结果,最后由协调层汇聚出全局答案。
1.2 《从标题拆解出的三个需求层次》
回到项目标题本身:“群体智能 AI Agent:多个 LLM 协作的分布式系统”。这里面其实藏了三层需求。
个体层:每个 Agent 内部要封装一个或多个 LLM,并且拥有自己的系统提示词、工具集和记忆策略。不同 Agent 可以选用不同模型,有的用快速低成本模型做大量并行子任务,有的用强推理模型做复杂决策。组织层:多个 Agent 之间要定义清楚角色、通信方式、任务拆解和结果仲裁机制。这一层是整个系统的核心,也是我后面花最多时间调的地方。基础设施层:既然是分布式系统,就要考虑消息传递、任务调度、状态持久化、容错重试和可观测性。很多做 AI 的人忽略这层,结果 Demo 能跑,一上并发就崩。
所以整个项目最终回答的问题是:如何把一组“各怀偏见”的模型个体,组织成一个“整体可信”的分布式系统。
1.3 在什么场景下值得引入群体智能
我的判断标准很简单:任务是否具备“可拆解性”和“可验证性”。可拆解,指主任务能明确分成若干相互独立或弱依赖的子任务,否则拆了还要频繁协调,不如不拆。可验证,指每个子任务的输出能被评审 Agent 或规则判断质量,否则质量不可控,群体协作就变成了群体幻觉。
还有一个容易被忽略的点:多 Agent 不是用来“显得高级”的,而是为了给系统增加冗余和对抗性。比如评审 Agent 用来挑出执行 Agent 的错,本质上就是用模型之间的差异降低单一模型的系统性偏差。这跟分布式系统里的冗余节点思路一样:单节点会出错,多节点投票可以把错误稀释掉。
2. 整体架构与角色设计
2.1 角色分层:协调者、执行者与评审者
我搭的第一个版本试图让所有 Agent 都能互相通信,结果两个 Agent 在一个细枝末节上反复争论,token 烧得飞快。后来才明白:群体智能的第一步不是“怎么协作”,而是“谁负责什么”。我的系统最终收敛成四类角色,各有各的边界。
| 角色 | 核心职责 | 典型调用频率 | 建议模型 |
|---|---|---|---|
| 协调者(Router) | 任务拆解、派发、超时管理、结果仲裁 | 每个主任务调用 1-2 次 | 强推理模型 |
| 执行者(Worker) | 完成具体子任务,调用工具、检索数据 | 主任务级调用最多 | 按场景选低成本或高精度模型 |
| 评审者(Critic) | 对执行结果打分、找漏洞、给修改建议 | 每子任务调用 1 次 | 与执行者异构的模型 |
| 聚合者(Aggregator) | 合并所有子结果,结合主任务上下文生成最终输出 | 每个主任务调用 1 次 | 叙事和总结能力强的模型 |
这里有个关键设计:评审者最好和执行者使用不同厂商或不同系列的模型。原因是同源模型往往有相似的训练数据和偏见,会让评审失去对抗效果。用异构模型后,评审者更容易找出执行者“自己看不出”的逻辑跳跃。
2.2 拓扑结构:星型、树形还是网状
在拓扑结构上,很多人一上来就搞网状,觉得 Agent 之间自由对话才叫智能。我的经验是:网状只适合小规模演示,生产系统一定要控制通信拓扑。当前主流方案有三种:
星型结构:所有通信都经过中心 Router,逻辑最简单,适合任务依赖不强的场景,但 Router 会成为瓶颈和单点风险。树形结构:Router 下面挂多个执行组,每组有 Group Leader,组长负责组内微协调,适合有层级关系的业务,比如“市场分析”拆到“行业报告”“竞品分析”“用户画像”三个组。混合结构:主干走星型或树形,局部允许网状,比如评审组内部可以让多个评审者互相交换意见,但对外只通过一个出口通信。
我最终选了树形为主、局部网状为辅。核心原因是可追踪性:每条消息在网络里都能向上回溯到某个分支,出了问题很容易定位是哪个分支的哪个 Agent 造成的,而不会在地铁图一样的网状链路里大海捞针。
2.3 通信方式:消息即契约
Agent 之间的通信方式直接决定系统的松耦程度。我对比过同步请求、异步消息队列和共享黑板三种方案。
同步请求好理解,Agent A 直接调用 Agent B 的接口等待结果,实现简单,但 LLM 调用是秒级慢操作,同步调用会把延迟串起来,一个环节超时整套任务就卡住。异步消息队列是目前的主力方案,Agent 把消息投递到队列,消费方处理完把结果回写,发送方不阻塞,系统弹性好很多,重试也好做。共享黑板则是把中间结果都写到共享存储,Agent 间不直接通信,而是通过读写同一块状态区协作,适合任务结构相对固定的流水线场景。
生产环境我推荐“异步消息 + 共享状态”混合:消息队列负责任务流转,共享存储负责结果汇聚。这样既保留了异步解耦的弹性,又避免了每条中间结果都走一遍消息队列带来的序列化开销。
3. 核心实操:从零搭一个可复现的多 Agent 协作底座
3.1 Agent 服务化封装
我在项目里把每个 Agent 封装成了无状态服务,输入输出都是统一 JSON 结构。无状态这个点很重要,它意味着每个 Agent 可以水平扩展,A 实例挂了,请求会自动打到 B 实例;也意味着每个 Agent 可以独立迭代,升级某个执行者模型不需要重启整个系统。
Agent 描述文件大概长这样:
{ "agent_id": "worker-sql-01", "role": "executor", "model": "lightning-v3", "system_prompt": "你是数据库查询专家,负责将自然语言问题转换为 SQL,并解释查询逻辑。你只做这一件事。", "tools": ["database_query", "schema_reader"], "max_output_tokens": 1024, "temperature": 0.1, "timeout_ms": 30000 }配置里有两个我后来才加上的字段。一个是timeout_ms,因为 LLM API 偶尔会超时,不给单 Agent 设超时,任务表里会堆积一堆永远 RUNNING 的僵尸任务。另一个是system_prompt里的职责边界措辞。我一开始写得很宽泛,比如“你是智能助手”,结果执行者经常顺手把评审者的活也干了,输出里混入大量越权内容。后来改成了“你只做这一件事”,行为立刻规范很多。
3.2 协作协议与消息结构
Agent 之间传消息,有点像两个人对接,光说“你帮我处理一下”肯定不够,得说清楚“谁让我做的、做什么、优先级多高、什么时候要”。我定义的项目内统一消息结构如下:
{ "task_id": "task-001", "parent_task_id": "task-000", "from": "router", "to": "worker-group-01", "type": "execute_subtask", "content": { "subtask_goal": "分析近30天订单的退货原因", "input_data_ref": "object-store://order-record/0021", "constraints": ["只输出结论和置信度,不要输出原始数据"] }, "priority": 1, "timeout_ms": 60000, "metadata": { "trace_id": "trace-8f31a2", "attempt": 1 } }trace_id我必须重点强调。分布式系统排查问题全靠它,从协调者发起的第一个任务开始,到派生出的一切子任务,都透传同一个 trace_id,日志里一搜,整个任务的执行链路完整浮现。没有这个字段,你面对几十个 Agent 的日志时会彻底失控。
3.3 任务拆解、并行执行与结果聚合
整个编排流程可以用一段伪代码讲清楚:
def orchestrate(main_task): # 1. 协调者拆解主任务 subtasks = planner.split(main_task) # 2. 每个子任务投递到队列,执行组并行消费 for st in subtasks: queue.dispatch(st) # 3. 收集所有子任务结果 results = [] while len(results) < len(subtasks): batch = queue.collect(timeout=30) results.extend(batch) # 注意:这里示例用轮询,工程实现建议走回调/状态机 # 4. 聚合者合并子结果,生成最终输出 final_answer = aggregator.merge(main_task, results) # 5. 评审者做质量检查,不合格则触发重做 if critic.review(final_answer)["passed"]: return final_answer else: return rework(main_task, critic.suggestions)这个流程看似简单,但有三处细节很容易翻车。第一,拆解出的子任务如果有依赖关系,必须按依赖顺序执行,不是所有子任务都能并行,我一般让协调者输出一个 DAG 结构,先用拓扑排序找出可并行批次,再分批投递。第二,聚合者的 prompt 里要有“忠于子结果”的强约束。我遇到过聚合者为了输出流畅,擅自“脑补”了子结果里不存在的数据,这个问题的解法是在聚合阶段把子结果按来源编号列出,并要求只要超出编号范围的信息都算违规。第三,评审者每次必须输出“通过”或“需修改”的明确结论,并带上具体修改建议,不能只说“内容还不够好”,否则执行者根本不知道往哪个方向改。
3.4 成本与延迟的数学账
上多 Agent 系统之前,最好先算一笔账。我第一次上线时没估算成本,一个任务拆成 8 个子任务,全部用高精度模型,一个任务光 token 就烧掉一块多。后来换了策略:拆出来的子任务里,机械性、结构性的内容走廉价快速模型,只有最终聚合和评审走强模型,总成本立刻降了一半以上。
这里提供一个简单的估算公式:
单次调用成本 = 输入 token 数 × 输入单价 + 输出 token 数 × 输出单价
假设一个主任务拆成 4 个子任务,每个子任务平均输入 2000 token、输出 800 token。强模型输入单价按 $0.15/百万 token、输出单价按 $0.6/百万 token 算,四个子任务总成本约为 4 × (2000 × 0.15 + 800 × 0.6) / 1000000 = $0.00312,看起来很低。但如果一次任务有 200 条并发,单价就变成 $0.624,再加上检索工具调用和重试,每月账单会非常可观。所以建议每个 Agent 在任务级设置 token 上限,子任务超过上限时强制输出“已截断摘要”,而不是带着未完成的结果继续跑。
延迟方面,我对“并行”的收益做过一次实测。一条主任务拆成 4 个子任务,原来串行需要约 12 秒(4 × 单任务 3 秒),改成并行后,总耗时为单任务最长耗时 + 聚合开销,实测大约 4.2 秒。这就是群体智能最直接的收益:用多个模型的分工换时间。
4. 工程化落地:分布式基础设施选型与稳定性
4.1 消息队列与任务调度
Agent 之间的异步通信需要一个任务中间件。我在初期偷懒,直接用数据库任务表加轮询,结果并发一上来,频繁锁表不说,任务重试逻辑还写得又臭又长。后来换成了成熟的消息队列中间件,思路一下子清晰了,三个好处立竿见影:解耦、削峰、故障隔离。
任务量比较小的团队其实可以继续用任务表方案,但要记住一个原则:先能跑,再优化。任务量到了每秒钟几十条,或单任务携带的上下文超过几十 KB 时,就值得引入消息队列了。队列选型看团队熟悉什么就用什么,Kafka 适合高吞吐日志型场景,但 Agent 任务更看重可靠投递和延迟,常规的消息队列中间件反而更合适。
4.2 任务状态管理与幂等控制
分布式环境下,同一个任务可能因为重试被执行两次。比如执行者调用了外部工具 API,第一次调用其实成功了,但响应超时,系统误判失败触发重试,第二次又执行一遍,造成重复扣费和重复写入。解决办法是幂等控制,具体就两步。
第一步,每个子任务在任务表里都有唯一 task_id,消费端处理前先查状态,已处理就丢弃。第二步,执行者在调用外部工具时,把 task_id 透传给工具侧做去重,或者在写外部存储时带上 task_id 做唯一键。任务表里状态流转基本是这样:
| 状态 | 含义 | 触发条件 |
|---|---|---|
| PENDING | 已拆解,待派发 | 协调者写任务表时 |
| DISPATCHED | 已投递到队列 | 消息投递成功后 |
| RUNNING | 执行者正在处理 | 消费者拉取消息时 |
| SUCCEEDED | 子任务完成 | 结果回写成功后 |
| FAILED | 不可恢复错误 | 重试次数用尽 |
| TIMEOUT | 超时未返回 | 看门狗扫描发现超时 |
状态机看起来简单,但真实场景里最容易漏掉的是 TIMEOUT 状态。必须要有一个后台扫描任务定期扫描 RUNNING 超过阈值时间的记录,强制置为 TIMEOUT,否则一个执行者实例崩掉后,它的任务会永远占据 RUNNING 状态。
4.3 可观测性三件套
多 Agent 系统比普通微服务更需要可观测性,因为模型输出是不确定的,一个任务失败了你很难判断是模型本身能力不够、prompt 写得不对、还是外部工具出错。
我的做法是三条线并行。日志线,所有 Agent 的输入输出、外部 API 响应时长、错误码都打结构化日志,必须带 task_id、agent_id、trace_id。指标线,在任务入口统计每个子任务的执行时长、成功率和 token 消耗,聚合出整体趋势。链路追踪线,就是前面说的 trace_id 透传,配合队列消息的流转记录,还原任务全链路。
再列一下我最常盯的几项指标:任务整体成功率、平均完成任务时长、单 Agent 失败率、队列积压数、token 月消耗量、外部 API 错误率和 P95 延迟。我踩过的教训之一就是没在早期盯 token 消耗,直到账单翻倍才发现是某个评审 Agent 陷入了重试循环。
4.4 超时、重试与熔断
LLM API 偶尔抽风是常态。我定的重试策略是:第一次失败立即重试,第二次失败等待 2 秒再重试,第三次失败等 4 秒,超过三次把任务扔进死信队列,由人工或降级逻辑处理。这个指数退避的目的是避免所有失败任务同时重试,引发对下游 API 的“重试风暴”。
熔断也同样重要。比如外部工具接口连续返回 5xx,如果系统不熔断,大量并发任务会同时打向这个不健康的服务,把服务彻底拖死。我的做法是:错误率连续 10 个请求超过 50% 时,熔断器打开,后续请求直接短路返回“服务暂不可用”,等冷却期过后放少量请求试探,成功率达到阈值再逐步放开。简单说,这就像高铁检票口,前面已经堵死了,新票就不要再往里硬塞。
4.5 一致性难点:记忆与上下文的隔离
多 Agent 系统里最难定制的不是通信,而是记忆。如果多个 Agent 共享同一个上下文存储,A 任务的中间结论很容易被 B 任务读到,造成上下文污染。
我的方案是按 task_id 对记忆做分区隔离。每个主任务维护独立的状态空间,子任务只能读写自己分支下的记忆区域。跨 Agent 需要共享的信息,必须由协调者显式转写,而不是让 Agent 自行去共享区翻找。这样牺牲了一点点信息共享的便利性,但换来的是结果稳定性和可解释性,值。
5. 常见问题与排查技巧实录
5.1 高频问题速查表
下面这张表是我在实际情况中整理出来的高频问题排查清单:
| 现象 | 可能原因 | 排查思路 | 处理建议 |
|---|---|---|---|
| 任务一直处于 RUNNING 状态 | 执行者实例崩溃,或消息消费后未回传结果 | 看看门狗日志,查消费端异常堆栈 | 加任务表超时扫描,TIMEOUT 后自动重派发 |
| 多 Agent 输出内容互相矛盾 | 多个 Agent 使用相同系统提示词,输出同质化 | 对比各 Agent 的 prompt 和模型类型 | 异构模型拆分,评审 Agent 换不同厂商模型 |
| token 成本翻倍上升 | 重试风暴、上下文膨胀、Agent 陷入协商死循环 | 按 task_id 查 token 日志,定位高消耗环节 | 设置单任务 token 上限,协商过程限最大轮次 |
| 消息丢失导致子任务未执行 | 消费者未确认,或投递时没有持久化 | 查队列积压和消费确认日志 | 显式开启消息持久化,消费成功后必须返回 ACK |
| 聚合结果里出现子结果没有的数据 | 聚合 Agent 脑补,违反忠实性约束 | 随机抽查聚合输入输出,复核信息源头 | 在聚合 prompt 中强制要求只能引用输入编号范围内的信息 |
| 任务总超时,聚合阶段才报错 | 拆解阶段没有识别依赖,并行批次过多串行等待 | 看 DAG 拓扑和批次耗时分布 | 优化拆解时的依赖排序,高耗时子任务优先触发 |
5.2 实际踩过的三个大坑
坑一是评审 Agent 的无脑否定。我曾把评审的 prompt 写成“请严格挑出一切可能的问题”,它果然每次都成功找出几个“可能有问题”,导致执行者反复重做,成本翻倍。后来加了一个硬约束:评审者必须对每个意见给出具体修改路径,且只能输出二分类“通过/需修改”,如果拿不出至少一条可执行的修改建议,就视为通过。效果立刻改善,重做率从 60% 降到了 15%。
坑二是共享记忆的污染。当时为了省事,所有 Agent 把中间结果都写到一个全局存储区,结果 A 任务的某个结论被无关的 B 任务读到,生成了一段牛头不对马嘴的内容。排查了很久才从 trace_id 链路里发现。后来改成按 task_id 分区隔离,问题直接根治。
坑三是协商死循环。两个 Agent 在一个小分歧上互发消息纠正对方,最多一次来回烧了 20 多次调用才被系统强停。解决方案是给所有“协商型”任务设定最大轮次,比如 3 轮,到达上限后由协调者根据双方的论据按规则仲裁,不允许无限循环。
5.3 一个容易被忽视的设计准则
经历这么多问题后,我最大的心得是:多 Agent 系统的复杂度和 Agent 数量之间不是线性关系,而是近似指数关系。每加一个 Agent,通信路径、状态同步点和出错可能都会增加,所以能由规则完成的判断就别用 Agent,能用单 Agent 做好的任务就别硬拆。群体智能的价值在于“化整为零”,而不是“制造混乱”。
同时还要想清楚职责边界。每个 Agent 都应该有且只有一个不可替代的核心职责,如果某个 Agent 的职能可以被一段 if-else 规则替代,那它就不应该存在。
6. 如果再做一次,我会怎样优化
先说工具选型层面。第一次做的头号错误是把太多业务逻辑写死在编排代码里,导致每次调整路由策略和拆解规则都要发版。现在我会把编排策略做成可配置的规则文件,拆分逻辑、超时阈值、角色列表全部外置,让运维同学也能在后台调整。
再一个优化点是流式输出。早期系统要等所有子任务完成后聚合结果,体验上很迟钝。后来改成:协调者先输出一份“完成进度”和“初步结论”,执行过程中同步流式输出中间发现,聚合阶段再生成最终报告。用户等待体验好了很多。
最后是评测体系。多 Agent 系统的质量评测比普通应用难得多,普通应用出错是崩溃,Agent 系统出错是“看起来合理但实际有偏差”。所以我们后面搭了一套仿真任务集,包含 50 条标注过正确答案的典型业务任务,每次调整版本都先跑一遍回归,用“最终答案是否命中预期结论”来判断整体质量,这个做法帮我拦住了好几次“上线前自我感觉良好”的版本。
复盘与体会
我做了几轮多 Agent 协作系统之后,一个反复印证的判断是:多 Agent 不是模型数量的加法,而是职责约束的乘法。每新增一个 Agent 角色,都要明确它有不可替代的职责,否则只能给系统增加混乱和 token 成本。另一个实践体会是:多 Agent 的效果上限由最强的拆解策略决定,而不是最强的单模型决定。一个能合理切分任务的协调者,哪怕模型稍弱,也比一个把任务硬塞给单模型的强推理模型稳定得多。
如果你也在做类似系统,我建议你从最小的两角色模型开始搭:一个执行者加一个评审者。先跑通主流程、监测成本和错误率,再逐步加入并行执行组和记忆模块。这个系统真正难的地方不是调用几个大模型,而是把一组有各自缺陷的个体,约束成一个整体可信的协作体。这中间的编排技巧、排查工具和工程经验,希望上面这些内容能帮你少走几条弯路。