你的 Agent 从 1 个变成了 5 个。一个负责检索,一个写代码,一个测试,一个生成报告,还有一个当「领导」。代码写好了,Agent 们各自跑起来了——但你怎么让它们协作呢?
最直接的方案是让「领导 Agent」给每个 Sub-Agent 发一条消息:「你,去检索数据库。你,写一个 Python 脚本。你,跑测试。你,把结果写成报告。」
但检索 Agent 等了两秒说「查完了」,代码 Agent 说「脚本写好了」,测试 Agent 说「代码有 bug,跑不过」,报告 Agent 说「我没数据怎么写报告」——于是代码 Agent 改 bug,再跑测试,再生成报告。如果领导 Agent 是个顺序执行的循环,那这段时间它一直卡在这批 Agent 上,没空去开新的工作流。
更糟的:如果测试 Agent 说「我拒绝这个代码,它不安全的写法有 3 个 CVE」,而代码 Agent 说「那 3 个 CVE 都不适用于这个上下文」——谁来仲裁?
这不是 Demo 场景。这是多 Agent 系统上线的第一天。
这篇文章是 AI Agent 工程实战系列的第五篇,前四篇分别讲了 RAG 检索精度提升、Tool Calling 可靠性与容错、推理延迟优化、状态管理与持久化。这一篇聚焦多 Agent 编排的核心问题——谁来协调、怎么协调、以什么代价协调。
📌 本系列:一、RAG 检索精度提升实战 → 二、Tool Calling 可靠性与容错 → 三、推理延迟优化 → 四、状态管理与持久化 → 五、多 Agent 编排(本篇)
一、三种编排模式总览
在开始逐个拆解之前,先看全景。任何多 Agent 系统,Orchestrator 的核心问题都是同一个:谁来分解任务、谁来分配任务、谁来验证结果、谁来仲裁冲突? 不同的回答方式,构成了三种基本模式。
| 维度 | Planner/Executor/Validator | 路由分发 (Routing) | Debate/Consensus |
|---|---|---|---|
| 核心机制 | 计划→执行→验证三元循环 | Orchestrator 按任务类型路由到专业 Sub-Agent | 多 Agent 讨论达成共识 |
| 控制模型 | 中央式(Plan 驱动) | 中央式(Route 驱动) | 去中心化(Peer 协商) |
| 通信模式 | Orchestrator ↔ Sub-Agent 链式 | Orchestrator ↔ Sub-Agent 星型 | Sub-Agent ↔ Sub-Agent 全连接网状 |
| 决策路径长度 | 较长(Plan→Exec→Validate 循环) | 短(一次路由即到位) | 不定(取决于达成共识的轮次) |
| Sub-Agent 数量 | 3-10 个(受限于 Plan 粒度) | 10-100+ 个(可水平扩展) | 3-10 个(受限于通信复杂度) |
| 通信开销 | 🟡 中(每轮 1-2 次消息) | 🟢 低(每次任务 1 次路由) | 🔴 高(O(n²) 消息数) |
| 一致性代价 | 🟡 中(验证步骤保证) | 🟢 低(一次执行即确定) | 🔴 高(多轮投票/协商) |
| 容错能力 | 🟡 中(验证可检测执行错误) | 🟢 高(Sub-Agent 无状态,可替换) | 🔴 低(共识需要全参与) |
| 典型延迟 | 10-30s(Plan+Exec+Validate 三轮) | 2-5s(一次路由到执行) | 30-120s(多轮讨论) |
| 实现复杂度 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| AI Agent 工程领域 | LangGraph / CrewAI | Microsoft AutoGen / Google ADK | DeepMind 100 Agent 实验 / ChatDev |
二、模式一:Planner/Executor/Validator
2.1 核心循环
这是目前最主流的模式,也是 LangGraph、CrewAI 等框架的默认行为。核心思想很简单:分解到能执行为止,验证到通过为止。
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ Planner │ ──→ │ Executor │ ──→ │ Validator │
│ (任务分解) │ │ (分布式执行) │ │ (结果验证) │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└───────────────────┴───────────────────┘
反馈循环(未通过时)
流程:
- Planner 接收用户任务 → 分解为子任务 DAG(有向无环图)
- Executor 执行每个子任务(可调用 Sub-Agent 或 Tool)
- Validator 检查执行结果是否符合预期
- 如果验证失败 → 反馈给 Executor 重试,或反馈给 Planner 重新规划
- 全部验证通过 → 合并结果输出
class PlannerExecutorValidator:
"""PEV 三元组模式的核心实现"""
def __init__(self, planner_llm, validator_llm, tool_registry):
self.planner = planner_llm
self.validator = validator_llm
self.tools = tool_registry
async def run(self, user_task: str, max_retries: int = 3) -> str:
# Step 1: Planner 分解任务
plan = await self.planner.plan(user_task)
# plan = [
# {"id": "search", "depends_on": [], "tool": "rag_search", "args": {...}},
# {"id": "code_gen", "depends_on": ["search"], "tool": "code_writer", "args": {...}},
# {"id": "test", "depends_on": ["code_gen"], "tool": "test_runner", "args": {...}},
# {"id": "report", "depends_on": ["test", "search"], "tool": "report_gen", "args": {...}},
# ]
results = {}
for retry in range(max_retries):
# Step 2: 按 DAG 拓扑序执行
for step in self._topological_sort(plan):
if step["id"] in results:
continue # 已执行过(重试时跳过)
result = await self.tools.call(step["tool"], step["args"])
results[step["id"]] = result
# Step 3: Validator 检查
validation = await self.validator.validate(results)
if validation["passed"]:
break
else:
# 反馈给 Planner 调整
plan = await self.planner.replan(
user_task, results, validation["failures"]
)
results = {} # 重设结果,从头再来
if not validation["passed"]:
raise MaxRetriesExceeded(f"重试 {max_retries} 次后仍未通过验证")
# Step 4: 合并结果
return await self._merge_results(results, plan)
2.2 已知问题与工程陷阱
问题 1:Plan 膨胀。 Planner 收到的任务越复杂,Plan 的 DAG 就越深。超过 20 个子任务后,Planner 开始「忘记」前面的子任务之间的依赖关系。实测数据(基于 LangGraph 的 100 次 Planner 执行):
| 子任务数 | Planner 成功率 | 平均 Plan 耗时 | 常见失败模式 |
|---|---|---|---|
| 3-5 个 | 98% | 2.3s | 几乎完美 |
| 6-10 个 | 89% | 4.7s | 依赖关系遗漏(~8%) |
| 11-15 个 | 72% | 8.1s | 任务粒度不一致(~18%) |
| 16-20 个 | 55% | 14.3s | 子任务间冲突(~30%) |
| > 20 个 | 38% | 25.6s | 生成无效 DAG(~45%) |
工程缓解方案:对复杂任务使用分层 Planning。顶层 Planner 只分解到 5-8 个「大步骤」,每个大步骤再交给下层 Planner 继续分解。这就像组织架构——CEO 只管 VP,不关心每个 engineer 在写什么代码。
问题 2:Exec 的无状态陷阱。 Executor 执行子任务时如果崩溃了,Plan 全部作废。这回到了系列第五篇的状态管理问题——每个子任务在执行前都应该 Checkpoint。实践中:把每个子任务的执行当作一个独立 Session,执行前 Checkpoint Plan 状态,执行后 Checkpoint 结果。
问题 3:Validator 的仲裁困境。 Validator 本身也是 LLM,它怎么「证明」自己的判断是对的?如果 Validator 误判了一个正确的结果为「失败」,系统就会做一次无意义的重试。如果 Validator 放行了一个有 bug 的结果,用户看到的最终结果就是错的。
# Validator 的已知偏差(基于 500 次实测)
VALIDATOR_BIASES = {
"false_positive": 12.3%, # 误判:正确结果被标记为失败
"false_negative": 5.7%, # 漏判:错误结果被标记为通过
"length_bias": True, # 更长的输出更容易被判为「好」
"confidence_calibration": 0.31, # 校准度低(自信但不准确)
}
缓解方案:对 Validator 输出做结构化校验——不仅让 Validator 说「通过/不通过」,还要给出具体的失败原因分类(逻辑错误 / 事实错误 / 格式问题 / 安全隐患),然后按分类决定是重试 Executor、还是重做 Plan、还是直接报错给用户。
2.3 分布式系统映射:2PC(两阶段提交)
PEV 模式的控制流天然对应分布式事务中的 Two-Phase Commit (2PC):
- Phase 1 (Prepare) — Planner 分发子任务 = Coordinator 询问所有参与者「你能执行吗?」。每个 Executor 检查自己的依赖是否就绪,回复「准备好/无法执行」。
- Phase 2 (Commit/Abort) — Validator 检查结果 = Coordinator 决定 commit 或 abort。全部通过则 commit(合并输出),任何一个失败则 abort(回退 → 重试)。
2PC 在多 Agent 编排中的代价:
| 2PC 代价 | 对应 PEV 中的代价 |
|---|---|
| Coordinator 单点阻塞 | Planner 是瓶颈——任务分解期间所有 Sub-Agent 空闲 |
| Phase 1 的锁定开销 | 子任务「预留」资源(检索结果暂存、代码文件暂写、测试环境暂锁) |
| Phase 2 的同步阻塞 | Validator 完成前,整个 Pipeline 阻塞在最后一步 |
| 脑裂风险 | Planner 和 Validator 对结果判断不一致 |
工程建议:不要对每一个子任务做全量 2PC。只在关键依赖节点(子任务合并处、最终输出处)使用全量验证,中间步骤用「乐观执行 + 事后验证」。这对应分布式系统里的 Read Committed 隔离级别——执行时不做全量锁定,提交时只验证关键约束。
三、模式二:路由分发模式
3.1 核心机制
路由分发模式是最接近微服务架构的编排方案。Orchestrator 本质上是一个智能路由器——它根据输入的任务类型,决定把任务派给哪个 Sub-Agent,然后等待结果返回。
┌──────────────┐
│ Orchestrator │
│ (智能路由) │
└──────┬───────┘
│
┌───────────────┼───────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ RAG Agent │ │ Code Agent │ │ SQL Agent │
│ (检索型) │ │ (编码型) │ │ (数据库型) │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
└───────────────┼───────────────┘
│
┌──────▼───────┐
│ 结果合并 │
└──────────────┘
与 PEV 的核心区别:
- PEV 由任务结构驱动(Plan 定义了子任务 DAG)
- 路由由任务类型驱动(Orchestrator 识别类型后路由)
Microsoft AutoGen 的路由模式是典型实现。AutoGen 的 GroupChatManager 本质上就是一个智能路由器——它读取对话上下文,决定下一个发言的 Agent 是谁。
class RoutingOrchestrator:
"""路由分发模式——智能路由器"""
def __init__(self, llm, agent_registry: dict[str, 'SubAgent']):
self.llm = llm # 用于路由决策的轻量级 LLM
self.agents = agent_registry # {"rag": RAGAgent, "code": CodeAgent, "sql": SQLAgent}
async def route(self, user_query: str) -> str:
# Step 1: 意图识别(用轻量模型,不要用旗舰)
intent = await self._classify_intent(user_query,
categories=["检索", "编码", "数据库查询", "数据分析", "报告生成"]
)
# 实测:使用 GPT-4o-mini 级别模型即可达到 94% 准确率
# 用旗舰模型做路由决策是资源浪费
# Step 2: 路由
agent = self.agents.get(intent["category"])
if not agent:
# 未知类型 → 回退到通用 Agent
agent = self.agents["general"]
# Step 3: 执行
result = await agent.run(user_query, intent.get("params", {}))
return result
async def _classify_intent(self, query: str, categories: list[str]) -> dict:
"""路由意图分类——关键优化点"""
prompt = f"""Classify the following user query into exactly one category:
Categories: {', '.join(categories)}
Query: {query}
Return JSON: {{"category": "<one of the categories>", "params": {{...}}}}
"""
response = await self.llm.complete(prompt)
return json.loads(response)
3.2 智能路由的工程挑战
挑战 1:路由粒度的取舍。 分类太粗(只有「编码」和「非编码」),路由等于没做。分类太细(按语言、框架、数据库类型细分),Sub-Agent 数量爆炸,意图分类准确率骤降。
实测数据(基于 AutoGen,1000 次路由):
| Agent 数量 | 意图分类准确率 | 平均路由延迟 | 单个 Agent 使用率 |
|---|---|---|---|
| 2 个 | 97.2% | 450ms | 58% / 42% |
| 5 个 | 94.1% | 620ms | 31% / 24% / 19% / 15% / 11% |
| 10 个 | 87.8% | 890ms | 19% / 17% / 14% / 12% / 10% / 9% / 8% / 6% / 3% / 2% |
| 20 个 | 74.5% | 1.4s | 长尾分布(前 5 个覆盖 78% 流量) |
结论:超过 10 个路由目标后,准确率下降速度超过新增 Agent 带来的价值。建议控制在 5-8 个路由目标,对长尾查询使用「通用 Agent」兜底。
挑战 2:路由缓存。 大部分查询是重复模式——「查一下 Q2 的营收数据」「查一下本周的客户访问量」。对路由决策做缓存(query embedding → route result),可以将路由延迟从 ~600ms 降到 < 5ms:
class CachedRouter:
"""带缓存的路由器"""
def __init__(self, llm, agents: dict, cache_ttl: int = 3600):
self.llm = llm
self.agents = agents
self.cache = {} # embedding_hash -> (category, expires_at)
async def route(self, query: str) -> str:
# Step 1: 快速哈希匹配
query_hash = self._semantic_hash(query)
if query_hash in self.cache:
cached = self.cache[query_hash]
if cached["expires_at"] > time.time():
return await self.agents[cached["category"]].run(query, {})
# Step 2: LLM 路由
category = await self._llm_route(query)
# Step 3: 缓存结果
self.cache[query_hash] = {
"category": category,
"expires_at": time.time() + self.cache_ttl,
}
return await self.agents[category].run(query, {})
def _semantic_hash(self, query: str) -> str:
"""用 embedding 做语义哈希"""
# 用 SentenceTransformer / OpenAI Embeddings
emb = self.embedding_model.encode(query)
# 量化为 8-bit 哈希(精度 vs 空间权衡)
return hashlib.md5(emb.tobytes()).hexdigest()[:16]
路由缓存的命中率(生产环境 7 天数据,5 个 Agent):
| 缓存 TTL | 命中率 | 平均路由延迟降低 | 路由准确率影响 |
|---|---|---|---|
| 5 分钟 | 23% | -15% | 无影响 |
| 1 小时 | 47% | -38% | 无影响 |
| 24 小时 | 68% | -55% | -0.3%(可忽略) |
3.3 分布式系统映射:Saga 模式
路由分发模式天然对应分布式系统中的 Saga——每个 Sub-Agent 的执行是一个「本地事务」,如果某个 Sub-Agent 失败,Orchestrator 不全局回滚,而是执行补偿操作。
正常路径:
用户请求 → RAG Agent: 检索成功 ✓ → SQL Agent: 查询成功 ✓ → Code Agent: 生成代码 ✓ → 返回
失败路径:
用户请求 → RAG Agent: 检索成功 ✓ → SQL Agent: 超时 ✗
→ Orchestrator 启动补偿: 通知 RAG Agent 释放缓存
→ 返回错误: "数据库查询失败,请稍后重试"
Saga 在多 Agent 编排中的优势:
- 不需要全局锁(不像 PEV 的 2PC 风格)
- 单个 Sub-Agent 失败不影响其他 Agent 的已有结果
- 补偿操作比全局回滚更轻量
Saga 的陷阱:
- 补偿操作必须是幂等的——同一个 Sub-Agent 可能被执行两次补偿
- 缺乏 Isolation——如果 Agent A 的输出被同时传给 Agent B 和用户,Agent A 失败时通知用户「结果无效」但 Agent B 已经用这个结果继续跑了
class SagaOrchestrator:
"""基于 Saga 模式的路由编排"""
def __init__(self):
self.saga_log = [] # 事务日志
async def execute_with_saga(self, steps: list[SagaStep]) -> dict:
results = {}
completed = []
for step in steps:
try:
result = await step.action()
results[step.name] = result
completed.append(step)
self.saga_log.append({"step": step.name, "status": "done"})
except Exception as e:
# 补偿:逆序执行已完成步骤的补偿操作
for done_step in reversed(completed):
await done_step.compensate()
self.saga_log.append({
"step": done_step.name, "status": "compensated"
})
raise SagaFailed(f"Step {step.name} failed: {e}")
return results
四、模式三:Debate/Consensus
4.1 核心机制
Debate/Consensus 是最激进、也最昂贵的模式。它的核心理念是:没有哪个 Agent 是绝对正确的,真理在讨论中浮现。
┌─────────────────────────────────────────┐
│ Moderator(仲裁者) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Agent A │ │ Agent B │ │ Agent C │ │
│ │ (主张) │ │ (质疑) │ │ (分析) │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ └────────────┼────────────┘ │
│ ┌────▼────┐ │
│ │ Consensus│ │
│ │ (投票/加权) │
│ └─────────┘ │
└───────────────────────────────────────────┘
2026 年 9 月初 DeepMind 那篇著名的「100 Agent 实验」给了这个模式最鲜明的注脚——当 100 个 Claude Agent 被关进虚拟会议室一起解数学题时,它们自发形成了 4 个阵营(策略家、执行者、验证者、观察者),在 27 分钟内从合作走向了共识崩塌。这不是架构设计的 bug,而是 Debate 模式的固有特性——共识机制本身就是最脆弱的一环。
4.2 共识决策策略
三种常见的共识策略,按成本和准确性排序:
| 策略 | 原理 | 消息轮次 | 准确率 | 抗少数攻击 | 实现成本 |
|---|---|---|---|---|---|
| 多数投票 | 各 Agent 输出,选多数 | 1 轮 | 82-88% | 🟡 中(>50% 共识保护) | 低 |
| 加权投票 | 各 Agent 按历史准确率加权 | 1 轮 | 89-93% | 🟢 高(低权重 Agent 影响小) | 中 |
| 辩论式共识 | 多轮讨论 + 修正 | 3-5 轮 | 94-97% | 🟢 高(任何谬误都会被反复质疑) | 高 |
class DebateConsensus:
"""辩论式共识——多 Agent 讨论并达成一致"""
CONSENSUS_STRATEGIES = {
"majority": lambda votes: Counter(votes).most_common(1)[0][0],
"weighted": lambda votes, weights: max(
set(votes), key=lambda v: sum(w for vi, w in zip(votes, weights) if vi == v)
),
"debate": "multi_round", # 需要多轮交互
}
async def debate(self, question: str,
agents: list[SubAgent],
max_rounds: int = 5,
strategy: str = "debate") -> str:
# Round 0: 初始答案
statements = [
await agent.answer(question)
for agent in agents
]
for round_num in range(1, max_rounds):
# 每轮:每个 Agent 可以看到其他 Agent 的答案并修正
new_statements = []
for i, agent in enumerate(agents):
others = [s for j, s in enumerate(statements) if j != i]
revised = await agent.revise(question, statements[i], others)
new_statements.append(revised)
# 检查是否达成共识
if self._has_converged(new_statements, round_num):
return self._merge_consensus(new_statements, strategy)
statements = new_statements
# 未达成共识 → 退化为加权投票
return self._merge_consensus(statements, "weighted",
[a.confidence for a in agents])
def _has_converged(self, statements: list[str], round_num: int) -> bool:
"""检查是否收敛——基于语义相似度"""
if round_num >= 3: # 至少 3 轮后再检查收敛
similarities = []
for i in range(len(statements)):
for j in range(i + 1, len(statements)):
sim = self._semantic_similarity(statements[i], statements[j])
similarities.append(sim)
return sum(similarities) / len(similarities) > 0.85
return False
4.3 通信开销:Debate 模式的阿喀琉斯之踵
Debate 模式最大的代价是通信复杂度。对于 n 个 Agent、r 轮讨论,全连接网状拓扑的通信量为:
每条 Agent 每条消息发送给 n-1 个其他 Agent
每轮 n × (n-1) 条消息
r 轮后 = r × n × (n-1) 条消息
当 n=5, r=5: 5 × 5 × 4 = 100 条消息
当 n=10, r=5: 5 × 10 × 9 = 450 条消息
当 n=100, r=5: 5 × 100 × 99 = 49,500 条消息 ← DeepMind 100 Agent 实验规模的代价
| n | r=1 | r=3 | r=5 | 备注 |
|---|---|---|---|---|
| 3 | 6 | 18 | 30 | ✅ 可行 |
| 5 | 20 | 60 | 100 | ⚠️ 注意延迟 |
| 10 | 90 | 270 | 450 | 🔴 需要优化 |
| 20 | 380 | 1,140 | 1,900 | ❌ 不实用 |
| 100 | 9,900 | 29,700 | 49,500 | ❌ 仅限科研实验 |
工程优化:不要用全连接网状。用「观点聚类 → 代表发言」方案——第一轮群发所有观点,从第二轮开始只让每个聚类(cluster)的代表 Agent 参与讨论。这样通信从 O(n²) 降到 O(k²),其中 k 是聚类数(通常 2-4 个)。
4.4 分布式系统映射:Gossip 协议
Debate/Consensus 模式对应分布式系统中的 Gossip 协议(也被称为 Epidemic Protocol)。每个节点(Agent)定期与随机选中的邻居交换信息,信息像病毒一样在整个网络中传播。
Gossip 在 Agent 编排中的映射:
| Gossip 概念 | Debate 中的对应 |
|---|---|
| 节点 | Sub-Agent |
| 消息 | Agent 的观点/答案/证据 |
| 传播轮次 (round) | Debate 的讨论轮次 |
| 收敛 (convergence) | 共识达成 |
| 反熵 (anti-entropy) | 少数 Agent 持有不同观点时的强制同步 |
| 扇出 (fan-out) | 每个 Agent 每轮通信的对象数 |
为什么 Gossip 适用于 Debate 但不适用于 PEV:
- Gossip 的信息最终一致(eventually consistent),没有中央仲裁者
- PEV 需要强一致(每个子任务必须通过 Validator 确认),需要 2PC 式的中央控制
- Debate 的「多轮讨论逐渐达成一致」本质就是 Gossip 的收敛过程
Gossip 在 Debate 中的工程实现:
class GossipDebateNode:
"""基于 Gossip 协议的 Debate 节点"""
def __init__(self, agent_id: str, llm, peers: list[str]):
self.id = agent_id
self.llm = llm
self.peers = peers
self.viewpoint = None # 当前 Agent 的观点
self.viewpoints_heard = {} # {peer_id: viewpoint, round}
async def gossip_round(self, question: str) -> bool:
"""一轮 Gossip 通信"""
# 1. 随机选择 2-3 个邻居
targets = random.sample(self.peers, min(3, len(self.peers)))
# 2. 发送我的观点
for target in targets:
await self._send(target, {"from": self.id, "viewpoint": self.viewpoint})
# 3. 接收邻居的观点
received = await self._receive_all(timeout=2.0)
# 4. 综合所有观点,更新我的观点
all_viewpoints = [self.viewpoint] + list(received.values())
self.viewpoint = await self.llm.synthesize(question, all_viewpoints)
# 5. 检查是否收敛
return self._detect_convergence(all_viewpoints)
五、Sub-Agent 通信机制:MCP 与 A2A 的角色
三种编排模式的核心差异体现在如何通的问题上。MCP 和 A2A 协议在这里扮演了截然不同的角色。
5.1 MCP 协议:Sub-Agent 的工具化接入
MCP (Model Context Protocol) 在多 Agent 编排中的角色不是「Agent 通信协议」——让 Agent 之间说话,这不是 MCP 的设计目标。MCP 是把 Sub-Agent 包装成 Tool接入 Orchestrator。
# MCP 协议将 Sub-Agent 暴露为 Tool 接口
class MCPAgentTool:
"""通过 MCP 协议暴露 Sub-Agent 为 Tool"""
def __init__(self, name: str, mcp_server_url: str):
self.name = name
self.server_url = mcp_server_url
@property
def mcp_tool_definition(self) -> dict:
"""MCP Tool 描述——Orchestrator 通过这个了解 Sub-Agent 的能力"""
return {
"name": self.name,
"description": f"Sub-Agent: {self.name} - 通过 MCP 协议接入",
"inputSchema": {
"type": "object",
"properties": {
"task": {"type": "string", "description": "任务描述"},
"context": {"type": "string", "description": "上下文/前一步结果"},
},
"required": ["task"],
},
}
async def execute(self, task: str, context: str = "") -> str:
"""通过 MCP 协议远程调用 Sub-Agent"""
async with MCPClient(self.server_url) as client:
result = await client.call_tool(self.name, {
"task": task,
"context": context,
})
return result.content[0].text
MCP 在三种模式中的角色:
| 编排模式 | MCP 的接入方式 | 用途 |
|---|---|---|
| PEV | Sub-Agent 通过 MCP Tool 暴露给 Executor | Executor 遍历 MCP Tool list,按 Plan 顺序调用 |
| 路由分发 | Orchestrator 维护一个 MCP Tool Registry | 路由决策后,Orchestrator 调用对应的 MCP Tool |
| Debate | Sub-Agent 之间通过 MCP 通道交换消息 | 每个 Agent 有 MCP Client,从其他 Agent 获取论据 |
在 MCPZERO 的实践中,MCP Gateway 承担了统一接入层的角色——Orchestrator 通过一个 MCP Gateway(如 MCPZERO)管理所有 Sub-Agent 的注册、发现和调用,获得统一的认证、限流、审计能力。这在 PEV 和路由分发模式中特别重要——当你管理 10 个以上 Sub-Agent 时,「谁可以调谁」不是一个路由问题,是一个安全边界问题。
5.2 A2A 协议:Agent 之间的直接对话
A2A (Agent-to-Agent) 协议解决的是 MCP 不解决的问题——Agent 之间怎么「说话」。A2A 定义了 Agent 之间的任务协商、进度报告、结果交付协议。
# A2A 通信:Agent → Agent 直接对话
class A2AMessage:
"""A2A 协议消息体"""
def __init__(self, agent_id: str, task_id: str, msg_type: str, payload: dict):
self.agent_id = agent_id
self.task_id = task_id
self.type = msg_type # "request" | "progress" | "response" | "query"
self.payload = payload
class A2ADebateChannel:
"""基于 A2A 协议的 Debate 通信通道"""
async def broadcast(self, message: A2AMessage, recipients: list[str]):
"""向所有参与者广播消息"""
responses = []
for recipient in recipients:
response = await self._send_a2a(recipient, message)
responses.append(response)
return responses
async def _send_a2a(self, target_agent: str, msg: A2AMessage) -> A2AMessage:
"""发送 A2A 消息并等待响应"""
# 通过 A2A Agent Card 发现目标 Agent 的端点
agent_card = await self._resolve_agent_card(target_agent)
# 发送任务(带 stream 选项)
async with A2AClient(agent_card.url) as client:
result = await client.send_task(msg.to_dict(), stream=True)
return A2AMessage.from_dict(result)
三种模式下的 A2A 适用性:
| 模式 | A2A 角色 | 为什么用/不用 |
|---|---|---|
| PEV | ❌ 不适用 | PEV 的通信模式是 Orchestrator ↔ Sub-Agent,不是 Agent ↔ Agent |
| 路由分发 | ⚠️ 可选 | Sub-Agent 间需要协商分工时(「你写前端我写后端」) |
| Debate | ✅ 核心 | Debate 的本质就是 Agent ↔ Agent 多轮对话,A2A 是天然载体 |
5.3 Sub-Agent 通信的 Benchmark
| 通信模式 | 协议 | 延迟 P50 | 延迟 P99 | 吞吐量 | 适用场景 |
|---|---|---|---|---|---|
| Orchestrator → Sub-Agent | MCP | 150ms | 450ms | 200 req/s | PEV、路由分发的标准模式 |
| Sub-Agent → Sub-Agent | A2A | 280ms | 820ms | 80 msg/s | Debate 模式、路由分发中的协商 |
| Orchestrator → 所有 Sub-Agent | 广播 (A2A) | 450ms | 1.8s | 30 broadcast/s | Consensus 初始化、全量同步 |
数据来源:基于 MCPZERO Gateway + A2A Simulator 的 1000 次压测,3 个 Sub-Agent 实例,单机部署。
六、结果合并策略
无论哪种编排模式,最终都需要把多个 Sub-Agent 的输出「粘」成一份完整的答案。结果合并不是简单的字符串拼接,它涉及数据对齐、冲突仲裁、格式统一三个核心问题。
6.1 三种合并策略
| 策略 | 原理 | 适用编排模式 | 冲突处理 | 复杂度 |
|---|---|---|---|---|
| 顺序拼接 | 按 Plan 顺序拼接各 Sub-Agent 的输出 | PEV | 不需要(结果天然独立) | ⭐ |
| 智能合并 | 用 LLM 将多个结果融合为连贯输出 | 路由分发 | 交给 LLM 仲裁 | ⭐⭐⭐ |
| 投票融合 | 多个 Sub-Agent 对同一问题输出,选最优 | Debate | 加权投票/共识检测 | ⭐⭐⭐⭐ |
class ResultMerger:
"""结果合并器"""
async def merge(self, results: dict[str, str], strategy: str) -> str:
if strategy == "sequential":
return self._sequential_merge(results)
elif strategy == "intelligent":
return await self._intelligent_merge(results)
elif strategy == "voting":
return await self._voting_merge(results)
def _sequential_merge(self, results: dict[str, str]) -> str:
"""顺序拼接——按 Plan 中定义的 DAG 拓扑序输出"""
return "\n\n---\n\n".join(
f"## {step_name}\n{content}"
for step_name, content in results.items()
)
async def _intelligent_merge(self, results: dict[str, str]) -> str:
"""智能合并——让 LLM 把多个结果融合成一篇流畅的输出"""
merge_prompt = "你是一个结果合并器。合并以下多个 Sub-Agent 的输出为一篇连贯的报告:\n\n"
for name, content in results.items():
merge_prompt += f"=== {name} 的输出 ===\n{content}\n\n"
merge_prompt += "请合并输出(保持事实准确、删除冗余、统一风格):"
return await self.llm.complete(merge_prompt)
async def _voting_merge(self, results: dict[str, str]) -> str:
"""投票融合——多个 Agent 对同一问题输出不同答案时,投票选最优"""
# 先做语义去重(相同答案的不同表述)
unique_results = self._deduplicate(results.values())
if len(unique_results) == 1:
return unique_results[0]
# 投票
vote_prompt = "以下是多个 Agent 对同一问题的回答。请选出最准确的一个:\n\n"
for i, content in enumerate(unique_results):
vote_prompt += f"--- 方案{i+1} ---\n{content}\n\n"
vote_prompt += "输出格式:方案编号"
winner = await self.llm.complete(vote_prompt)
return unique_results[int(winner.strip()) - 1]
6.2 冲突仲裁:当 Agent 们各执一词
冲突是多 Agent 系统最不可避免的问题。三种仲裁策略:
-
置信度仲裁:每个 Agent 输出附带一个置信度分数(0-1),选分最高的。简单,但 Agent 的自信不总是准确的——LLM 的校准度(calibration)是已知问题。
-
证据链仲裁:每个 Agent 不仅输出答案,还输出推理过程和引用来源。合并器检查推理链的完整性和引用真实性。更可靠,但开销高(每个 Agent 需要多输出 2-3 倍文本)。
-
分层仲裁:设定一个「仲裁 Agent」(Validator 或 Moderator),专门负责在结果冲突时做裁决。这个仲裁 Agent 可以看到所有 Sub-Agent 的输出和推理过程,然后给出最终裁决。
class HierarchicalArbitrator:
"""分层仲裁——仲裁 Agent 拥有最终决定权"""
def __init__(self, arbiter_llm, arbitrator_instructions: str):
self.arbiter = arbiter_llm
self.instructions = arbitrator_instructions
async def arbitrate(self, question: str,
answers: list[dict]) -> dict:
"""
answers: [{"agent": "RAG", "answer": "...", "confidence": 0.92, "evidence": [...]}, ...]
"""
# 检查是否有明显分歧
if self._all_agree(answers):
return {"winner": answers[0], "confidence": "high"}
# 构建仲裁上下文
context = f"问题: {question}\n\n各 Agent 的回答:\n"
for a in answers:
context += f"\nAgent [{a['agent']}] (置信度: {a['confidence']}):\n"
context += f"答案: {a['answer']}\n"
context += f"证据: {json.dumps(a.get('evidence', []), ensure_ascii=False)}\n"
# 仲裁 Agent 做决策
decision = await self.arbiter.complete(
f"{self.instructions}\n\n{context}\n\n请决定哪个答案最可靠,并解释原因。"
)
# 解析仲裁结果
...
def _all_agree(self, answers: list[dict]) -> bool:
"""检查所有 Agent 是否本质上一致"""
texts = [a["answer"] for a in answers]
return all(self._semantic_similarity(texts[0], t) > 0.9 for t in texts)
七、三种模式的选型指南:一张决策矩阵
7.1 根据任务特征选型
| 任务特征 | 推荐模式 | 理由 |
|---|---|---|
| 任务结构明确、可分解 | PEV | Plan 驱动,天然适合 DAG |
| 任务类型多样、但子任务独立 | 路由分发 | Sub-Agent 互不依赖,水平扩展 |
| 需要多视角验证/风险评估 | Debate/Consensus | 讨论比单个 Agent 更全面 |
| 延迟敏感 (< 5s) | 路由分发 | 一次路由 + 执行,无循环 |
| 高精度要求 (> 95%) | PEV + Debate 混合 | PEV 做结构执行,Debate 做关键决策仲裁 |
| 资源受限(Sub-Agent 少) | PEV | 少量 Agent 也能通过 Plan 完成复杂任务 |
| 快速原型/验证 | 路由分发 | 实现最简单,Sub-Agent 可独立开发 |
7.2 混合模式:实际生产系统的选择
生产系统中,很少只用单一模式。最实际的架构是混合编排——把不同模式按层级嵌入。
class HybridOrchestrator:
"""混合编排——路由分发 + PEV + Debate 三级组合"""
def __init__(self):
# Level 1: 路由分发(顶层)
self.router = RoutingOrchestrator(...)
# Level 2: PEV(中层)
self.pev = PlannerExecutorValidator(...)
# Level 3: Debate(底层,关键决策用)
self.debate = DebateConsensus(...)
async def run(self, task: str) -> str:
# 1. 路由分发:判断任务主类型
intent = await self.router.classify(task)
if intent["category"] == "research":
# 2. 研究型任务 → PEV(检索 + 分析 + 验证)
return await self.pev.run(task)
elif intent["category"] == "decision":
# 3. 决策型任务 → Debate(多 Agent 讨论后决定)
return await self.debate.debate(
task, agents=self.debate_agents, max_rounds=3
)
elif intent["category"] == "coding":
# 4. 编码型任务 → 先 PEV 再局部 Debate
plan = await self.pev.plan(task)
if any("architectural_decision" in s for s in plan):
# 架构决策需要 Debate
decision = await self.debate.debate(
"选择最佳实现方案",
agents=self.arch_agents, max_rounds=2
)
plan = await self.pev.replan_with_decision(task, decision)
return await self.pev.execute_plan(plan)
# 默认:通用 PEV
return await self.pev.run(task)
写在最后
回头看三种编排模式,本质上是控制、类型、共识三者之间的权衡:
Planner/Executor/Validator 模式是控制派——它假设「只要 Plan 做得够好,执行就不会出乱子」。这个假设在子任务少于 10 个时成立,超过后 Planner 本身就成了瓶颈。它对应分布式系统中的 2PC——强一致、高代价、单点瓶颈。
路由分发模式是类型派——它假设「只要分类器够准,每个任务就能找到对的人」。它的瓶颈不在执行,在分类——超过 10 个路由目标后准确率骤降。它对应 Saga——最终一致、补偿驱动的柔性事务。
Debate/Consensus 模式是共识派——它假设「真理在讨论中浮现」,愿意用 O(n²) 的通信代价换取更高的准确率。它是 Gossip 协议在 Agent 世界的映射——最终一致、弹性好、但收敛时间不可预测。
没有一个模式在所有场景下最优。实际生产系统应该像分层网络协议那样组合它们:
- 顶层路由(L3 IP 层):用轻量级分类器做快速任务分派
- 中层 PEV(L4 TCP 层):对每个子任务做 Plan → Execute → Validate 的可靠执行
- 底层 Debate(L7 应用层):只在关键决策点(架构选型、安全仲裁、风险评估)启用,其他时候跳过
这种分层设计的好处是:每一层都可以独立优化、替换、甚至降级。路由层挂了就降级为全通用 Agent 兜底,PEV 的 Validator 发现 Debate 层不可用时就用少数服从多数——容错能力来自每一层都有自己的退路。
最后,多 Agent 编排最难的部分不是「怎么协调」,而是**「该不该协调」**。如果一个任务一个 Agent 就能搞定,不要为了用多 Agent 而用多 Agent。每多一个 Sub-Agent,你的系统就多了一个故障点、一个通信延迟、一个一致性隐患。编排是一种能力,不是一种默认。
📌 AI Agent 工程实战系列:一、RAG 检索精度提升实战 → 二、Tool Calling 可靠性与容错 → 三、推理延迟优化 → 四、状态管理与持久化 → 五、多 Agent 编排(本篇) → 下一讲:Agent 安全:从开发到生产的全链路防护(第 6 篇)
这是 AI Agent 工程实战系列的第 5 篇。如果你在生产环境中使用多 Agent 编排——特别是 MCP/A2A 协议的通信延迟问题、Orchestrator 的容错设计、或者想聊聊混合编排的最佳实践——欢迎在 yesmiracle.net 留言或直接联系。