第 10 章 从单图到团队:多 Agent 架构
本章目标:用前九章的全部积木搭出多 Agent 系统。理解"Agent = 带工具循环的子图"这个组合公式,掌握 Supervisor(主管协调)与 Swarm(同伴移交)两种主流协作拓扑,以及它们各自适合的问题形状
本章目标:用前九章的全部积木搭出多 Agent 系统。理解"Agent = 带工具循环的子图"这个组合公式,掌握 Supervisor(主管协调)与 Swarm(同伴移交)两种主流协作拓扑,以及它们各自适合的问题形状。
10.1 什么时候需要多 Agent
先把"多 Agent"祛魅。它不是玄学,而是一个朴素的工程判断:当一张图的职责多到 prompt 装不下、工具列表长到模型选择困难时,拆。
具体信号有三个:
- System prompt 膨胀。你发现自己在给同一个模型同时交代"解析简历的规范、评估的尺度、报告的格式、拒信的口吻"——每一项职责都想占满 prompt,互相稀释。
- 工具过载。绑了 15 个工具,模型选错工具的频率明显上升(工具选择是按 schema 匹配的,候选越多噪声越大)。
- 子图复用。"JD 分析"、"简历解析"这些能力已经在别的图里存在了,多 Agent 化只是把既有组件组装起来。
反面对照:如果三个信号一个都没有,单图 + 好好写 prompt 是更简单、更快、更便宜的正确答案。多 Agent 是应对复杂度的手段,不是目的。
确立一个贯穿本章的组合公式:
Agent = 子图(通常是 ReAct 循环)+ 专属 prompt + 专属工具集
多 Agent 系统 = 若干 Agent 子图 + 协作拓扑(谁调谁、怎么传话)
前九章的每块积木都在这里派上用场:子图(第 9 章)封装每个 Agent,条件边/Command(第 4、6 章)实现协作路由,Send(第 7 章)实现并行协作,消息状态(第 3 章)是它们传话的介质。
10.2 协作拓扑一:Supervisor(主管协调)
最直观的拓扑:一个主管 Agent 负责分诊,把任务路由给专职 Agent,收集结果,决定下一步。
主管的产出是一个路由决策:读当前状态(尤其是各 Agent 的产出与待办),决定下一步激活谁,直到判定任务完成。实现上,主管就是一个返回 Command 的节点——第 6 章的机制原封不动:
from typing import Annotated, TypedDict
import operator
from langchain_core.messages import AnyMessage
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.types import Command
class TeamState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages] # 团队共享消息黑板
resume_text: str
parsed: dict # parser 产物
tech_report: str # tech 产物
final_report: str # report 产物
# ---- 三个专职 Agent:演示版各是一个简单节点 ----
# 真实实现里,每个都是第 8 章那样的 ReAct 子图
def parser_agent(state: TeamState) -> Command:
lines = [l.strip() for l in state["resume_text"].splitlines() if l.strip()]
parsed = {
"name": lines[0].split(":")[0],
"skills": next((l.replace("- 技能:", "").split("、") for l in lines if l.startswith("- 技能")), []),
}
# 干完活,控制权交回主管
return Command(update={"parsed": parsed}, goto="supervisor")
def tech_agent(state: TeamState) -> Command:
score = min(100, 30 + len(state["parsed"]["skills"]) * 15)
report = f"技术评估:{state['parsed']['name']} 得分 {score}"
return Command(update={"tech_report": report}, goto="supervisor")
def report_agent(state: TeamState) -> Command:
final = f"{state['tech_report']}\n解析字段数:{len(state['parsed'])}"
return Command(update={"final_report": final}, goto=END) # 报告完成即收工
# ---- 主管:读状态,决定下一个干活的 Agent ----
def supervisor(state: TeamState) -> Command:
if not state.get("parsed"):
return Command(goto="parser_agent") # 还没解析 → 先解析
if not state.get("tech_report"):
return Command(goto="tech_agent") # 没评估 → 去评估
return Command(goto="report_agent") # 齐了 → 出报告
builder = StateGraph(TeamState)
builder.add_node("supervisor", supervisor)
builder.add_node("parser_agent", parser_agent)
builder.add_node("tech_agent", tech_agent)
builder.add_node("report_agent", report_agent)
builder.add_edge(START, "supervisor")
graph = builder.compile()
result = graph.invoke({
"resume_text": "李思远:Python 后端\n- 技能:Python、FastAPI、PostgreSQL",
"messages": [],
})
print(result["final_report"])
# 技术评估:李思远 得分 75
# 解析字段数:2
执行轨迹:supervisor → parser_agent → supervisor → tech_agent → supervisor → report_agent → END。主管每轮都被激活一次,像一个不断核对清单的项目经理。
这个模式的可贵之处在于控制流集中在主管:新增一个 Agent(比如"背景核查 Agent"),只需要注册节点 + 在主管的路由逻辑里加一个分支,其他 Agent 毫无感知。代价是主管成了瓶颈与单点——每次移交都要过它一道,多一跳超步;主管的路由逻辑写歪,整个团队跟着歪。
前面反复出现的"Command 节点不挂出边"铁律在本例中的体现:supervisor 和三个 Agent 节点都返回 Command,因此都不挂任何出边,全部流向由 goto 声明。
10.3 协作拓扑二:Swarm(同伴移交)
去掉主管,让 Agent 之间直接移交控制权。每个 Agent 干完活,自己决定"接下来最适合接手的是谁":
def parser_agent(state: TeamState) -> Command:
lines = [l.strip() for l in state["resume_text"].splitlines() if l.strip()]
parsed = {"name": lines[0].split(":")[0]}
# 不回主管,直接点名下一个接手者
return Command(update={"parsed": parsed}, goto="tech_agent")
def tech_agent(state: TeamState) -> Command:
report = f"技术评估完成:{state['parsed']['name']}"
return Command(update={"tech_report": report}, goto="report_agent")
def report_agent(state: TeamState) -> Command:
return Command(update={"final_report": state["tech_report"] + "(终版)"}, goto=END)
builder = StateGraph(TeamState)
builder.add_node("parser_agent", parser_agent)
builder.add_node("tech_agent", tech_agent)
builder.add_node("report_agent", report_agent)
builder.add_edge(START, "parser_agent")
graph = builder.compile()
result = graph.invoke({
"resume_text": "李思远:Python 后端",
"messages": [],
})
print(result["final_report"]) # 技术评估完成:李思远(终版)
对比 supervisor 版:同样的任务,执行轨迹短了三跳(超步从 7 降到 4),结构也更轻。适合流程形状相对固定、移交规则清晰的场景。
它的弱点恰恰是 supervisor 的强项:没有全局协调者,"接下来去哪"的决策分散在每个 Agent 手里。需要动态协商、条件分支复杂时,每个 Agent 的移交逻辑都会变复杂,最终陷入分布式意大利面。此外它还容易丢掉"任务是否真正完成"的全局判断——supervisor 模式里这个判断天然在主管手里。
选型经验法则:协作动态多变、需要全局调度 → Supervisor;流水线形态稳定、移交路径清晰 → Swarm。两者可混合:外层 Supervisor 管大阶段,某个阶段内部是 Swarm。
10.4 消息黑板:Agent 间传话的介质
两种拓扑里 Agent 怎么交换信息?看 TeamState 的设计:messages(add_messages)承载对话性质的信息(用户请求、Agent 间的自然语言交流),而 parsed、tech_report 这类字段承载结构化产物。
这是多 Agent 状态设计的实用二分法:
| 信息类型 | 放哪 | 例子 |
|---|---|---|
| 结构化产物 | 专用字段(带 reducer) | 解析结果、评分、报告 |
| 对话与上下文 | messages 黑板 | 用户请求、Agent 间的移交留言 |
专职 Agent(尤其内嵌 ReAct 循环的那种)主要消费 messages——它的模型节点把黑板上的对话读一遍就能理解全局。而 supervisor 的路由逻辑更适合读结构化字段:if not state.get("parsed") 比解析一段自然语言可靠得多。让模型读对话,让代码读字段,各取所长。
混用时的一个坑值得点名:往 messages 里塞结构化产物(比如让 parser 把解析 JSON 塞进对话),下游 Agent 的模型就要从对话里"捞"数据——解析失败、幻觉字段名都会发生。结构化数据走专用字段,消息黑板只放给模型看的自然语言。
10.5 完整形态:Agent 即子图
本章 demo 里的"Agent"是简化版单节点。真实形态是第 8 章 + 第 9 章的组合:
# 每个 Agent = ReAct 子图(独立 schema)+ 专属 prompt + 专属工具
tech_agent_graph = build_react_graph(
system_prompt=TECH_PROMPT,
tools=[lookup_company, get_job_requirements],
).compile()
# 在 supervisor 图里注册(模式二:包装翻译)
def tech_agent(state: TeamState) -> Command:
sub_result = tech_agent_graph.ainvoke(...) # 或同步 invoke
return Command(update={...}, goto="supervisor")
builder.add_node("tech_agent", tech_agent)
这个组合公式值得在头脑里固化:多 Agent 系统 = 图套图。外层是协作图(supervisor/swarm 拓扑),内层是能力图(ReAct 循环),层与层之间靠第 9 章的两种嵌入模式衔接。没有引入任何新机制——这是本书最后一次强调这一点:到本章为止,LangGraph 的核心机制已经全部讲完,剩下的章节全是运行时特性。
常见坑与排错
-
过早多 Agent 化。最常见的坑。单图能跑的任务套上三 Agent 拓扑,结果延迟翻倍(多跳超步)、token 翻倍(重复读消息黑板)、调试难度翻倍。先对照 10.1 的三个信号,一个都没有就别拆。
-
supervisor 的路由死循环。主管的路由条件写得有漏洞(比如某字段永远填不上),supervisor 与某 Agent 之间无限乒乓,直到 recursion_limit 爆炸。治理与第 4 章循环安全阀同源:路由条件要覆盖"失败/缺失"分支(填不上就 goto 收尾节点),并保留 recursion_limit 兜底。
-
Agent 之间共享可变对象。多个 Agent 子图并行时(Send 扇出场景),载荷或状态字段里共享同一个 list/dict——第 7 章 7.6 的纪律在多 Agent 下同样生效。
-
消息黑板无限膨胀。每跳超步都往 messages 追加,长对话 + 多 Agent 轮转后上下文爆窗。第 15 章的记忆裁剪专门解决这个问题,这里先立 flag:多 Agent 系统必须配记忆管理。
-
Swarm 模式下移交目标写死。
goto="tech_agent"硬编码在 Agent 代码里,流程一调整就要改代码。成熟做法是把"移交候选"写进每个 Agent 的 prompt/配置,由模型在运行时决定(LangGraph 生态的langgraph-swarm等高层封装做的就是这件事)。 -
以为多 Agent 能提升"智能"。多 Agent 改变的是组织方式,不是模型能力。解析质量差不会因为套了三个 Agent 就变好——先优化单环节,再谈编排。
动手练习
-
给 10.2 的 supervisor 版增加第四个 Agent:
background_agent(读parsed里的公司名,产出bg_report)。改动清单:注册节点、扩展 TeamState、修改 supervisor 路由逻辑。体会"新增成员只动主管"的扩展性。 -
把 10.2 的 supervisor 路由从"读结构化字段"改写成"把 messages 黑板发给 LLM,让模型输出下一个 Agent 名"(用结构化输出)。对比两种实现的可靠性:哪种路由更稳?哪种更灵活?
-
混合拓扑实验:外层 Supervisor 管两个阶段("评估"与"报告"),"评估"阶段内部是 Swarm(parser → tech 直接移交)。画出最终图结构(用 draw_mermaid),数一数从 START 到 END 最少要几个超步。
本章速查
| 概念 | 一句话 |
|---|---|
| 组合公式 | Agent = ReAct 子图 + 专属 prompt + 专属工具;多 Agent = 图套图 |
| 拆分三信号 | prompt 膨胀、工具过载、子图待复用 |
| Supervisor 拓扑 | 主管集中路由,Agent 干完回主管;控制集中、多一跳 |
| Swarm 拓扑 | Agent 直接移交控制权;轻量快速、缺全局协调 |
| 选型法则 | 动态协商选 Supervisor,稳定流水线选 Swarm |
| 消息黑板二分法 | 结构化产物走专用字段,对话上下文走 messages |
| 代码读字段,模型读对话 | supervisor 读结构化字段做路由,Agent 读 messages 做推理 |
| 机制清单 | 至本章止核心机制讲完,后续为运行时特性 |