多智能体系统是一种运行时架构,不是 Prompt 写作技巧。它能隔离权限、并行执行独立研究,也能让由不同团队负责的能力协同;代价则是更多消息、模型调用、部分失败、重复上下文,以及新的提示词注入传播路径。
实用原则可以浓缩为一句话:
先用单 Agent 和确定性工作流解决问题。只有能明确说出新增 Agent 消除了哪个瓶颈,并定义证明收益的指标时,才增加 Agent。
核心结论
- 多步骤不等于多 Agent,稳定顺序应由普通工作流代码执行。
- 一条 Agent 边界应带来隔离、并行或独立归属中的至少一项收益。
- 将控制面与 Worker 执行分开,不能让 Worker 自行发明拓扑或权限。
- Role Prompt 描述行为,凭证和 Tool Allowlist 才真正限制能力。
- 传递带 Provenance 的类型化 Artifact,不要转发整段会话。
- 每个 Worker 与整个 Run 都必须有硬预算。
- 即使输出来自自有模型,也要按不可信输入处理。
- 上线前必须与单 Agent 基线比较。
先做拆分决策
至少有一个条件产生实质影响时,多 Agent 才可能值得:
| 条件 | 新增 Agent 的收益 | 必须提供的证据 |
|---|---|---|
| 独立子任务主导延迟 | Worker 可以真正并发 | 端到端 p95 降低,且成本可接受 |
| 上下文互相干扰 | 专家只接收更小、更干净的上下文 | 质量优于“单 Agent 加检索”基线 |
| 权限不同 | 每个 Worker 获得更窄的能力集 | Policy 测试证明跨边界操作被拒绝 |
| 归属不同 | 团队可以独立版本化和评测组件 | 契约、SLO 与升级负责人明确 |
| 独立批判有价值 | Reviewer 能挑战 Producer | 留出集上的缺陷召回率提高 |
以下情况通常不应拆分:
- 流程只是固定的
提取 -> 校验 -> 存储; - Worker 需要几乎相同的上下文和工具;
- 所有结果最终仍受同一个串行瓶颈限制;
- 任务很短,协调开销大于执行开销;
- 没有团队负责跨 Agent 故障。
做一次消融实验:使用相同模型、工具和任务集,分别实现单 Agent 与多 Agent。比较任务成功率、p50/p95 延迟、Token、Tool Call、策略违规和人工处置成本。
架构模式
管理者—执行者
Manager 分解工作、分配有边界的任务并聚合结果。它适合并行研究和专家路由,但 Manager 也会成为性能瓶颈与高价值攻击目标。
请求 -> Manager -> [Worker A, Worker B, Worker C] -> Verifier -> 响应
Manager 应发出明确 Assignment,而不是转发模糊对话。Worker 返回证据和状态,不负责决定另一个 Worker 能否访问受保护 Tool。
对等 Handoff
一个 Agent 根据路由规则把控制权转移给另一个 Agent。它适合客服或分诊场景,由下一位专家负责后续阶段。
分诊 -> 账单
-> 账号恢复
-> 技术支持
Handoff 必须携带最小类型化状态和转移原因。系统应限制 Handoff 次数,并拒绝 分诊 -> 账单 -> 分诊 这类循环。
共享 Blackboard
Worker 在共享存储中读写带版本的 Artifact。它适合异步或长任务,但必须实现 Compare-and-Set、所有权规则与冲突解决。Blackboard 是数据库,不是无结构的 Prompt Transcript。
辩论或投票
多个 Worker 提出答案,由 Judge 选择或综合。当错误具有足够独立性时,这种模式才可能提高覆盖率。使用同一模型、近似 Prompt 与相同来源,通常只会产生相关错误,而不是独立证据。
控制面与数据面
编排逻辑应保持确定性:
控制面
任务分解、路由、预算、鉴权、重试、取消、
Checkpoint、人工批准、审计
Worker 数据面
模型推理、受限检索、已批准的 Tool Call、
类型化 Result 与 Provenance
控制面负责:
- Run ID、任务图与允许的状态转移;
- Principal 与 Tenant 身份;
- 每个 Worker 的 Tool 和数据范围;
- 重试与超时策略;
- 全局 Token、成本和墙钟预算;
- 高影响操作的人工确认;
completed、failed、cancelled、needs_review等终态。
Worker 不能给自己授予 Tool、创建无限子任务,也不能在缺乏 Executor 证据时声称业务操作成功。
消息契约
一条可运营的工作消息应包含:
{
"schema_version": "1",
"run_id": "run-42",
"task_id": "research-security",
"attempt": 1,
"sender": "manager",
"recipient": "security-researcher",
"deadline": "2026-10-04T12:00:00Z",
"input_refs": ["artifact://brief/17"],
"required_output": "evidence_bundle.v1"
}
对应 Result 应包含状态、Evidence Reference、必要的置信度或不确定性,以及大小受限的摘要。大型文档存入 Artifact Store,只传引用。这样既能降低上下文成本,又能保留 Provenance,避免反复复制不可信内容。
还要定义消息投递语义:
- 使用稳定的 Task ID 和 Attempt ID;
- 让写入具备幂等性;
- Queue 可能重复投递,必须去重;
- 区分可重试的基础设施故障与无效工作;
- 取消或任务被替代后,忽略迟到结果;
- 不能根据模型生成内容中的 Sender 字符串推导身份。
框架选型
不同框架编码了不同的控制模型。固定版本前应核对当前 Release 文档与迁移说明。
| 方案 | 控制模型 | 适用场景 | 主要风险 |
|---|---|---|---|
| CrewAI | Crew、Agent、Task、Process | 快速构建角色化工作流原型 | Persona 抽象可能遮蔽状态与 Policy 边界 |
| AutoGen AgentChat | Team 与对话式 Agent | 多轮协作与实验 | 跨大版本 API 和架构变化,需要谨慎固定版本 |
| LangGraph | 显式状态图、Checkpoint、Interrupt | 持久工作流与人工审核 | 设计成本更高,Graph State 仍需应用层安全 |
| OpenAI Agents SDK | Agent、Tool、Handoff、Guardrail、Session、Trace | 轻量 Handoff 与供应商集成 Trace | 供应商耦合,运行时 Policy 仍由应用负责 |
| 自研 Coordinator | 自有 Queue、状态机与模型 Adapter | 严格契约、接入已有平台 | 调度、持久化、Telemetry 与一致性全部自负 |
Microsoft 当前 AutoGen 文档区分 AgentChat 与旧 pyautogen/Classic API。不要把经典 autogen.GroupChat 示例直接用于新系统,必须先选择目标代际。OpenAI Swarm 是教育实验,新项目应使用持续维护的 Agents SDK 或其他受支持 Runtime。
可运行的 Go Coordinator
下面的标准库程序演示框架无法替你决定的部分:有界并行 Assignment、最小权限 Tool 检查、类型化 Result、取消和确定性聚合。可用真实模型 Adapter 替换 mockRunner,同时保留这份契约。
package main
import (
"context"
"errors"
"fmt"
"sort"
"sync"
"time"
)
type Assignment struct {
ID string
Brief string
RequiredTools []string
}
type Evidence struct {
ID string
Source string
Summary string
}
type Result struct {
TaskID string
AgentID string
Evidence []Evidence
Err error
}
type Agent struct {
ID string
AllowedTools map[string]bool
Run func(context.Context, Assignment) ([]Evidence, error)
}
type WorkItem struct {
Agent Agent
Assignment Assignment
}
func (a Agent) Execute(
ctx context.Context,
assignment Assignment,
) Result {
for _, tool := range assignment.RequiredTools {
if !a.AllowedTools[tool] {
return Result{
TaskID: assignment.ID,
AgentID: a.ID,
Err: fmt.Errorf("tool %q is not allowed", tool),
}
}
}
evidence, err := a.Run(ctx, assignment)
return Result{
TaskID: assignment.ID,
AgentID: a.ID,
Evidence: evidence,
Err: err,
}
}
func runParallel(
ctx context.Context,
work []WorkItem,
) ([]Result, error) {
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
results := make(chan Result, len(work))
var waitGroup sync.WaitGroup
for _, item := range work {
waitGroup.Add(1)
go func(agent Agent, assignment Assignment) {
defer waitGroup.Done()
results <- agent.Execute(ctx, assignment)
}(item.Agent, item.Assignment)
}
go func() {
waitGroup.Wait()
close(results)
}()
collected := make([]Result, 0, len(work))
for result := range results {
if result.Err != nil {
cancel()
return nil, fmt.Errorf(
"%s failed: %w",
result.AgentID,
result.Err,
)
}
if result.TaskID == "" || len(result.Evidence) == 0 {
cancel()
return nil, errors.New("invalid worker result")
}
collected = append(collected, result)
}
sort.Slice(collected, func(i, j int) bool {
return collected[i].TaskID < collected[j].TaskID
})
return collected, nil
}
func mockRunner(
agentID string,
source string,
) func(context.Context, Assignment) ([]Evidence, error) {
return func(ctx context.Context, assignment Assignment) ([]Evidence, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
return []Evidence{{
ID: assignment.ID + ":1",
Source: source,
Summary: agentID + " completed: " + assignment.Brief,
}}, nil
}
}
func main() {
marketAgent := Agent{
ID: "market-researcher",
AllowedTools: map[string]bool{"public_search": true},
Run: mockRunner("market-researcher", "public://market"),
}
riskAgent := Agent{
ID: "risk-reviewer",
AllowedTools: map[string]bool{"policy_read": true},
Run: mockRunner("risk-reviewer", "policy://controls"),
}
work := []WorkItem{
{
Agent: marketAgent,
Assignment: Assignment{
ID: "market",
Brief: "collect demand evidence",
RequiredTools: []string{"public_search"},
},
},
{
Agent: riskAgent,
Assignment: Assignment{
ID: "risk",
Brief: "identify launch controls",
RequiredTools: []string{"policy_read"},
},
},
}
results, err := runParallel(context.Background(), work)
if err != nil {
panic(err)
}
for _, result := range results {
fmt.Printf("%s: %s\n", result.TaskID, result.Evidence[0].Summary)
}
}
示例有意把 Agent Runner 放在函数之后。生产 Adapter 还应计量模型用量、校验结构化输出、附加 Trace ID、脱敏 Prompt 与 Result,并把供应商故障转换成小而稳定的内部错误分类。
安全边界
每个 Worker 都需要独立的 Capability Envelope:
- 签发短期、绑定 Audience 的凭证;
- 按 Tenant 与数据分类限制检索;
- 只暴露 Assignment 所需 Tool;
- 加载资源后再次执行对象级鉴权;
- 对外可见或不可逆写操作要求人工批准;
- 在另一个模型看到 Tool Result 前完成净化;
- 阻止 Worker 改写控制面指令。
Agent 间消息不会因为双方都在内部就变得可信。恶意网页可以向 Research Worker 注入指令,再由它转发给拥有更强 Tool 的 Worker。系统必须分离数据与指令,并在 Executor 对每个副作用重新鉴权。
失败策略
上线前应明确行为:
| 故障 | 推荐控制 |
|---|---|
| Worker 超时 | 取消下游依赖,只在 Attempt 与成本预算内重试 |
| Result 无效 | 在 Schema 边界拒绝,最多修复一次或转人工审核 |
| 重复投递 | 按 Task ID 与 Attempt 去重并返回已存 Result |
| 证据冲突 | 保留双方来源,交由确定性规则或人工裁决 |
| Manager 故障 | 从持久化 Graph State 恢复,不能重建 Chat History 猜状态 |
| 部分副作用 | 重试前先按 Idempotency Key 查询 |
| Handoff 循环 | 达到转移上限后以 cycle_detected 停止 |
| 预算耗尽 | 返回明确标注不完整的部分证据 |
不要自动重试结果未知的写操作。扣款、邮件或删除后的超时,可能只是响应丢失,操作本身已经成功。
评测与可观测性
分别评测三层:
- **Worker:**任务完成、证据 Precision/Recall、Tool 参数质量、策略合规。
- **Coordinator:**路由准确率、分解质量、重复工作、重试、取消与预算执行。
- **System:**业务结果、人工修正率、p50/p95 延迟、每个成功任务成本与安全事件。
追踪完整因果图:
run -> assignment -> model call -> tool call -> artifact
-> handoff/retry -> review -> final answer
记录 Prompt、Model、Tool Schema、Policy 与路由逻辑版本。脱敏后的输入输出也应遵循明确保留策略。只评最终答案无法发现无意义 Tool Call、数据泄露,或 Reviewer 对无证据结论的机械放行。
对抗用例应覆盖:恶意检索指令、跨 Tenant ID、Worker 不可用、Queue 重复投递、过期证据、专家冲突,以及副作用执行中的取消。
上线步骤
- 建立单 Agent 或普通工作流基线。
- 只在已测量的瓶颈增加一个 Specialist。
- 以 Shadow Mode 运行,不执行外部副作用。
- 比较质量、延迟、成本和人工介入。
- 增加持久 Checkpoint 与故障演练。
- 从低风险流量开始,并提供回滚路径。
- 只有收益高于协调成本时才扩大范围。
常见问题
Manager 本身应该是 Agent 吗?
可以,但分解与 Policy 执行必须受约束。常见做法是让模型提出计划,再由确定性代码校验任务类型、依赖、接收方、Tool 和预算。
独立投票能消除幻觉吗?
不能。共享模型、Prompt 模式和来源的 Worker 会产生相关错误。多样性需要来自证据、方法或真正独立的系统,Verifier 仍需明确 Rubric。
Agent 应共享完整会话历史吗?
通常不应该。只发送任务所需的最小上下文和 Artifact Reference。完整 Transcript 会增加成本、暴露无关敏感数据并模糊责任。
MCP 是多 Agent 协议吗?
不是。MCP 标准化 Host、Client 与 Server 之间的上下文和能力交换。Coordinator 可以使用 MCP Tool,但任务分解、Worker 调度、权限与多 Agent 状态仍由应用负责。
总结
多 Agent 架构的理由是可量化的隔离、并行或归属收益,而不是 Prompt 有多少步骤。把拓扑和权限留在确定性控制面,为 Worker 配置窄化能力,交换类型化证据,并以更简单的单 Agent 方案作为评测基线。
单 Agent 基础可阅读 AI Agent 生产架构指南,协调细节可继续阅读多 Agent 编排模式。