
📰 科技要闻
• 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大概长这样:
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
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工具调用来转移控制权。
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里会注入一段关于交接的说明:
"""
你是知识库检索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_search、es_search、graph_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间通信都用这个格式:
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能写的字段。
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说"答案不准确,需要重新检索"——这个"打回"怎么实现?
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需要动态注册工具(比如用户上传了一个自定义函数),以及工具权限控制(不同用户能调不同工具),怎么设计一个安全又灵活的工具系统。