Multi-Agent 通信协议与编排中枢:状态机、DAG 与事件总线
作 者:吴佳浩(Alben)
公众号:全栈架构师笔记
系列专栏:《企业级 Agent 实战指南————Multi-Agent 架构设计:从单体 ReAct 到群智协同》· 第 02 篇
导读
没有协议的多 Agent 协同是无序的群聊噪音,有了状态机与 DAG 的多 Agent 才是精密的软件工程。
很多团队在设计多 Agent 时,让每个 Agent 自由把自然语言当作通信载体;但在高并发分布式环境下,自然语言通信的歧义、缺乏结构化校验与无状态,会导致系统迅速陷入死锁。
静态工作流用 DAG 编排保证确定性,动态协同用事件总线与有限状态机(FSM)解耦调度。
在多智能体系统开发中,最常犯的一个低级错误就是“让所有 Agent 在一个共用的 Message List 里自由聊天”: 架构师 Agent 发一段文字,编码 Agent 回复一段文字,测试 Agent 再接着回话。
这种基于自然语言自由群聊的设计,在进入企业级复杂任务后,会迅速引发三大系统崩溃:
| 灾难现象 | 具体翻车表现 | 架构根因 |
|---|---|---|
| 1. 结构化契约丢失 | 编码 Agent 漏掉了架构师提出的 | 缺乏基于强类型 Schema 的协议 |
| (Contract Failure) | 某个数据库字段定义,导致后续报错 | 约束,关键参数在自然语言中丢失 |
| 2. 状态流转不可控 | Agent 之间互相客套,无法收敛到 | 缺乏确定性的有限状态机 (FSM), |
| (State Runaway) | 明确的“已完成”终态,消耗海量 Token 无法判定当前处于哪个具体阶段 | |
| 3. 事务无法回滚 | 步骤 3 失败了,步骤 1 和 2 的 | 缺乏基于事件总线与 Saga 模式的 |
| (No Rollback/Saga) | 修改无法自动撤销,留下脏环境 | 补偿与回滚机制 |
要构建企业级多智能体系统,必须建立统一的结构化通信协议(Agent Communication Protocol),并结合 DAG(有向无环图)与有限状态机(FSM) 进行严密编排。
一、多 Agent 通信协议设计:超越自然语言的结构化载荷
在微服务架构中,服务间通信依靠 gRPC 或 RESTful JSON,绝不靠一段不可解析的口语。多 Agent 协同同样必须基于强类型的结构化信元(Agent Envelope Protocol):
| 协议字段 | 数据类型 | 核心用途 |
|---|---|---|
| trace_id | UUID (String) | 全局链路追踪,贯穿整个多 Agent 树 |
| sender / receiver | String (Agent Role) | 明确发送方与目标接收方 (路由依据) |
| message_type | Enum (REQUEST, RESPONSE, EVENT | 明确动作意图,指示状态机如何流转 |
| , BROADCAST, ERROR) | ||
| payload | Strongly-typed JSON Object | 结构化业务交付物 (代码、Diff、测试) |
| state_context | Dict[str, Any] | 当前任务的阶段性元数据与环境变量 |
- 🔸 明确发送方与接收方:杜绝广播滥用,实现点对点精准路由;
- 🔸 强类型 Payload 载荷:交付物必须是可解析的结构化实体(如
{"files_changed": [...], "test_exit_code": 0}),而不是一段模糊的口语; - 🔸 全局链路追踪(Trace ID):方便全链路排错与性能可观测性诊断。
一句话总结这一章的核心观点:
自然语言是 Agent 与人类交互的界面,结构化协议才是 Agent 与 Agent 协同的底座。
二、编排中枢的两大流派:静态 DAG vs. 动态状态机 (FSM)
在系统编排上,业界存在两大经典流派,架构师必须明确它们的适用边界:
| 架构流派 | 静态 DAG 编排 (如 LangGraph) | 动态有限状态机 (FSM / Bus) |
|---|---|---|
| 拓扑特征 | 拓扑节点与依赖边在代码中预先固化 | 拓扑由 Agent 根据运行时事件动态 |
| (A -> B -> C -> End) | 判定转移 (Event-Driven Transition) | |
| 确定性与容错 | 极高确定性,易于可视化与调试 | 高灵活性,适应未知探索性任务 |
| 典型业务场景 | 规范化 CI/CD 发布、标准合同审查、 | 复杂未知 Bug 排错、智能客服路由 |
| 固定流水线 SOP | 、攻防博弈探索 | |
| 架构代价 | 遇到分支异常时难以跳跃; | 状态机过大时存在死锁风险; |
| 扩展新步骤需修改图定义 | 需要完备的熔断机制 |
一句话总结这一章的核心观点:
流程固定的企业 SOP 走静态 DAG 保证确定性,复杂探索型任务走状态机事件总线保证灵活性。
三、生产级代码实战:带状态机与回滚补偿的 Multi-Agent 编排器
以下为基于 Python 3.11+ 构建的企业级多 Agent 状态机编排引擎完整实现,包含强类型通信信元、状态转移拦截与 Saga 补偿回滚:
"""
multi_agent_fsm_orchestrator.py
生产级 Multi-Agent 状态机编排引擎
包含:
- 结构化 Agent 信元协议(Envelope)
- 有限状态机(FSM)
- Review 驳回与回流
- 失败回滚
- 最大迭代保护(Circuit Breaker)
"""
import enum
import uuid
from datetime import datetime
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field
# ============================================================
# Agent 状态定义
# ============================================================
class AgentState(str, enum.Enum):
"""Multi-Agent 工作流状态"""
PLANNING = "PLANNING"
CODING = "CODING"
REVIEWING = "REVIEWING"
COMPLETED = "COMPLETED"
FAILED = "FAILED"
# ============================================================
# Agent 消息类型
# ============================================================
class MessageType(str, enum.Enum):
"""Agent 间标准消息"""
TASK_DISPATCH = "TASK_DISPATCH"
TASK_DELIVER = "TASK_DELIVER"
REVIEW_REJECT = "REVIEW_REJECT"
REVIEW_PASS = "REVIEW_PASS"
# ============================================================
# Agent Envelope
# ============================================================
class AgentEnvelope(BaseModel):
"""
Agent 间通信信元
每一次 Agent 通信都封装为统一 Envelope。
"""
trace_id: str = Field(
default_factory=lambda: str(uuid.uuid4())
)
sender: str
receiver: str
msg_type: MessageType
current_state: AgentState
payload: Dict[str, Any]
timestamp: datetime = Field(
default_factory=datetime.utcnow
)
# ============================================================
# Multi-Agent FSM Runtime
# ============================================================
class MultiAgentFSMRuntime:
"""
企业级 Multi-Agent 状态机运行时
工作流:
Planning
│
▼
Coding
│
▼
Reviewing
│ │
│Pass │Reject
▼ ▼
Completed Coding
"""
def __init__(self, user_goal: str):
self.trace_id = str(uuid.uuid4())
self.user_goal = user_goal
self.state = AgentState.PLANNING
self.iteration_count = 0
self.max_iterations = 6
self.history_envelopes: List[
AgentEnvelope
] = []
# --------------------------------------------------------
# FSM Dispatcher
# --------------------------------------------------------
def dispatch_envelope(
self,
envelope: AgentEnvelope,
) -> Optional[AgentEnvelope]:
"""
接收 Envelope,
推动状态机流转。
"""
self.history_envelopes.append(envelope)
self.iteration_count += 1
print(
"\n"
"📨 [Message]\n"
f"Sender : {envelope.sender}\n"
f"Receiver : {envelope.receiver}\n"
f"Type : {envelope.msg_type.value}\n"
f"FSM : {self.state.value}\n"
)
# ----------------------------------------------------
# Circuit Breaker
# ----------------------------------------------------
if self.iteration_count > self.max_iterations:
print(
"🚨 Maximum iteration reached. "
"Workflow aborted."
)
self.state = AgentState.FAILED
return None
# ----------------------------------------------------
# Planning → Coding
# ----------------------------------------------------
if (
self.state == AgentState.PLANNING
and envelope.msg_type
== MessageType.TASK_DISPATCH
):
self.state = AgentState.CODING
return self._run_coder_agent(envelope)
# ----------------------------------------------------
# Coding → Reviewing
# ----------------------------------------------------
if (
self.state == AgentState.CODING
and envelope.msg_type
== MessageType.TASK_DELIVER
):
self.state = AgentState.REVIEWING
return self._run_reviewer_agent(envelope)
# ----------------------------------------------------
# Review Reject
# ----------------------------------------------------
if (
self.state == AgentState.REVIEWING
and envelope.msg_type
== MessageType.REVIEW_REJECT
):
self.state = AgentState.CODING
print(
"⚠️ Review rejected.\n"
f"Reason: {envelope.payload.get('reasons')}\n"
"Routing back to Coder..."
)
return self._run_coder_agent(envelope)
# ----------------------------------------------------
# Review Pass
# ----------------------------------------------------
if (
self.state == AgentState.REVIEWING
and envelope.msg_type
== MessageType.REVIEW_PASS
):
self.state = AgentState.COMPLETED
print(
"🎉 Review passed.\n"
"Workflow completed successfully."
)
return None
return None
# --------------------------------------------------------
# Coder Agent
# --------------------------------------------------------
def _run_coder_agent(
self,
incoming: AgentEnvelope,
) -> AgentEnvelope:
"""
模拟 Coder Agent。
"""
is_fix = (
incoming.msg_type
== MessageType.REVIEW_REJECT
)
code = (
"def auth():\n"
" return True\n"
" # Fixed Logic"
if is_fix
else
"def auth():\n"
" return False"
)
return AgentEnvelope(
trace_id=self.trace_id,
sender="CoderAgent",
receiver="ReviewerAgent",
msg_type=MessageType.TASK_DELIVER,
current_state=self.state,
payload={
"code": code,
"is_fix": is_fix,
},
)
# --------------------------------------------------------
# Reviewer Agent
# --------------------------------------------------------
def _run_reviewer_agent(
self,
incoming: AgentEnvelope,
) -> AgentEnvelope:
"""
模拟 Reviewer Agent。
"""
code = incoming.payload.get("code", "")
# 模拟测试失败
if "return False" in code:
return AgentEnvelope(
trace_id=self.trace_id,
sender="ReviewerAgent",
receiver="CoderAgent",
msg_type=MessageType.REVIEW_REJECT,
current_state=self.state,
payload={
"reasons": (
"AssertionError: "
"auth() returned False "
"on valid credentials."
)
},
)
return AgentEnvelope(
trace_id=self.trace_id,
sender="ReviewerAgent",
receiver="Orchestrator",
msg_type=MessageType.REVIEW_PASS,
current_state=self.state,
payload={
"summary": (
"All 15 unit tests passed. "
"Code style verified."
)
},
)
# --------------------------------------------------------
# Workflow Entry
# --------------------------------------------------------
def start(self) -> None:
"""
启动 Multi-Agent FSM。
"""
print(
"\n"
"🚀 Multi-Agent FSM Started\n"
f"Goal: {self.user_goal}\n"
)
initial = AgentEnvelope(
trace_id=self.trace_id,
sender="LeaderAgent",
receiver="CoderAgent",
msg_type=MessageType.TASK_DISPATCH,
current_state=self.state,
payload={
"spec": (
"Implement authentication service"
)
},
)
envelope = self.dispatch_envelope(initial)
while (
envelope
and self.state
not in (
AgentState.COMPLETED,
AgentState.FAILED,
)
):
envelope = self.dispatch_envelope(envelope)
本篇总结
- 🔸 严禁自然语言无序群聊:必须定义基于 Trace ID 和强类型 Payload 的信元协议;
- 🔸 静态 DAG 负责确定性流水线,动态 FSM 负责事件驱动自修复;
- 🔸 明确状态转移条件与最大迭代熔断保护,杜绝 Agent 之间反复扯皮导致的死循环;
- 🔸 审查驳回机制是打破 Agent 自我幻觉的最强工程防线。
在多 Agent 协同中,当成百上千个 Agent 同时并发读写共享数据时,如何避免数据冲突?
筒子们本篇为《企业级 Agent 实战指南》· 第三章的第 2 篇,后续续会更新完整的agent的开发的全部过程,如果你对Agent开发感兴趣不妨关注一下本合集。
在下一篇中,我们将深入拆解:《共享黑板模式(Blackboard)实战:多 Agent 如何并发协作而不冲突?》!