为什么一个 Agent 不够用?
前面八篇文章里,我们构建的都是单 Agent:一个 LLM,一组工具,一条对话历史。这套架构能解决大多数问题。
但有些任务天然是"多专家"的:
- 写一篇技术文章,需要研究员收集资料、写手起草、编辑润色------三个角色,三种思维方式
- 处理用户工单,需要意图识别、知识库查询、回复生成------三个阶段,独立可测
- 代码评审,需要静态分析、安全扫描、可读性审查------三个维度,互不干扰
用单 Agent 处理这些任务并非不可以,但你会发现 System Prompt 越写越长,输出质量越来越不稳定------因为你在强迫一个角色扮演所有人。
多 Agent 的核心价值:职责分离,每个 Agent 只做一件事,做好它。
两种主流架构模式
多 Agent 系统有多种拓扑结构,其中两种最常见:
sql
Supervisor 模式(动态路由):
classify → supervisor → researcher
↘ writer
↘ reviewer
↘ FINISH
特点:有一个"指挥中心",决定下一步调哪个 Agent
Pipeline 模式(固定顺序):
outline_agent → draft_agent → polish_agent → END
特点:执行路径硬编码,每个 Agent 只知道自己的上下文和下一个节点
两种模式不是竞争关系,而是适用场景不同。
Demo 1:Supervisor 模式
设计思路
Supervisor 模式的挑战在于路由可靠性。如果让 LLM 每一步都决定"下一个调谁",它会出现:
- 重复调用同一个 Worker
- 忘记记录已调用过的 Worker
- 不知道何时该终止
更好的设计是两阶段混合:
yaml
Phase 1: LLM 做一次任务分类(simple_fact vs full_article)
Phase 2: Python 根据分类 + 已调用列表做确定性路由
LLM 负责"看清楚这是什么任务",Python 负责"按规则执行"。
LangGraph 实现
python
class SupervisorState(TypedDict):
messages: Annotated[list, add_messages]
task: str
task_type: str # "simple_fact" or "full_article"
called: list[str]
next: str
def classify_node(state: SupervisorState) -> SupervisorState:
"""LLM 做一次分类,结果写入 state,后续路由全程可用"""
decision = _ask(
"Classify this task:\n"
" simple_fact --- a factual question with a direct short answer\n"
" full_article --- needs research, writing, and editorial review\n"
"Output one word only: simple_fact / full_article",
f"Task: {state['task']}",
).strip().lower()
task_type = "full_article" if "full_article" in decision else "simple_fact"
return {**state, "task_type": task_type}
def supervisor_node(state: SupervisorState) -> SupervisorState:
"""纯 Python 路由,不调 LLM,不会循环"""
called = state["called"]
task_type = state["task_type"]
if "researcher" not in called:
next_worker = "researcher"
elif task_type == "simple_fact":
next_worker = "FINISH" # 简单问题:研究完就结束
elif "writer" not in called:
next_worker = "writer"
elif "reviewer" not in called:
next_worker = "reviewer"
else:
next_worker = "FINISH"
return {**state, "next": next_worker}
图的拓扑结构
python
g = StateGraph(SupervisorState)
g.set_entry_point("classify")
g.add_edge("classify", "supervisor")
g.add_conditional_edges(
"supervisor",
route_supervisor,
{"researcher": "researcher", "writer": "writer",
"reviewer": "reviewer", "FINISH": END},
)
g.add_edge("researcher", "supervisor")
g.add_edge("writer", "supervisor")
g.add_edge("reviewer", "supervisor")
classify → supervisor → [workers] → supervisor → ... → FINISH 形成一个可控的循环。
实测执行结果
任务:"Write a short article about Python list comprehensions"
less
[classify] task_type = full_article
[supervisor] → researcher
[researcher] working...
[supervisor] → writer
[writer] working...
[supervisor] → reviewer
[reviewer] working...
[supervisor] → FINISH
Workers called: ['researcher', 'writer', 'reviewer']
Task type : full_article
classify 只调用一次,后续路由全程由 Python 掌控,执行链干净清晰:researcher → writer → reviewer → FINISH。
Demo 2:Pipeline 模式
Pipeline 的代码量比 Supervisor 少很多------因为不需要路由逻辑:
python
class PipelineState(TypedDict):
topic: str
outline: str
draft: str
polished: str
stage_log: list[str]
def outline_agent(state: PipelineState) -> PipelineState:
outline = _ask("创建一个 5 点大纲...", state["topic"])
return {**state, "outline": outline, "stage_log": [...]}
def draft_agent(state: PipelineState) -> PipelineState:
draft = _ask("基于大纲写 200 字草稿...", state["outline"])
return {**state, "draft": draft, "stage_log": [...]}
def polish_agent(state: PipelineState) -> PipelineState:
polished = _ask("润色...", state["draft"])
return {**state, "polished": polished, "stage_log": [...]}
# 拓扑:outline → draft → polish → END
g.add_edge("outline_agent", "draft_agent")
g.add_edge("draft_agent", "polish_agent")
g.add_edge("polish_agent", END)
实测执行结果
ini
[outline_agent] 957 chars
[draft_agent] 1846 chars
[polish_agent] 2168 chars
最终输出(前 300 字):
"### Unveiling the Power of List Comprehensions in Python
Python's lists are dynamic and powerful data containers...
每个阶段的输出都是下一阶段的输入,内容逐步丰富:大纲 957 字符 → 草稿 1846 → 润色 2168。
整个流程没有任何 LLM 路由决策------路径在写代码时就确定了。
Demo 3:同一张图,不同执行路径
Supervisor 模式最直观的优势:不改代码,不同任务自动走不同路径。
用同一个 Supervisor 图跑一道简单的事实问题:
ini
任务:"What year was Python created?"
[classify] task_type = simple_fact
[supervisor] → researcher
[researcher] working...
[supervisor] → FINISH
Workers called : ['researcher']
Task type : simple_fact
结果:researcher → FINISH(writer + reviewer 被跳过)
对比两次运行:
java
任务 Workers 调用链 步骤数
──────────────────────────────────────────────────────────────────────
Write article (full_article) researcher→writer→reviewer 3
Factual question (simple_fact) researcher 1
同一个 Supervisor 图,根据任务分类自动选择执行路径。
Pipeline 做不到这件事------它的路径在代码里已经写死了。
模式选择矩阵
维度 Pipeline Supervisor
──────────────────────────────────────────────────────────────────────
执行路径 固定,写死在代码里 动态,分类驱动
最适合场景 ETL 流水线、文档处理 研究、开放问答、混合任务
可调试性 高(线性 trace) 中(路径随任务变化)
LLM 调用次数 N(每阶段一次) N + 1(多一次分类调用)
灵活性 低 高
可预测性 高 较低
实现复杂度 简单 中等
经验法则:
- 明确知道执行哪几步 → Pipeline
- 需要根据任务决定步骤 → Supervisor
多 Agent 设计 Checklist
架构选型
- 任务步骤固定且已知 → Pipeline;需要动态决策 → Supervisor
- Worker 数量 ≤ 3 且职责清晰时,两种模式都可行
- Worker 数量 > 5 时,考虑分层 Supervisor(Supervisor of Supervisors)
State 设计
- 每个 Worker 只读自己需要的字段,只写自己生成的字段
- Supervisor 状态必须包含
called: list[str],用于路由去重 - Pipeline 状态按阶段命名(
outline,draft,polished),便于调试
路由可靠性
- 避免纯 LLM 路由(LLM 无法可靠追踪调用历史)
- 推荐:LLM 做一次分类 + Python 做确定性路由
- 设置
recursion_limit(建议 20--30),防止意外循环
Worker 设计
- 每个 Worker 只做一件事,输入输出明确
- Worker 之间通过 State 传递结果,不直接相互调用
- 写好 Worker 的 System Prompt,不要让它猜测上下文
可观测性
- 在每个节点打印执行日志(worker 名称、输入/输出摘要)
- 记录
called列表,方便回溯路由决策 - 对异常分支(如 FINISH 提前触发)加 warning log
总结
五个核心结论:
- 多 Agent 的本质是职责分离:不是为了炫技,而是当单 Agent 的 Prompt 开始失控时,拆成多个专注的角色
- Pipeline 胜在简单可预测:执行路径写在代码里,trace 是线性的,测试和调试成本最低
- Supervisor 胜在自适应:同一张图,简单问题一步到位,复杂任务全程走完,不需要改代码
- LLM 路由 + Python 执行是最佳搭档:让 LLM 做分类(它擅长),让 Python 做路由(它可靠)
called列表是 Supervisor State 的关键字段:路由确定性的基础,缺了它 Supervisor 容易出现重复调用或死循环
参考资料
更多实用知识和有趣产品,欢迎访问我的个人主页