ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

多智能体总调度设计指南:从混乱协作到稳定编排的落地避坑方案

多智能体总调度设计指南:从混乱协作到稳定编排的落地避坑方案

做多智能体的人,大概都经历过这么一段心路:最开始只有两三个 Agent,大家各干各的,配合得像模像样;等到数量慢慢涨到七八个、十几个,系统就开始“闹鬼”了。我在做某模拟项目X的时候,曾经把智能体从4个扩展到8个,结果问题接踵而至:两个 Agent 同时看中同一份客户资料,一个改完另一个覆盖;一次咨询进来,三个 Agent 分别给出了三套答案,用户差点当场懵掉。问题并不出在某个 Agent 的模型能力上,而是它们根本不知道还有谁在同时干活。直到我把“总调度”作为独立模块加进去,所有任务先排队、再分派、最后汇总,整条链路才算稳定下来。这篇博客就把我踩过的坑、验证过的方案讲清楚:多智能体越多,为什么越需要一个总调度,以及这个总调度应该怎么设计、怎么落地、怎么避坑。适合正在从单 Agent 走向多 Agent 的开发者,也适合准备把智能体系统接进真实业务的架构师。

1. 智能体一多,乱的不是代码,是协作拓扑

1.1 从两个Agent说起:双人协作和十人混战差别在哪

两个 Agent 协作,其实不需要什么调度器。A 把结果直接丢给 B,B 处理完再回传,双方约定好接口就能跑起来。三个 Agent 也还行,让其中一个充当临时协调者,另外两个听它分配。可一旦超过五个、十个,问题就来了:每个 Agent 都要知道“我该听谁的”“谁在等我”“我和谁共享同一份数据”,这种知识散落在各个 Agent 的提示词和上下文里,根本维护不住。

我见过不少团队把协作规则写进系统提示词,比如“如果你是财务 Agent,你只能回答财务问题”“遇到不确定的情况,请询问审计 Agent”。这在两个 Agent 的场景下有效,但十个 Agent 时,协作关系就变成了完全图。N 个 Agent 之间的双向协作关系数量是 N×(N-1),8 个 Agent 就有 56 种潜在交互。实际运行时未必每一种都会发生,但只要缺少全局计划,任何一种交互都可能被触发,并且往往是意外触发。最后的结果就是系统行为变得极其随机:同一个任务跑三次,三次的路径都不一样,谁也说不清哪条是对的。

这个阶段的乱,本质上是协作拓扑失控。代码本身没有语法错误,单个 Agent 的 Prompt 也没有明显问题,但组合起来就是不稳定。我在项目X里遇到过最典型的情况:一个“合同审查”任务被同时分给了法律 Agent 和合规 Agent,两个 Agent 各自调用了不同的外部工具,得出了两个互相矛盾的结论。单独看任何一方都没有错,但放在同一条业务链路里就变成了严重事故。

1.2 常见的三种失序现象:抢活、空转、互相等待

把多智能体系统跑起来以后,最常见的失序现象有三种。第一种叫“抢活”:能力重叠的两个 Agent 同时收到同样一个任务。原因通常是任务路由没有统一入口,或者入口只管分发、不管查重。比如你有一个负责写邮件草稿的 Agent,又有一个负责“商务沟通文案”的 Agent,两个能力描述高度相似,同一个请求就可能同时命中它们。结果就是两边各做一遍,浪费算力不说,返回给用户的结果还得你自己挑一个,挑错了就是事故。

第二种叫“空转”:Agent 在等待另一个 Agent 的结果,而那个 Agent 也在等待别人,甚至整个链路里没有任何一个任务正式开始执行。这类问题在引入异步执行之后尤其容易出现。A 任务依赖 B 任务,B 任务依赖 C 任务,而 C 任务的触发条件又写成了“等待 A 完成”,于是一条环形依赖直接把整批任务卡死。没有调度器的时候,这类问题只能靠日志一条条翻,翻到最后你甚至会发现部分 Agent 根本没有被调用过。

第三种叫“上下文污染”:一个 Agent 读取了共享数据源并做了修改,另一个 Agent 读到的却是修改前的旧快照。我在项目X里就踩过这个坑。两个 Agent 并行处理同一份合同的不同条款,其中一个把“金额”字段更新了,另一个手里还攥着老版本的全文,结果最终报告里金额前后不一致。这三个现象的根因全部指向同一个地方:缺少一份所有任务都认可的全局执行计划,缺少一个唯一的事实来源。

1.3 为什么多Agent不是算力问题,而是结构性矛盾

很多人第一反应是“加计算资源”“换更强的模型”。但多 Agent 系统的问题并不是单个步骤算不动,而是步骤之间的衔接关系乱了。无论模型多强,你都无法通过一条系统提示词让十个 Agent 长期保持步调一致。大模型的每一次推理都带有概率性,哪怕同一个 Agent,给它完全相同的输入,两次也可能给出略有差异的输出。让这样一批概率性组件去自行协调,相当于让一群各自拿着半张地图、且地图信息还会过期的士兵协同作战,结果自然不可控。

多智能体系统本质上是一个分布式系统。分布式系统的第一法则就是:不能假设所有节点都会主动配合。放在智能体场景里也一样:你不能假设每个 Agent 都会主动上报进度,不能假设每个 Agent 都在用最新版本的数据,不能假设两个 Agent 不会对同一个外部工具做重复调用。总调度就是把“隐性的协作假设”变成“显式的执行约束”。它不一定是复杂系统,但必须是独立于业务 Agent 之外的一个角色,专门负责回答四个问题:现在该做什么、由谁来做、做完之后给谁、遇到冲突听谁的。

补充一点,调度不是限制自由度。恰恰相反,有了总调度之后,每个 Agent 反而可以更自由。因为它们不需要再关心“别人在干什么”“该不该主动汇报”,只需要把眼前的任务做好,把结果标准化地交回去。整个系统的自由度,从“无序自治”变成了“有限自治”,问题复杂度会立刻降一个台阶。

2. 总调度不是“加一个Agent”,而是独立分层

2.1 总调度器区别于普通Agent的三个特征

很多人在设计多智能体系统时,第一反应是“再加一个主管 Agent 来分配任务”。这个思路方向对,但容易做偏。主管 Agent 本质上仍然是一个 LLM Agent,它还是会有随机性,还是会漏状态,还是可能遗忘上下文。真正稳定的总调度器,应当是一个独立的分层模块,而不是一个加了特殊 Prompt 的普通 Agent。

总调度器至少有三个特征。

第一,它不直接产出业务结果。总调度不写文案、不判断合同条款、不做数据抽取。它的产出物是“任务分配方案”“执行顺序”“冲突裁定结果”。一旦调度器下场干活,它就失去了中立性,也会把自己的算力消耗和状态管理混进业务链路,复杂度不降反升。

第二,它拥有全局状态。只有调度器能看到所有 Agent 的任务状态、输出摘要、资源占用情况。普通 Agent 只能看到分配给它自己的那部分上下文,这是一个刻意的隔离设计。Agent 知道得越少,就越不容易产生上下文污染。

第三,它有最终仲裁权。当两个 Agent 给出的结论互相矛盾时,谁来拍板?不能靠两个 Agent 继续对话协商,因为它们很可能各自坚持己见。仲裁权必须收归到调度器,或者由调度器决定“是否需要引入第三方复核”“是否直接进入人工通道”。没有仲裁权的调度器,本质上只是消息转发器。

2.2 总调度要管的四件事:路由、状态、依赖、仲裁

展开来说,总调度至少要做四件事。

第一件是任务路由。接收一个外部请求后,调度器要判断这个请求属于什么类型,再由能力匹配算法选择合适的 Agent。路由不是简单的关键词匹配。同一个请求,可能有多个 Agent 都声称自己能处理,这时候就要有一个打分机制,把“能力描述相似度”“当前负载”“历史成功率”等因素综合进去。

第二件是状态维护。每个任务都要有明确的生命周期状态:等待中、运行中、已完成、失败、超时、被仲裁。状态是全局唯一的,所有下游逻辑都以这个状态为准。你可以用一张数据库表、一个 Redis 缓存或者内存字典来维护,核心是要让状态成为“唯一事实来源”。

第三件是依赖协调。业务复杂以后,任务之间会有先后关系。比如必须先把文档解析完,才能做条款提取;条款提取完,才能做风险评估。调度器必须能表达这种依赖关系。我建议用 DAG 来表达:每个任务是一个节点,依赖关系是边,只有入度为 0 的任务才进入执行队列。这样可以自然表达并行和串行。

第四件是冲突仲裁。两个 Agent 的结果不一致,调度器要决定如何处理。我的经验是:不要盲目信任“最新结果”或“置信度最高”的一方。置信度高不代表正确,最新也不代表更准确。最稳妥的策略是“能自动合并则合并,不能合并则走人工复核通道”。哪怕是多花一点时间,也好过让错误结果进入下一个环节。

2.3 机场塔台类比:调度员不该自己开飞机

总调度最合适的类比是机场塔台。机场不会让每个飞行员通过公共频道广播自己的航线,让所有人自行协调避让,而是由塔台统一统筹。塔台知道每架飞机的当前位置、计划航线、跑道占用情况,然后给出清晰的指令:你现在保持高度,你跟着前面的飞机进近,你暂时在等待区盘旋。

Agent 也一样。有了总调度之后,每个 Agent 不需要感知全局。它只接收三样东西:格式化的任务描述、任务必要的上下文、输出格式要求。执行完毕后,上报结果摘要。这个模式的优点很明显:低耦合、可演进。新增一个 Agent 时,只需要向调度器注册它的能力和约束条件,其他 Agent 的代码完全不用动。整个系统从“网状协作”变成了“星型协作”,生态位清晰了,故障定位也容易了。一个任务出问题,先查调度记录里“分配给谁”“执行多久”“输出摘要是什么”,基本一眼就能定位到是路由错了、还是某个 Agent 自身能力不行。

还有一条纪律值得特别注意:调度器一定不要顺手做一些“简单但紧急”的业务任务。比如看到某个数据格式需要转换,就自己写个转换逻辑。一次两次没事,时间长了调度器就会变成一个隐形的业务 Agent,它的输出也开始需要被别人调度,最终回到老路上去。

3. 实现总调度的四种模式与选型依据

3.1 中央调度器模式:直接但别让中心变成瓶颈

最常见的实现方式,是把调度器做成一个独立的中央服务。外部请求先进入调度器,调度器把请求转成内部任务,塞进队列,然后按顺序或按优先级分发给各个 Agent。每个 Agent 执行完自己的部分,把结果摘要回传给调度器,调度器再决定下一步动作。

这种模式的优势是简单直接,控制力强。所有任务都过同一个入口,状态集中在一个地方管理,出现问题时,排查路径非常短。我一般建议几十个 Agent 以内的系统,优先采用中央调度器模式。不要一上来就搞分布式协调,那是过度设计。

但中央调度器也有明显的软肋:它本身可能成为性能瓶颈和单一故障点。如果所有任务都是同步等待,来一个请求就阻塞一个线程,并发一高,调度器立刻变成最慢的环节。我的处理方式是把同步调用改成异步任务队列:调度器只负责入队、分派、记录状态,Agent 执行完了通过回调或者轮询上报结果。这样调度器本身的耗时只占整个链路的一小部分,瓶颈压力会小很多。另外,调度器一定要做持久化,不能只把状态放在内存里。否则一旦服务重启,所有在途任务全部丢失,屏幕上全是“任务状态未知”。

3.2 工作流/状态机模式:适合流程固定、过程可预期的系统

如果业务场景是一个稳定的流程,比如“客户工单进来 -> 意图识别 -> 信息抽取 -> 分类 -> 自动回复生成 -> 质检”,那么用工作流/状态机模式会更舒服。所谓工作流模式,就是把调度逻辑预先定义成一张流程图或状态表:当前处于什么状态、满足什么条件、接下来把任务交给哪个 Agent。

与中央调度器相比,工作流模式的优势在于流程可视化、结果可预期。开发阶段就可以把整条链路画出来,评审的时候大家直接对着流程图讨论,而不是读代码。真实项目里,我习惯用 JSON 或 DSL 来描述流程定义,而不是硬编码在代码里。这样流程调整不需要改代码重新发布,运营同学改个配置就能实现“先做 B 再做 A”。

这种模式的代价是灵活性差。一旦业务流程开始变得动态,比如“根据上一步的产出决定下一步的分支”,工作流描述就会迅速膨胀。分支多了以后,配置文件和传统代码一样难以维护。所以我通常只在“流程确定性比较高、变化频率低”的场景采用这个模式。如果每两天就要加一个新分支,建议回到中央调度器模式,把决策逻辑交给代码。

3.3 事件总线+分布式协调:适合高动态、长时运行的系统

当 Agent 数量继续增加,或者任务是长时运行、中间有大量等待时,事件驱动模式会更合适。Agent 不再直接向调度器汇报“我做完了”,而是向事件总线发布一个事件,比如“合同条款提取完成,结果存储在某个可访问地址”。调度器监听自己关心的事件类型,根据事件内容推进后续任务。

这个模式和前两种的核心差别在于:调度器和 Agent 之间解耦了。Agent 不需要知道调度器是谁,调度器也不需要惦记每个 Agent 的实时状态。事件总线天然支持消息的异步投递,也方便新 Agent 接入。

代价是调试复杂度明显上升。事件是分布式系统中的双刃剑:它会乱序,会重复投递,也可能丢失。所以每个事件必须携带唯一的任务 ID,消费方要做幂等处理。在项目X的后期,跑多个环境时,我甚至见过同一事件被重复消费导致任务执行两遍,对外表现为重复扣费。后来在消费逻辑里加了“按任务 ID 去重”的机制,才算止住。事件总线模式比较适合已经有一定分布式系统经验的团队,新手团队直接上手容易被打得晕头转向。

3.4 多级调度:先全局规划,再局部自治

还有一种模式往往在大型系统里出现:多级调度。全局调度器只负责大的阶段划分,比如“先做资料准备,再做分析,最后出报告”;每个阶段内部,又有一个局部调度器负责细化该阶段里的 Agent 分工。

这就像公司组织架构里的部门制。全局调度器是总经理,局部调度器是部门主管。总经理只管目标和结果,不干预部门内部谁做哪一步;部门主管负责把目标拆成具体动作,指挥下面的人干活。这样做的好处是隔离故障:某个局部调度器出了事故,最多影响那个阶段,不会拖垮全链路。

多级调度的难点在于两级状态怎么同步。局部调度器上报给全局的状态,必须有一个清晰的信息模型,否则会出现“全局认为任务已经完成,局部还在跑”的错位。我常用的思路是:上级只下发目标和约束,不干预微观操作;下级只上报状态和完成事件,不试图影响其他阶段。各管各的,才能确保边界清晰,不至于两级互相打架。

3.5 选型对照表与决策经验

不同模式的取舍,我整理了一张选型对照表,方便直接照着看。

模式适用规模流程确定性灵活性实现成本主要风险
中央调度器几十个 Agent 以内任意高低中心节点性能瓶颈
工作流/状态机流程稳定场景高低中分支膨胀难维护
事件总线+分布式协调大规模、长时任务中很高高事件乱序、重复、丢失
多级调度大型跨团队系统中中高两级状态同步复杂

选型不需要一步到位。我的建议是:刚开始做多智能体系统,直接选中央调度器;跑通之后,发现流程固定,再抽工作流;规模真正大了,再考虑事件总线和多级调度。不要在一开始就搭一个包含全部机制的“豪华调度平台”,大概率只会得到一个没人能维护的大泥球。

4. 一个可以抄作业的总调度落地样例

4.1 给Agent定义统一的“任务执行协议”

纸上谈兵容易,但落地时最关键的是给所有 Agent 定义一个统一的任务执行协议。我在项目X里用了非常朴素的 HTTP 接口:每个 Agent 暴露一个POST /execute接口,接收统一的请求结构,返回统一的结果结构。请求结构大致长这样:

{ "task_id": "task_20241218_001", "task_type": "contract_clause_extraction", "payload": { "document_url": "s3://bucket/contracts/2024-12-18/a.pdf", "clause_types": ["payment", "liability", "term"] }, "context_uri": "s3://bucket/shared_context/2024-12-18/contract_001.json", "output_schema": { "type": "object", "properties": { "clauses": {"type": "array"}, "confidence": {"type": "number"} } } }

响应结构统一为:

{ "task_id": "task_20241218_001", "status": "success", "summary": "共提取到 12 个条款,覆盖支付、责任、期限三类,置信度 0.92", "result_uri": "s3://bucket/results/task_20241218_001.json" }

这里有两个容易被忽略的设计点。第一,调度器不直接请求 Agent 返回完整结果,而是只拿 summary,引用 result_uri。原因很简单,完整结果可能很大,如果在调度器里全部加载,再传递给下游 Agent,上下文很快就会被塞满,调度器也会变成内存消耗大户。第二,output_schema 必须显式声明,让 Agent 明确知道自己该输出什么结构。没有这个约束,Agent 自由发挥,返回格式千奇百怪,下游解析代码会变成一场灾难。

所有 Agent 在接入系统时,还要向调度器注册:注册名、能力描述、输入输出 schema、最大并发数、平均处理时延。注册信息是路由模块的基础数据。

4.2 调度器的核心数据结构与路由规则

调度器内部最重要的数据结构有两张表:任务表和能力路由表。

任务表记录所有任务的全局状态。字段包括:task_id、task_type、status、depends_on、assignee、retries、timeout、created_at。能力路由表记录每个 Agent 能干什么:agent_id、capabilities、max_concurrency、current_load、success_rate。任务进来时,先不看 Agent,而是先看它依赖的任务是不是都完成了。依赖没完成的任务直接挂起,不参与路由。

路由规则我最初用的是最简单的关键词重合度,后来发现不够。一个 Agent 的能力描述写成“合同条款审查”,另一个写成“法律风险分析”,关键词重合度很低,但实际业务上承担的工作很接近。于是改为“能力描述向量相似度 + 负载系数 + 历史成功率”的组合评分。伪代码大致如下:

def route_task(task): candidates = [] for agent in agents: if not agent.available(): continue score = compute_capability_score(task, agent) # 基于向量相似度 score *= (1 - agent.current_load / agent.max_concurrency) score *= agent.success_rate candidates.append((score, agent)) if not candidates: return defer_task(task, reason="all_agents_busy") candidates.sort(reverse=True) return candidates[0][1]

这段逻辑并不复杂,但它把一个关键原则落地了:宁可让任务排队,也不要同时发给多个 Agent 去抢。没有总调度的时候,任务广播出去,谁响应快谁干;有总调度以后,每次只派给一个 Agent。这个“唯一指派”原则是减少抢活的有效手段。

4.3 依赖解析与冲突仲裁的判别逻辑

依赖解析我用的是标准 DAG 算法。每个任务生成时,会声明它依赖哪些任务 ID。调度器维护一个 pending 队列,只把入度为 0 的任务放出去执行。任务完成以后,更新所有下游任务的依赖计数,当某个任务的所有依赖都完成时,它就可以进入待执行队列。这个逻辑用 Python 写起来非常直观,官方数据结构就能实现。

冲突仲裁稍微难一点。一个典型场景:合同金额抽取结果,一个 Agent 从文本里识别出“100 万”,另一个 Agent 从表格附件里识别出“120 万”。两个结果都不为空,并且都声称置信度 0.85 以上。这时候调度器怎么办?我的策略分三步:第一步,比较置信度,取高的一方;第二步,如果置信度接近,比较数据的来源时间戳,取更新的一份;第三步,如果双方都相差不大,直接不再自动决定,置为needs_review状态,进入人工复核队列。

特别说一句,人工复核不是无奈之举,而是多智能体系统的保险丝。自动流程追求的是效率和覆盖率,但总有少数边界情况是自动裁决搞不定的。与其让系统带着不确定性继续往下跑,不如把不确定性显式暴露出来,让人类接手。项目X上线后的真实数据表明,进入人工复核的任务只占总量的 3% 左右,但对最终业务准确率的影响非常大。

4.4 超时、降级与人工介入通道

Agent 是外部依赖,永远要假设它会失联、会卡死、会返回乱数据。所以超时和降级机制必须设计在前面,而不是出了问题再补。

我用的超时策略不是固定值,而是动态计算:单个任务的超时上限为“该 Agent 的历史平均处理时延乘以 3,再加上 30 秒”。这样既不会给慢任务误判,也不会让一个卡死的任务占用队列太久。超时之后不马上标记失败,而是重试一次。如果重试仍然超时,再看当前是否有替代 Agent。有,就转移任务;没有,就把它降级成“规则兜底”,比如用简单的字符串匹配逻辑先给一个临时结果,并明确标记为“低置信度,未经 Agent 验证”。降级不能静默发生,所有降级事件都必须记录原因,输出到监控看板。

人工介入通道要做得极其简单。我见过一些系统把人工复核做成一个复杂的审批流,结果一线人员根本不愿意用。实用性设计是:一个只有三列的任务列表,待复核、已通过、已驳回;通过或驳回时写一句话原因即可。调度器根据人工结论决定继续走流程还是终止重跑。

5. Agent变多之后,这些坑我基本都踩过

5.1 调度器自己成为性能瓶颈

调度器设计不好,第一个坑就是它自己变成瓶颈。我在项目X早期,所有任务都走同步调用,调度器同时收到大量请求时,响应时间呈指数级上升。排查以后发现问题非常简单:每个请求都在等下游 Agent 返回,而每个下游 Agent 的处理耗时要好几秒,积压到一定数量,调度器线程池直接被占满,新请求全部排队。

解法是把同步链路改成异步链路:调度器收到外部请求后,立刻返回“任务已受理,task_id 为 xxx”,后面的执行全部在后台推进,前端通过轮询或回调拿最终结果。再把调度器的状态存储从数据库切换成内存缓存加异步落盘,让状态读写在主链路里保持极低的延迟。做这一步之前,调度器的 P95 延迟经常飙到十几秒;改完以后,P95 稳定在几十毫秒以内。调度器必须轻,这是硬道理。

5.2 Agent上下文串台

第二个我踩过的大坑是上下文串台。最初 my design 太“慷慨”了,把所有 Agent 可能用到的资料都塞到一个共享上下文里,每个 Agent 都能读取全局信息。结果就是 Agent A 在处理客户 A 时,不知怎么回事引用了客户 B 的备注信息;Agent B 在处理某合同条款时,参考的是另一个项目的背景说明。排查后发现,是共享上下文的 key 设计得太宽泛,多个任务都在写同一个缓存键,后写的覆盖了先写的。

改法很直接:把共享上下文改成分任务切片。每个任务只携带和它相关的 payload 片段;如果不确定是否需要,就给一个 context_uri,让 Agent 按需读取,而不是把整个数据库都塞给它。同时,所有上下文写入都必须带 task_id 前缀,确保不同任务之间物理隔离。这个改动很基础,但它彻底解决了串台问题。

5.3 死锁和任务循环

死锁在任务依赖复杂之后一定会出现。我在项目里遇到过一次:任务 A 依赖 B,B 依赖 C,C 又依赖 A,铁三角。三个任务在 pending 队列里互相等待,谁也不会被执行,整条链路直接静止。最麻烦的是,这种死锁不会报错,看起来只是“系统很慢”或者“任务一直没有进展”。

我后来在调度器里加了两个机制。第一个是提交任务时,对整张依赖图做环检测。发现有环,直接拒绝提交并返回错误。第二个是为每个任务设置最大等待时间。一个任务在 pending 队列里等待超过阈值,就自动唤起检查逻辑,判断是“依赖还没完成”还是“形成了环”。另外,我严格控制了 Agent 的权限,不允许 Agent 在任务完成后修改自己的依赖关系,所有依赖变更只能由调度器发起。这个纪律让任务循环的概率大幅下降。

5.4 不可观测才是最大的隐患

加调度器不是为了“加一个黑盒”,而是要让系统变得可观测。因为所有任务都从调度器经过,调度器天然拥有全链路信息。如果不好好利用这些信息,等于守着金矿不挖。

我在项目X里为每个任务生成了一个全局唯一的 task_id,并强制所有日志、所有 Agent 的调用记录、所有回调事件都带上这个 ID。出现任何问题,输入 task_id,就能拉出一条完整的任务时间线:什么时候创建、什么时候进入队列、分给了哪个 Agent、Agent 返回了什么摘要、下一步去了哪里。没有这套链路追踪,排查多 Agent 问题基本上靠猜,猜一次两次还行,猜多了就会漏真实原因。

我还做了一个简单的统计分析:按 Agent 维度统计成功率、平均处理时长、平均置信度。每周看一眼这个表,哪个 Agent 能力退化了一眼就能发现。曾经有一个 Agent 因为外部接口改版,成功率从 0.95 掉到 0.6,就是靠这张表发现的,省去了很多用户投诉才发现的尴尬。

5.5 状态管理的取舍:集中状态 vs 分散状态

最后一个经常讨论的问题:状态应该放在调度器里,还是分散在各自 Agent 里?我的实践经验是:以调度器状态为唯一事实来源。Agent 可以保留自己的内部状态,但那些状态不会被当作权威依据。任务是否完成、结果是否合法、下一步是什么,都只能看调度器里存的状态。这样能保证全局视图一致。

当然,集中状态也有自己的问题,比如存储压力。但多智能体系统的任务数量通常远没有想象中的大,几十个任务并发已经是中等规模了。用一张设计良好的表或者缓存来维护状态,完全不是问题。真正需要警惕的是“两边都写状态,然后互相覆盖”。我见过的最严重的状态事故,就是 Agent 自己把任务改成“已完成”,而调度器还认为它在“运行中”,导致外部看到两个矛盾的状态。后来我在协议里明确规定:Agent 不能写全局状态,只能上报结果,最终状态更新必须由调度器执行。宁可多一次网络调用,也不能让状态多源写入。

收尾:最后再分享一点实际体会

做了这么久的多智能体系统,我最大的体会是:总调度不是给 Agent 戴上枷锁,而是让每个 Agent 可以安心只做自己擅长的事。在没有调度器的系统里,每个 Agent 都像会议桌上同时发言的人,所有人都很聪明,但没有人听得清完整议题。加了总调度之后,会场里多了一个主持人:有人负责议题拆分,有人负责控制发言顺序,有人负责确认结论一致,冲突发生时还有一套明确的三方裁决机制。整个系统从“聪明人开混乱会议”变成了“有主持人的高效工作坊”。

如果你现在正被多 Agent 的混乱状态困扰,我的建议是不要一步到位。先把任务入口统一,引入一个简单的队列和路由模块,观察一周,再看哪里的协作问题最突出。稳定之后,再逐步加入依赖编排、冲突仲裁、人工复核通道。一次只加一个机制,验证井井有条之后再继续,比一次上一整套调度平台要稳妥得多。这套思路救过我的项目,也希望它能帮你少踩几个坑。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进