首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >多Agent协作架构:Supervisor/Router/Handoff模式设计

多Agent协作架构:Supervisor/Router/Handoff模式设计

作者头像
陆业聪
发布2026-07-28 15:52:17
发布2026-07-28 15:52:17
2160
举报

📰 科技要闻

• Claude Opus 5正式发布,Anthropic在推理能力上再次刷新基准测试

• 宇树科技CEO王兴兴登上《时代》杂志封面,中国机器人公司获全球关注

• 国家反诈中心App可一键检测AI生成痕迹,AIGC内容治理进入新阶段

上周我把知识库的Docker Compose全栈环境搞定之后,终于有精力回来处理一个拖了很久的问题:我那个"万能Agent"越来越不可控了。

说实话,当你的单Agent挂了十几个工具——向量检索、ES搜索、知识图谱查询、文档解析、权限校验、答案生成、引用格式化——它开始表现得像一个同时处理十件事的实习生:每件事都知道一点,但没一件做得漂亮。

更要命的是,调试的时候你根本不知道它为什么选了ES而不是向量检索,也不知道为什么它在需要审核的时候直接跳过了。Context越长,推理质量越差,这不是调个prompt就能解决的结构性问题。

所以这一篇,我们聊多Agent协作。不是那种学术论文里的"emergent multi-agent behavior",而是工程上怎么把一个臃肿的单Agent拆成多个专业Agent,用什么模式组织它们,以及——最难的部分——它们之间怎么传递上下文。

单Agent的天花板在哪

先说清楚问题。我的单Agent知识库助手,最初设计很简单:用户提问→检索→生成答案。但需求迭代之后变成了这样:

🔧 工具数量:14个(向量检索、ES搜索、知识图谱、文档上传、权限校验、答案审核...)

📏 System Prompt:2800 tokens(光工具描述就占了一大半)

🧠 典型对话上下文:8000-12000 tokens

❌ 工具选择错误率:~15%(尤其是ES和向量检索之间的选择)

三个核心问题:

1. 工具太多,选择困难。14个工具的描述挤在system prompt里,模型需要在每次推理时"扫描"所有选项。GPT-4o在工具数超过10个时,选择准确率明显下降。

2. 上下文太长,推理漂移。多轮对话后,早期的检索结果和后期的用户追问混在一起,模型开始"遗忘"关键信息。

3. 职责不清,调试困难。一个Agent同时负责检索、生成、审核,出了问题你不知道是哪个环节的锅。日志里全是同一个Agent的trace,分不清边界。

三种协作模式:Supervisor / Router / Handoff

业界主流的多Agent协作架构基本是这三种模式的变体或组合。我逐个拆解。

模式一:Supervisor(监督者)

一个"老板Agent"接收用户请求,拆解任务,分配给下属的专家Agent,收集结果后汇总返回。

👤 用户提问

🧠 Supervisor Agent

拆解任务 → 分配 → 汇总

↓ ↓ ↓

🔍 检索Agent 负责向量/ES/图谱检索

✍️ 生成Agent 负责答案组织与格式化

🛡️ 审核Agent 负责事实核查与合规检查

Supervisor模式的核心实现,用LangGraph大概长这样:

代码语言:javascript
复制
from langgraph.graph import (
StateGraph, END
)
from typing import TypedDictclass KBState(TypedDict):
query: str
docs: list
answer: str
review: str
next_agent: strdef supervisor(state):
# Supervisor决定下一步交给谁
if not state["docs"]:
return {
"next_agent":
"retriever"
}
if not state["answer"]:
return {
"next_agent":
"generator"
}
if not state["review"]:
return {
"next_agent":
"reviewer"
}
return {"next_agent": "end"}graph = StateGraph(KBState)
graph.add_node(
"supervisor", supervisor
)
graph.add_node(
"retriever", retrieval_agent
)
graph.add_node(
"generator", generation_agent
)
graph.add_node(
"reviewer", review_agent
)# 条件边:根据supervisor决策路由
graph.add_conditional_edges(
"supervisor",
lambda s: s["next_agent"],
{
"retriever": "retriever",
"generator": "generator",
"reviewer": "reviewer",
"end": END,
}
)

优点:控制流清晰,Supervisor有全局视角,适合需要多步编排的复杂任务。

缺点:Supervisor本身就是一次LLM调用,增加了延迟和成本。如果Supervisor决策出错,整条链路都会跑偏。

模式二:Router(路由器)

Router比Supervisor轻量得多——它不拆解任务,只做意图分类,然后把整个请求路由到对应的专家Agent。每个专家Agent独立完成全部工作。

👤 用户提问

🔀 Router(意图分类)

📖 知识问答 → 知识库检索Agent

📤 文档管理 → 文档操作Agent

💬 闲聊 → 通用对话Agent

代码语言:javascript
复制
ROUTER_PROMPT = """
根据用户消息,分类为以下意图:
- kb_query: 知识库问答
- doc_mgmt: 文档管理操作
- chat: 闲聊/其他只返回意图标签,不要解释。
"""async def route_request(
query: str,
llm: ChatModel
) -> str:
# 用小模型做分类,快且便宜
resp = await llm.invoke(
ROUTER_PROMPT + query,
model="gpt-4o-mini",
max_tokens=10
)
intent = resp.strip()# 路由到对应Agent
agents = {
"kb_query": kb_agent,
"doc_mgmt": doc_agent,
"chat": chat_agent,
}
agent = agents.get(
intent, chat_agent
)
return await agent.run(query)

关键实践:Router通常用小模型(GPT-4o-mini或claude-haiku)做分类,延迟增加很少(~200ms),但能让每个专家Agent的prompt更精简、工具更少、准确率更高。

我实际使用下来,Router模式对知识库场景最实用。因为大部分时候用户的意图是明确的——要么查资料,要么管文档,要么瞎聊。不需要一个Supervisor来拆解复杂任务。

模式三:Handoff(交接)

Handoff是最灵活也最危险的模式。Agent在执行过程中,如果发现当前任务超出自己能力范围,可以主动把对话"交接"给另一个Agent。

OpenAI的Swarm框架和Anthropic推荐的agent模式都采用了这种设计。核心思想:Agent之间通过一个特殊的handoff工具调用来转移控制权。

代码语言:javascript
复制
from dataclasses import dataclass@dataclass
class Agent:
name: str
instructions: str
tools: list
handoffs: list  # 可交接的Agent列表# 定义Agent和它们的交接关系
triage = Agent(
name="triage",
instructions="分诊Agent...",
tools=[classify_intent],
handoffs=[
kb_agent,
doc_agent
]
)kb_agent = Agent(
name="knowledge_base",
instructions="知识库检索...",
tools=[
vector_search,
es_search,
graph_query
],
handoffs=[review_agent]
)review_agent = Agent(
name="reviewer",
instructions="审核答案...",
tools=[fact_check, cite],
handoffs=[kb_agent]  # 可以打回重查
)

Handoff模式下,每个Agent的system prompt里会注入一段关于交接的说明:

代码语言:javascript
复制
"""
你是知识库检索Agent。当你完成检索并组织好答案后,
调用 handoff_to_reviewer 工具,
传递:
- answer: 你生成的答案
- sources: 引用的文档列表
- confidence: 你对答案的置信度如果用户的问题与文档管理相关
(上传/删除/更新文档),
调用 handoff_to_doc_agent。
"""

Handoff的核心难点是上下文传递。交接给另一个Agent时,你需要决定传什么、不传什么。传太多——token浪费,新Agent被噪音干扰;传太少——新Agent缺乏上下文,做出错误判断。

三种模式的选型对比

维度

Supervisor

Router

Handoff

复杂度

额外延迟

多次LLM调用

1次分类

每次交接1次

适用场景

多步编排

意图明确

流程动态

可控性

强(集中控制)

强(确定路由)

弱(Agent自主)

调试难度

中等

我的经验法则:先用Router,不够再加Supervisor,Handoff慎用。

为什么?因为Router的行为最可预测。一个分类结果决定了走哪条路,出问题很容易定位。Supervisor增加了编排的灵活性但也增加了出错面。Handoff最灵活,但Agent自主决定交接这件事本身就不可靠——你永远不确定它会不会在不该交接的时候交接。

知识库场景的Agent拆分实战

说完理论,聊聊我实际怎么拆的。最终方案是Router + 内部Handoff的混合架构:

👤 用户请求

🔀 Router(意图分类)

路径A:知识问答

检索Agent → handoff → 生成Agent → handoff → 审核Agent

路径B:文档管理

文档操作Agent(独立完成)

路径C:通用对话

Chat Agent(独立完成)

四个专家Agent的职责划分:

检索Agent——只干一件事:把用户问题转化为最优检索策略,拿到最相关的文档片段。它有3个工具:vector_searches_searchgraph_query。它不生成答案,只输出检索结果。

生成Agent——接收检索结果和原始问题,组织成带引用的答案。它没有任何检索工具,这确保它不会"自作主张"去查新东西。

审核Agent——检查生成的答案是否有事实错误、引用是否准确、是否符合企业合规要求。它可以"打回"给检索Agent重新检索。

文档操作Agent——处理上传、删除、更新文档的请求,独立完成不需要其他Agent参与。

拆分后的效果:

✅ 每个Agent的工具数 ≤ 4个(vs 原来14个)

✅ System Prompt长度下降60%

✅ 工具选择错误率:15% → 3%

✅ 审核覆盖率:0%(原来经常跳过)→ 100%

⚠️ 代价:端到端延迟增加约1.5s(多了Router + Handoff的LLM调用)

Agent通信协议:传什么、怎么传

这是多Agent架构里最被低估的问题。两个Agent之间传递信息,本质上是在设计一个"内部API"。设计不好,整个系统就是一团乱麻。

消息格式标准化

我定义了一个统一的AgentMessage结构,所有Agent间通信都用这个格式:

代码语言:javascript
复制
from pydantic import BaseModel
from enum import Enumclass HandoffReason(Enum):
TASK_COMPLETE = "task_complete"
NEED_EXPERTISE = "need_expertise"
REVIEW_NEEDED = "review_needed"
REJECTED = "rejected"class AgentMessage(BaseModel):
# 谁发的、发给谁
from_agent: str
to_agent: str
reason: HandoffReason# 必须传的上下文
original_query: str
conversation_summary: str# 这一步的产出
payload: dict
# 如:检索Agent传 {docs: [...]}  
# 生成Agent传 {answer: "...",
#                sources: [...]} # 元数据
trace_id: str
attempt: int = 1
max_attempts: int = 3

状态共享 vs 消息传递

两种方式各有优劣:

方式一:共享状态(LangGraph风格)——所有Agent读写同一个State对象。优点是简单直接;缺点是任何Agent都能改全局状态,容易产生意想不到的副作用。

方式二:消息传递(Swarm/Actor风格)——Agent之间通过结构化消息通信,每个Agent有自己的私有状态。优点是隔离性好;缺点是需要额外设计消息协议。

我的选择:用LangGraph的共享状态做骨架,但用TypedDict严格约束每个Agent能写的字段。

代码语言:javascript
复制
class SharedState(TypedDict):
# 只读区(Router写入后不变)
query: str
intent: str
user_id: str# 检索Agent写入
retrieved_docs: list
retrieval_strategy: str# 生成Agent写入
draft_answer: str
citations: list# 审核Agent写入
review_passed: bool
review_notes: str# 最终输出
final_answer: str

错误传播与重试

多Agent系统里,错误传播是个大坑。审核Agent说"答案不准确,需要重新检索"——这个"打回"怎么实现?

代码语言:javascript
复制
def review_node(state):
result = review_agent.run(state)if result.passed:
return {
"review_passed": True,
"final_answer":
state["draft_answer"]
}# 打回:带上审核意见
attempt = state.get(
"attempt", 1
)
if attempt >= 3:
# 重试上限,降级返回
return {
"final_answer":
"抱歉,未能找到可靠答案"
}return {
"review_passed": False,
"review_notes":
result.feedback,
"attempt": attempt + 1,
# 清空检索结果触发重新检索
"retrieved_docs": [],
"draft_answer": ""
}

关键点:必须设重试上限。不然两个Agent可能进入死循环——检索Agent拿了新结果,审核Agent还是不满意,无限循环。3次是我测试下来的经验值,超过3次基本就是问题本身没答案,不是检索不够好。

踩过的坑和实战建议

坑1:过度拆分。我最初把知识库拆了6个Agent,包括单独的"查询改写Agent"和"引用格式化Agent"。结果发现这两个太轻量了,单独做一个Agent调用不划算,合并到检索Agent和生成Agent里反而更好。经验:如果一个Agent的工作量不值一次LLM调用的成本(~$0.01 + 500ms),就不该独立成Agent。

坑2:Conversation Summary丢信息。Handoff时我用LLM生成对话摘要传给下一个Agent,结果关键细节经常被压缩掉。最终方案是传原始query + 最近3轮对话 + 结构化产出,而不是依赖摘要。

坑3:Router分类用大模型。最初Router也用GPT-4o,latency加了800ms。换成GPT-4o-mini后准确率只下降了2%(97%→95%),但延迟从800ms降到200ms。分类任务用小模型足够。

坑4:忘记trace关联。多Agent架构下如果不做trace关联,LangSmith里你看到的是一堆散落的独立trace,根本无法追踪一个请求经过了哪些Agent。解决:全局trace_id从Router开始生成,贯穿所有Agent。

我的选型结论:知识库场景用 Router + Pipeline Handoff 是性价比最高的方案。Router做第一层意图分流,然后在"知识问答"路径里用固定顺序的Handoff(检索→生成→审核)。不要用纯Supervisor,太重了。

下一篇我们聊Tool Use的高级模式——当你的Agent需要动态注册工具(比如用户上传了一个自定义函数),以及工具权限控制(不同用户能调不同工具),怎么设计一个安全又灵活的工具系统。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-07-25,如有侵权请联系 cloudcommunity@tencent.com 删除
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档