上一篇讨论了什么情况下,一个任务适合交给多个 Agent 协作。
完成职责划分后,接下来要解决的是 Multi-Agent 系统如何运行,也就是控制流(Control Flow)的问题。
比如,研究 Agent 已经返回结果,下一步交给写作 Agent,还是先让审校 Agent 检查资料?如果结果不完整,是重新研究,还是继续往下执行?整个任务又应该在什么条件下结束?
这和初学编程时控制流很相似。普通程序通常由开发者提前写好执行路径:什么时候进入分支,什么时候循环,下一步调用哪个函数。Multi-Agent 系统在此基础上多了一层变化:部分路径可以由模型根据当前状态动态决定。
因此,设计 Multi-Agent 系统时,关键要明确两件事:任务如何从一个执行单元流向下一个执行单元,以及每一次路径选择由谁来决定。
顺序、条件、并行、计划和移交,都是围绕这两个问题形成的不同控制方式。
一、控制流的设计对象
普通程序中的控制流很容易识别。
if 创建分支,for 创建循环,程序写完后,其执行路径也随之确定。
Multi-Agent 系统依然需要这些规则,只是“下一步做什么”多了一种可能:交给模型在运行时判断。
例如,一个线上故障排查系统包含日志分析、指标分析、代码检查和修复验证四个 Agent。
最简单的方式是固定执行:
日志分析 → 指标分析 → 代码检查 → 修复验证
另一种方式,是增加一个负责调度的 Agent。
数据库延迟升高时,它先检查慢查询;而上线后不久刚好出现故障,它可能优先检查代码变更;证据不足时,还可以增加新的调查步骤。
以上两种设计中,Agent 的功能可能完全相同,但运行方式却不同。
前一种方案中,路径主要由代码决定;后一种方案中,模型开始参与路径选择。
所以,设计 Multi-Agent 时,应考虑一个重要问题:每一次路由决定,由代码、规则、人工,还是模型负责?
二、显式控制的工作流
工作流(Workflow)由开发者预先定义主要执行路径。
一种常见实现方式,是把工作流表示成计算图。比如 LangGraph 中的设计。其中:
- 节点(Node):负责执行具体任务;
- 边(Edge):规定节点之间怎样流转;
- 条件边(Conditional Edge):根据当前状态选择后续节点。
显式工作流路径确定,适合步骤稳定、依赖清楚,或者对审计和安全要求较高的任务。
它的问题也很明显:如果实际执行路线经常变化,条件分支会越来越多,最后可能形成一张很难维护的流程网。
三、三类基础工作流
顺序、条件和并行,是最常见的三种显式控制方式。
顺序工作流
顺序工作流按照:
A → B → C
依次执行,前一个节点的结果成为后一个节点的输入。
例如故障处理可以设计成:
收集信息 → 诊断问题 → 生成方案 → 验证结果
验证阶段依赖前面的诊断和修改结果,因此没有必要提前执行。
这种结构路径清楚,失败后也容易知道任务停在哪里。
缺点是延迟会累加。前面某一步执行较慢,后面的节点都要等待。
条件工作流
条件工作流根据当前状态选择分支。
条件可以来自普通代码,例如错误码、请求类型和风险等级,也可以来自模型生成的分类结果。
例如监控系统可以把告警交给数据库、网络或应用 Agent。
即使分类由模型完成,只要模型只能在几个预先声明的节点中选择,整体控制权仍然属于工作流。
下面是一个 LangGraph 示例:
from typing import Literal
from typing_extensions import TypedDict
from langgraph.graph import END, START, StateGraph
class IncidentState(TypedDict):
alert_type: str
result: str
def route_alert(
state: IncidentState,
) -> Literal["database_agent", "network_agent"]:
if state["alert_type"] == "database":
return "database_agent"
return "network_agent"
def database_agent(state: IncidentState) -> dict:
return {"result": "检查慢查询和连接池"}
def network_agent(state: IncidentState) -> dict:
return {"result": "检查丢包率和网关延迟"}
builder = StateGraph(IncidentState)
builder.add_node("route_alert", lambda state: {})
builder.add_node("database_agent", database_agent)
builder.add_node("network_agent", network_agent)
builder.add_edge(START, "route_alert")
builder.add_conditional_edges("route_alert", route_alert)
builder.add_edge("database_agent", END)
builder.add_edge("network_agent", END)
workflow = builder.compile()
result = workflow.invoke(
{"alert_type": "database", "result": ""}
)
print(result["result"])
StateGraph 创建状态图,add_node() 注册节点,add_edge() 添加固定边,add_conditional_edges() 则根据路由函数的返回值选择后续节点。
运行结果如下:
检查慢查询和连接池
因为 alert_type 为 database,工作流会进入 database_agent。
如果把 route_alert 换成模型分类,模型可以参与判断,但它仍只能选择预先允许的节点。
这类设计的优点比较明显:把难以硬编码的判断交给模型,同时保留清楚的执行边界。
并行工作流
如果多个任务之间没有依赖,就可以同时执行,再统一合并结果。
这就是常见的 Fan-out / Fan-in。
例如排查线上延迟时,可以同时检查日志、监控指标和最近的发布记录,完成后再由汇总 Agent 对照分析。
并行可以缩短整体等待时间,但也会增加协调问题:
某个分支超时怎么办?汇合节点需要等待哪些结果?多个 Agent 同时写入状态时如何处理冲突?
因此,判断是否适合并行,关键要看数据依赖和可能影响 Agent 运行的异常情况,而不是 Agent 的数量。
四、运行时产生的路径
有些任务很难提前确定完整流程。
例如用户只提出:
找出这次性能下降的原因,并给出修复建议。
系统可能先查看监控指标,再检查日志和版本变更;调查过程中,也可能发现问题其实来自第三方服务。
这类任务无法把完整执行路径提前写死。模型需要根据当前目标、已有证据和工具返回结果,在运行过程中决定下一步:
下一步调查什么
↓
调用哪个 Agent 或工具
↓
是否继续调查
因此,任务的实际路径是在运行时逐步形成的。这类方式通常可以称为动态控制流(Dynamic Control Flow),在 Multi-Agent 系统中也常表现为动态编排(Dynamic Orchestration)。
如果多个 Agent 能够自主协作,整体执行路径并非由某个中心控制器明确规划,而是从 Agent 之间的局部决策和交互中逐渐形成,也可以进一步称为涌现式控制(Emergent Control)。
不过,自主并不意味着没有限制。
生产系统通常仍然要设定:
- 可以调用哪些 Agent 和工具;
- 最大执行轮数;
- Token 或调用预算;
- 超时规则;
- 哪些操作必须人工确认。
一个实用原则是:模型负责难以提前确定的判断,程序负责安全边界和资源上限。
例如模型可以决定是否继续调查,但最多执行十轮;它可以提出修改数据库配置,但真正执行前必须经过人工审批。
自主程度提高以后,测试方式也要改变。
此时不能只检查是否经过某条固定路径,还要验证最终结果、异常行为、调用次数和资源消耗。
五、计划式集中编排
计划式编排(Plan-Based Orchestration)通常由一个编排 Agent 管理整个任务。
典型过程是:
理解目标
↓
生成计划
↓
分配 Agent
↓
检查结果
↓
调整计划
↓
判断结束
例如故障原因未知时,编排 Agent 可以先安排日志和指标调查。
如果指标 Agent 发现数据库连接等待异常,它可以暂停原来的代码检查,增加慢查询分析,修复后再安排验证。
这种方式的优势是:动态执行仍然保留了明确的任务状态。
系统可以知道哪些步骤已经完成、哪些仍在等待,以及为什么增加或取消了某个任务。
计划式编排也有利于控制上下文。
编排 Agent 掌握完整任务状态,而数据库 Agent 只接收当前工作真正需要的内容,不必读取此前所有对话。
这样可以减少干扰,也能降低 Token 消耗。
风险同样集中在编排 Agent。
如果它最开始判断错了方向,后续专业 Agent 很可能沿着错误路线继续工作。
因此,计划式编排更适合目标明确,但解决步骤需要动态产生的任务。
如果流程本来就固定,直接使用工作流通常更简单。
六、移交式局部协作
移交(Handoff)让当前 Agent 判断自己的工作是否结束,并把任务交给另一个 Agent。
例如:
一线运维 Agent
↓
数据库 Agent
↓
基础设施 Agent
一线运维 Agent 判断问题来自数据库,于是交给数据库 Agent;数据库 Agent 又发现问题来自云基础设施,再继续移交。
这种方式适合职责边界清楚、处理过程需要连续交互的场景。
不过,Handoff 至少需要明确三个问题:
- 什么情况下移交;
- 可以移交给谁;
- 需要传递哪些状态。
否则很容易形成:
Agent A → Agent B → Agent A → Agent B
另一个常见问题是上下文丢失。
因此,移交时通常应该携带:
- 当前任务目标;
- 已确认事实;
- 已执行操作;
- 尚未解决的问题;
- 希望接收方完成什么。
没有必要复制全部聊天记录,否则既增加 Token 成本,也会引入无关信息。
七、对话式共同协作
对话式协作(Conversation-Driven Pattern)让多个 Agent 共享一段会话,通过轮流发言完成任务。
这种模式也常被称为 Group Chat。
发言顺序可以固定:
研究 Agent → 分析 Agent → 审校 Agent
也可以由模型根据当前内容决定下一位发言者。
例如审校 Agent 发现某条结论缺少来源,可以重新调用研究 Agent,而不需要提前写出固定回路。
这种模式搭建简单,也方便观察讨论过程,适合方案探索、技术评审和多角色讨论。
问题主要来自成本和控制。
随着消息越来越多,每个 Agent 读取的历史也会越来越长,Token 消耗持续增加。
如果固定轮询,还可能出现无效调用;如果让模型选择下一位发言者,又增加了路由判断的不确定性。
因此,它适合开放探索,且必须有明确的停止条件。
八、模式选择与组合
下表展示了上述所有模式以及适用场景。
| 模式 | 下一步由谁决定 | 适合场景 | 主要代价 |
|---|---|---|---|
| 顺序工作流 | 固定边 | 步骤和依赖稳定 | 串行延迟 |
| 条件工作流 | 规则或受限路由 | 分支已知 | 分支维护 |
| 并行工作流 | 预定义依赖 | 子任务独立 | 状态合并 |
| 计划式编排 | 编排 Agent | 目标明确、路径未知 | 编排错误 |
| 移交式协作 | 当前 Agent | 职责边界清楚 | 循环移交 |
| 对话式协作 | 规则或模型 | 开放探索 | Token 增长 |
实际系统很少只使用一种模式。
例如故障处理可以设计成:
告警分类
↓
并行检查日志、指标和版本
↓
计划式调查根因
↓
固定审批
↓
执行变更
↓
固定验证
规则明确的部分交给工作流,未知程度高的部分再增加模型决策。
一个复杂 Agent 内部也可以继续包含另一套控制模式。比如,外层工作流中的“故障调查”节点,可以运行计划式 Multi-Agent;其中的“安全审查”又可以采用固定检查流程。
总结来说,选择控制方式时,我们可以从几个方面判断。
如果下一步能够提前明确,优先使用固定工作流,执行路径更稳定,也更容易测试和维护。
如果子任务之间彼此独立,可以并行执行;存在明显前后依赖时,则更适合保持顺序。
还要看一次动态决策需要多少上下文。如果决策依赖完整任务状态,集中编排通常更合适;如果只涉及相邻角色之间的职责转交,可以考虑使用 Handoff。
最后要考虑系统能够接受多大的路径变化。模型拥有的控制权越多,执行路径越灵活,但测试、可观测性、成本控制和异常恢复的要求也会随之提高。
最后,回答本文开头的问题。
研究 Agent 完成任务以后,如果资料检查属于固定步骤,可以直接进入审校节点;如果下一步取决于研究结果,可以交给编排 Agent 判断;如果任务已经进入另一个明确职责范围,则适合 Handoff。
Multi-Agent 架构中有多少个 Agent,并不能说明系统是怎样运行的。真正决定系统行为的,是谁拥有控制权,依据什么状态决定下一步,以及异常发生后如何停止和恢复。
社区讨论
参与讨论
有问题或想法?欢迎继续讨论。