首页/LangGraph 框架指南/第 10 章 从单图到团队:多 Agent 架构
LangGraph 框架指南 · 第 10 章

第 10 章 从单图到团队:多 Agent 架构

本章目标:用前九章的全部积木搭出多 Agent 系统。理解"Agent = 带工具循环的子图"这个组合公式,掌握 Supervisor(主管协调)与 Swarm(同伴移交)两种主流协作拓扑,以及它们各自适合的问题形状

约 17 分钟6,939 字6 个代码示例

本章目标:用前九章的全部积木搭出多 Agent 系统。理解"Agent = 带工具循环的子图"这个组合公式,掌握 Supervisor(主管协调)与 Swarm(同伴移交)两种主流协作拓扑,以及它们各自适合的问题形状。

10.1 什么时候需要多 Agent

先把"多 Agent"祛魅。它不是玄学,而是一个朴素的工程判断:当一张图的职责多到 prompt 装不下、工具列表长到模型选择困难时,拆。

具体信号有三个:

  1. System prompt 膨胀。你发现自己在给同一个模型同时交代"解析简历的规范、评估的尺度、报告的格式、拒信的口吻"——每一项职责都想占满 prompt,互相稀释。
  2. 工具过载。绑了 15 个工具,模型选错工具的频率明显上升(工具选择是按 schema 匹配的,候选越多噪声越大)。
  3. 子图复用。"JD 分析"、"简历解析"这些能力已经在别的图里存在了,多 Agent 化只是把既有组件组装起来。

反面对照:如果三个信号一个都没有,单图 + 好好写 prompt 是更简单、更快、更便宜的正确答案。多 Agent 是应对复杂度的手段,不是目的。

确立一个贯穿本章的组合公式:

Agent = 子图(通常是 ReAct 循环)+ 专属 prompt + 专属工具集
多 Agent 系统 = 若干 Agent 子图 + 协作拓扑(谁调谁、怎么传话)

前九章的每块积木都在这里派上用场:子图(第 9 章)封装每个 Agent,条件边/Command(第 4、6 章)实现协作路由,Send(第 7 章)实现并行协作,消息状态(第 3 章)是它们传话的介质。

10.2 协作拓扑一:Supervisor(主管协调)

最直观的拓扑:一个主管 Agent 负责分诊,把任务路由给专职 Agent,收集结果,决定下一步。

flowchart TB S((START)) --> SUP[supervisor<br/>主管:分诊与汇总] SUP -->|route| A1[parser agent<br/>简历解析] SUP -->|route| A2[tech agent<br/>技术评估] SUP -->|route| A3[report agent<br/>报告生成] A1 --> SUP A2 --> SUP A3 --> SUP SUP -->|完成| E((END))

主管的产出是一个路由决策:读当前状态(尤其是各 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 干完活,自己决定"接下来最适合接手的是谁":

flowchart LR S((START)) --> P[parser agent] P -->|移交| T[tech agent] T -->|移交| R[report agent] R --> E((END))
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 的核心机制已经全部讲完,剩下的章节全是运行时特性。

常见坑与排错

  1. 过早多 Agent 化。最常见的坑。单图能跑的任务套上三 Agent 拓扑,结果延迟翻倍(多跳超步)、token 翻倍(重复读消息黑板)、调试难度翻倍。先对照 10.1 的三个信号,一个都没有就别拆。

  2. supervisor 的路由死循环。主管的路由条件写得有漏洞(比如某字段永远填不上),supervisor 与某 Agent 之间无限乒乓,直到 recursion_limit 爆炸。治理与第 4 章循环安全阀同源:路由条件要覆盖"失败/缺失"分支(填不上就 goto 收尾节点),并保留 recursion_limit 兜底。

  3. Agent 之间共享可变对象。多个 Agent 子图并行时(Send 扇出场景),载荷或状态字段里共享同一个 list/dict——第 7 章 7.6 的纪律在多 Agent 下同样生效。

  4. 消息黑板无限膨胀。每跳超步都往 messages 追加,长对话 + 多 Agent 轮转后上下文爆窗。第 15 章的记忆裁剪专门解决这个问题,这里先立 flag:多 Agent 系统必须配记忆管理。

  5. Swarm 模式下移交目标写死。goto="tech_agent" 硬编码在 Agent 代码里,流程一调整就要改代码。成熟做法是把"移交候选"写进每个 Agent 的 prompt/配置,由模型在运行时决定(LangGraph 生态的 langgraph-swarm 等高层封装做的就是这件事)。

  6. 以为多 Agent 能提升"智能"。多 Agent 改变的是组织方式,不是模型能力。解析质量差不会因为套了三个 Agent 就变好——先优化单环节,再谈编排。

动手练习

  1. 给 10.2 的 supervisor 版增加第四个 Agent:background_agent(读 parsed 里的公司名,产出 bg_report)。改动清单:注册节点、扩展 TeamState、修改 supervisor 路由逻辑。体会"新增成员只动主管"的扩展性。

  2. 把 10.2 的 supervisor 路由从"读结构化字段"改写成"把 messages 黑板发给 LLM,让模型输出下一个 Agent 名"(用结构化输出)。对比两种实现的可靠性:哪种路由更稳?哪种更灵活?

  3. 混合拓扑实验:外层 Supervisor 管两个阶段("评估"与"报告"),"评估"阶段内部是 Swarm(parser → tech 直接移交)。画出最终图结构(用 draw_mermaid),数一数从 START 到 END 最少要几个超步。

本章速查

概念 一句话
组合公式 Agent = ReAct 子图 + 专属 prompt + 专属工具;多 Agent = 图套图
拆分三信号 prompt 膨胀、工具过载、子图待复用
Supervisor 拓扑 主管集中路由,Agent 干完回主管;控制集中、多一跳
Swarm 拓扑 Agent 直接移交控制权;轻量快速、缺全局协调
选型法则 动态协商选 Supervisor,稳定流水线选 Swarm
消息黑板二分法 结构化产物走专用字段,对话上下文走 messages
代码读字段,模型读对话 supervisor 读结构化字段做路由,Agent 读 messages 做推理
机制清单 至本章止核心机制讲完,后续为运行时特性