LangGraph设计与实现-第17章-多 Agent 模式实战

8 阅读12分钟

《LangGraph 设计与实现》完整目录

第17章 多 Agent 模式实战

17.1 引言

单个 Agent 能力有限——它只有一组工具、一种提示、一个执行循环。当任务复杂度上升时(如"帮我分析这家公司的财报,然后写一份投资建议书"),单个 Agent 要么因为工具太多导致 LLM 选择困难,要么因为系统提示过长影响推理质量。多 Agent 系统通过分工协作解决这个问题:不同的 Agent 负责不同的能力域,通过明确的通信机制协同完成复杂任务。

LangGraph 的底层基础设施——StateGraph、Send、Command、子图——天然支持多 Agent 模式的构建。图的节点可以是子图(即嵌套的 Agent),条件边可以实现动态路由,Send 支持一对多的任务分发,Command 支持跨图的状态更新和导航。

本章将系统介绍基于 LangGraph 实现的四种典型多 Agent 架构:Supervisor(监督者)、Swarm(蜂群)、分层 Agent 和协作 Agent。每种模式都有其适用场景和权衡,我们会结合源码和实战代码深入分析它们的设计原理。

:::tip 本章要点

  1. Supervisor 模式——中央调度者分配任务给专业 Agent
  2. Swarm 模式——Agent 之间点对点的控制权交接
  3. 分层 Agent——多层级的 Supervisor 树形结构
  4. 协作 Agent——共享状态的对等协作
  5. 基于 LangGraph 原语实现各种模式的核心技术 :::

17.2 多 Agent 的核心抽象

17.2.1 Agent 作为子图

在 LangGraph 中,一个 Agent 本质上就是一个 CompiledStateGraph。多 Agent 系统就是一个图中嵌套了多个子图:

# 每个 Agent 是一个独立的图
research_agent = create_react_agent(model, research_tools, name="researcher")
writer_agent = create_react_agent(model, writing_tools, name="writer")

# 多 Agent 系统是一个包含子图的父图
builder = StateGraph(TeamState)
builder.add_node("researcher", research_agent)
builder.add_node("writer", writer_agent)

17.2.2 通信机制的三种模式

graph TB
    subgraph "模式 1:共享状态"
        A1[Agent A] -->|写入状态| S1[共享 State]
        S1 -->|读取状态| B1[Agent B]
    end

    subgraph "模式 2:Command 导航"
        A2[Agent A] -->|"Command(goto='B')"| B2[Agent B]
    end

    subgraph "模式 3:Send 分发"
        Router[路由器] -->|"Send('A', task1)"| A3[Agent A]
        Router -->|"Send('B', task2)"| B3[Agent B]
    end

17.3 Supervisor 模式

17.3.1 架构概述

Supervisor 模式是最直观的多 Agent 架构:一个"主管"Agent 接收用户请求,分析任务需求,然后将子任务分配给专业 Agent。每个专业 Agent 完成后将结果返回给 Supervisor,Supervisor 决定是否需要更多工作。

graph TB
    User[用户] --> Sup[Supervisor Agent]
    Sup -->|"分配研究任务"| RA[Research Agent]
    Sup -->|"分配写作任务"| WA[Writer Agent]
    Sup -->|"分配代码任务"| CA[Coder Agent]
    RA -->|"研究结果"| Sup
    WA -->|"文章草稿"| Sup
    CA -->|"代码片段"| Sup
    Sup -->|"最终回复"| User

    style Sup fill:#ffe6e6,stroke:#333,stroke-width:2px

17.3.2 实现方式

from typing import Annotated, Literal, TypedDict
from langchain_core.messages import BaseMessage, HumanMessage
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import create_react_agent
from langgraph.types import Command

class SupervisorState(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]
    next_agent: str

# 创建专业 Agent
research_agent = create_react_agent(model, research_tools, name="researcher")
writer_agent = create_react_agent(model, writing_tools, name="writer")

def supervisor_node(state: SupervisorState) -> Command:
    """Supervisor 决定下一步交给哪个 Agent"""
    response = supervisor_llm.invoke([
        SystemMessage(content="""你是一个任务分配主管。
        分析用户请求,决定交给哪个 Agent:
        - researcher: 负责信息检索和数据分析
        - writer: 负责内容创作和文档编写
        - FINISH: 任务完成,返回最终结果"""),
        *state["messages"]
    ])

    # 解析 LLM 的决策
    next_agent = parse_decision(response.content)

    if next_agent == "FINISH":
        return Command(goto=END, update={"messages": [response]})
    else:
        return Command(
            goto=next_agent,
            update={"messages": [response]}
        )

# 构建 Supervisor 图
builder = StateGraph(SupervisorState)
builder.add_node("supervisor", supervisor_node)
builder.add_node("researcher", research_agent)
builder.add_node("writer", writer_agent)

builder.add_edge(START, "supervisor")
builder.add_edge("researcher", "supervisor")  # 完成后返回 Supervisor
builder.add_edge("writer", "supervisor")

graph = builder.compile()

17.3.3 Supervisor 的关键设计

  1. 中心化决策:Supervisor 是唯一的决策者,专业 Agent 只负责执行
  2. 循环结构:Agent 完成后总是返回 Supervisor,由 Supervisor 决定下一步
  3. Command 导航:使用 Command(goto=next_agent) 实现动态路由

17.3.4 适用场景与局限

适用场景

  • 子任务之间有明确的先后依赖
  • 需要中央协调来决定任务顺序
  • Agent 数量较少(3-5 个)

局限

  • Supervisor 是单点瓶颈
  • 每次决策都需要额外的 LLM 调用
  • 子任务之间无法直接通信

17.4 Swarm 模式

17.4.1 架构概述

Swarm 模式(蜂群模式)没有中央调度者。每个 Agent 在完成自己的工作后,可以直接将控制权交给另一个 Agent。这类似于客服系统中的"转接"——当前 Agent 判断用户的需求超出自己的能力范围时,主动转接给更合适的 Agent。

graph LR
    User[用户] --> A1[接待 Agent]
    A1 -->|"转接"| A2[技术支持 Agent]
    A2 -->|"转接"| A3[退款处理 Agent]
    A3 -->|"完成"| User

    A1 -.->|"也可以直接完成"| User
    A2 -.->|"也可以转回"| A1

    style A1 fill:#e6f3ff,stroke:#333
    style A2 fill:#f3ffe6,stroke:#333
    style A3 fill:#ffe6f3,stroke:#333

17.4.2 实现方式

Swarm 的核心是"handoff"工具——每个 Agent 有一个特殊工具用于将控制权转交给其他 Agent:

from langchain_core.tools import tool
from langgraph.types import Command

# 创建 handoff 工具
def create_handoff_tool(target_agent: str, description: str):
    @tool(name=f"transfer_to_{target_agent}")
    def handoff() -> Command:
        f"""将对话转交给 {target_agent}{description}"""
        return Command(goto=target_agent)
    return handoff

# 定义 Agent 和它们的 handoff 能力
greeting_tools = [
    create_handoff_tool("tech_support", "当用户有技术问题时转交"),
    create_handoff_tool("billing", "当用户有账单问题时转交"),
]

tech_tools = [
    search_docs_tool,
    create_handoff_tool("greeting", "当问题解决后转回"),
]

billing_tools = [
    check_balance_tool,
    process_refund_tool,
    create_handoff_tool("greeting", "当问题解决后转回"),
]

# 创建各 Agent
greeting_agent = create_react_agent(model, greeting_tools, name="greeting")
tech_agent = create_react_agent(model, tech_tools, name="tech_support")
billing_agent = create_react_agent(model, billing_tools, name="billing")

# 构建 Swarm 图
builder = StateGraph(SwarmState)
builder.add_node("greeting", greeting_agent)
builder.add_node("tech_support", tech_agent)
builder.add_node("billing", billing_agent)
builder.add_edge(START, "greeting")  # 入口始终是接待 Agent

# 每个 Agent 可以通过 Command 跳转到任意其他 Agent
# 或者到达 END 结束对话
graph = builder.compile()

17.4.3 Swarm 的关键设计

  1. 去中心化:没有 Supervisor,Agent 之间是对等关系
  2. Handoff 工具:通过返回 Command(goto=...) 实现控制权转移
  3. 工具即路由:LLM 通过选择"转交"工具来决定路由

17.4.4 适用场景与局限

适用场景

  • 客服系统、对话路由
  • Agent 之间是专业领域的分工
  • 同一时间只有一个 Agent 活跃

局限

  • 难以协调并行任务
  • 可能出现循环转交
  • 需要每个 Agent 都了解其他 Agent 的能力

17.5 分层 Agent

17.5.1 架构概述

分层 Agent 是 Supervisor 模式的扩展:多个 Supervisor 形成树形结构,每个 Supervisor 管理一组专业 Agent 或下级 Supervisor。这种模式适用于大规模复杂任务的分解。

graph TB
    CEO[CEO Agent<br/>总调度] --> PM1[项目经理 Agent<br/>研究方向]
    CEO --> PM2[项目经理 Agent<br/>创作方向]

    PM1 --> R1[数据分析 Agent]
    PM1 --> R2[文献检索 Agent]

    PM2 --> W1[文案撰写 Agent]
    PM2 --> W2[校对编辑 Agent]

    style CEO fill:#ffe6e6,stroke:#333,stroke-width:2px
    style PM1 fill:#fff3e6,stroke:#333
    style PM2 fill:#fff3e6,stroke:#333

17.5.2 实现方式

# 第一层:专业 Agent
data_agent = create_react_agent(model, data_tools, name="data_analyst")
search_agent = create_react_agent(model, search_tools, name="searcher")
writing_agent = create_react_agent(model, writing_tools, name="writer")
editing_agent = create_react_agent(model, editing_tools, name="editor")

# 第二层:子 Supervisor
def research_supervisor(state):
    """研究方向的中层主管"""
    response = llm.invoke([
        SystemMessage(content="你管理数据分析和文献检索。决定分配给谁。"),
        *state["messages"]
    ])
    next_agent = parse_decision(response.content)
    return Command(goto=next_agent, update={"messages": [response]})

research_team = StateGraph(TeamState)
research_team.add_node("supervisor", research_supervisor)
research_team.add_node("data_analyst", data_agent)
research_team.add_node("searcher", search_agent)
research_team.add_edge(START, "supervisor")
research_team.add_edge("data_analyst", "supervisor")
research_team.add_edge("searcher", "supervisor")
research_graph = research_team.compile(name="research_team")

# 类似地创建 writing_team...

# 第三层:顶层 Supervisor
top_builder = StateGraph(TopState)
top_builder.add_node("ceo", ceo_supervisor)
top_builder.add_node("research_team", research_graph)  # 子图作为节点
top_builder.add_node("writing_team", writing_graph)
top_builder.add_edge(START, "ceo")
top_builder.add_edge("research_team", "ceo")
top_builder.add_edge("writing_team", "ceo")

top_graph = top_builder.compile()

17.5.3 子图作为节点的运行时行为

当 Pregel 执行到子图节点时:

  1. 子图的输入从父图状态中提取
  2. 子图内部按自己的超步循环运行
  3. 子图的 checkpoint_ns 自动嵌套(如 research_team:supervisor:task_id
  4. 子图完成后,输出合并回父图状态
sequenceDiagram
    participant Parent as 父图 (CEO)
    participant Sub as 子图 (Research Team)
    participant Agent as 子 Agent (Data Analyst)

    Parent->>Sub: 输入子任务
    Note over Sub: checkpoint_ns = "research_team"
    Sub->>Agent: Supervisor 分配任务
    Note over Agent: checkpoint_ns = "research_team:data_analyst:task_id"
    Agent-->>Sub: 返回结果
    Sub-->>Parent: 合并输出到父图状态

17.6 协作 Agent

17.6.1 架构概述

协作 Agent 模式中,多个 Agent 共享同一个状态空间,按照预定义的顺序或条件轮流执行。每个 Agent 都能读取和修改共享状态,形成一种"接力赛"式的协作。

graph LR
    Start[START] --> P[规划 Agent]
    P --> R[研究 Agent]
    R --> W[写作 Agent]
    W --> Rev[审核 Agent]
    Rev -->|需要修改| R
    Rev -->|通过| End[END]

    style P fill:#e6f3ff
    style R fill:#f3ffe6
    style W fill:#ffe6f3
    style Rev fill:#fff3e6

17.6.2 实现方式

class CollaborativeState(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]
    plan: str
    research_results: list[str]
    draft: str
    review_feedback: str
    iteration: int

def planner(state: CollaborativeState) -> dict:
    """规划 Agent:分析需求,制定计划"""
    response = planner_llm.invoke([
        SystemMessage(content="你是一个项目规划者。分析用户需求,制定详细的执行计划。"),
        *state["messages"]
    ])
    return {"plan": response.content, "messages": [response]}

def researcher(state: CollaborativeState) -> dict:
    """研究 Agent:根据计划收集信息"""
    response = research_llm.invoke([
        SystemMessage(content=f"执行以下研究计划:{state['plan']}"),
        *state["messages"]
    ])
    return {"research_results": [response.content], "messages": [response]}

def writer(state: CollaborativeState) -> dict:
    """写作 Agent:基于研究结果撰写"""
    context = "\n".join(state["research_results"])
    response = writer_llm.invoke([
        SystemMessage(content=f"基于以下研究结果撰写:\n{context}"),
        *state["messages"]
    ])
    return {"draft": response.content, "messages": [response]}

def reviewer(state: CollaborativeState) -> dict:
    """审核 Agent:评审草稿质量"""
    response = reviewer_llm.invoke([
        SystemMessage(content=f"审核以下草稿的质量:\n{state['draft']}"),
        *state["messages"]
    ])
    return {
        "review_feedback": response.content,
        "messages": [response],
        "iteration": state.get("iteration", 0) + 1
    }

def review_decision(state: CollaborativeState) -> str:
    """决定是否需要修改"""
    if "approved" in state["review_feedback"].lower():
        return END
    if state.get("iteration", 0) >= 3:
        return END  # 最多迭代 3 次
    return "researcher"

builder = StateGraph(CollaborativeState)
builder.add_node("planner", planner)
builder.add_node("researcher", researcher)
builder.add_node("writer", writer)
builder.add_node("reviewer", reviewer)

builder.add_edge(START, "planner")
builder.add_edge("planner", "researcher")
builder.add_edge("researcher", "writer")
builder.add_edge("writer", "reviewer")
builder.add_conditional_edges("reviewer", review_decision)

graph = builder.compile()

17.6.3 协作的关键设计

  1. 共享状态:所有 Agent 读写同一个 CollaborativeState
  2. 专用字段:每个 Agent 有自己的输出字段(plan、research_results、draft、review_feedback)
  3. 迭代循环:reviewer 可以触发新一轮的 research -> write -> review

17.7 模式对比与选择

17.7.1 四种模式对比

graph TB
    subgraph Supervisor
        S_Hub[中央调度者]
        S_A[Agent A]
        S_B[Agent B]
        S_Hub --> S_A
        S_Hub --> S_B
        S_A --> S_Hub
        S_B --> S_Hub
    end

    subgraph Swarm
        SW_A[Agent A]
        SW_B[Agent B]
        SW_C[Agent C]
        SW_A <-->|handoff| SW_B
        SW_B <-->|handoff| SW_C
    end

    subgraph 分层
        H_Top[顶层 Sup]
        H_Mid1[中层 Sup 1]
        H_Mid2[中层 Sup 2]
        H_A[Agent A]
        H_B[Agent B]
        H_Top --> H_Mid1
        H_Top --> H_Mid2
        H_Mid1 --> H_A
        H_Mid2 --> H_B
    end

    subgraph 协作
        C_A[Agent A] --> C_B[Agent B]
        C_B --> C_C[Agent C]
        C_C -.->|可选循环| C_A
    end
维度SupervisorSwarm分层协作
决策方式中央决策自主决策层级决策预定义顺序
通信模式星形(hub-spoke)点对点树形链式/环形
并行能力可通过 Send单一活跃层内可并行可通过 Send
适用规模小型(3-5 Agent)中型大型任意
复杂度
灵活性

17.7.2 选择建议

flowchart TB
    Q1{任务可以分解为<br/>独立子任务?}
    Q1 -->|是| Q2{需要动态决策<br/>任务分配?}
    Q1 -->|否| Q3{Agent 之间需要<br/>直接交接?}

    Q2 -->|是| Sup[Supervisor 模式]
    Q2 -->|否| Collab[协作 Agent 模式]

    Q3 -->|是| Swarm[Swarm 模式]
    Q3 -->|否| Q4{Agent 数量 > 5?}

    Q4 -->|是| Hier[分层 Agent 模式]
    Q4 -->|否| Sup

17.8 高级技巧

17.8.1 并行 Agent 执行

使用 Send API 让 Supervisor 同时分配多个任务:

def supervisor_with_parallel(state):
    """Supervisor 同时分配多个任务"""
    tasks = analyze_subtasks(state["messages"])
    return [
        Send(task["agent"], {"messages": state["messages"], "task": task["description"]})
        for task in tasks
    ]

17.8.2 Agent 间消息传递的状态设计

class MultiAgentState(TypedDict):
    # 公共消息历史
    messages: Annotated[list[BaseMessage], add_messages]
    # 各 Agent 的中间结果(使用 reducer 合并)
    agent_outputs: Annotated[dict[str, Any], merge_dicts]
    # 当前活跃的 Agent
    active_agent: str
    # 全局上下文
    shared_context: dict[str, Any]

17.8.3 跨 Agent 的 Store 共享

def research_agent_node(state, runtime: Runtime):
    """研究 Agent:结果保存到 Store"""
    results = do_research(state["messages"])
    runtime.store.put(
        ("shared", "research"),
        state["task_id"],
        {"results": results}
    )
    return {"messages": [AIMessage(content="研究完成")]}

def writer_agent_node(state, runtime: Runtime):
    """写作 Agent:从 Store 读取研究结果"""
    research = runtime.store.search(
        ("shared", "research"),
        limit=10
    )
    context = "\n".join(r.value["results"] for r in research)
    draft = write_with_context(context, state["messages"])
    return {"messages": [AIMessage(content=draft)]}

17.9 常见陷阱与最佳实践

17.9.1 避免无限循环

多 Agent 系统最常见的问题是循环转交——Agent A 认为应该交给 Agent B,Agent B 又认为应该交回 Agent A。解决方案:

class SafeState(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]
    handoff_count: int
    max_handoffs: int

def safe_handoff(state: SafeState, target: str) -> Command:
    """带有次数限制的安全转交"""
    if state.get("handoff_count", 0) >= state.get("max_handoffs", 10):
        return Command(
            goto=END,
            update={"messages": [AIMessage(content="已达到最大转交次数,结束对话。")]}
        )
    return Command(
        goto=target,
        update={"handoff_count": state.get("handoff_count", 0) + 1}
    )

17.9.2 状态 Schema 设计原则

多 Agent 系统的状态设计需要平衡共享与隔离:

class WellDesignedState(TypedDict):
    # 公共字段:所有 Agent 共享
    messages: Annotated[list[BaseMessage], add_messages]
    task_description: str

    # Agent 特有字段:使用 NotRequired 避免其他 Agent 被强制提供
    research_notes: NotRequired[str]
    draft_content: NotRequired[str]
    review_score: NotRequired[float]

    # 元数据字段:追踪执行过程
    current_agent: str
    iteration_count: int

17.9.3 消息历史管理

多 Agent 系统中,消息历史会快速膨胀。建议使用 pre_model_hook 或专门的消息管理节点:

from langgraph.graph.message import REMOVE_ALL_MESSAGES, RemoveMessage

def trim_messages_hook(state):
    """保留最近 20 条消息,加上系统消息"""
    messages = state["messages"]
    if len(messages) > 20:
        # 保留系统消息 + 最近 20 条
        system_msgs = [m for m in messages if isinstance(m, SystemMessage)]
        recent = messages[-20:]
        return {
            "messages": [RemoveMessage(id=REMOVE_ALL_MESSAGES)] + system_msgs + recent
        }
    return {"messages": messages}

17.10 设计决策

17.9.1 为什么用子图而不是函数调用?

将 Agent 实现为子图(而非简单的函数调用)的优势:

  1. 独立 Checkpoint:每个子图有自己的 checkpoint 命名空间,支持独立的中断恢复
  2. 可视化:子图在图的可视化中自然展示层级关系
  3. 复用:同一个 Agent 子图可以在不同的多 Agent 系统中复用
  4. 流式输出:子图的流式事件自动携带命名空间前缀,便于追踪

17.9.2 为什么 Handoff 用 Command 而不是状态更新?

Swarm 模式中使用 Command(goto=target) 而不是 return {"next_agent": target} 的原因:

  1. 原子性:Command 的 goto 和 update 在同一步中执行
  2. 类型安全:goto 的目标必须是已注册的节点名
  3. 跨图能力:Command 可以通过 graph=Command.PARENT 导航到父图

17.9.3 状态隔离 vs 状态共享

不同的多 Agent 模式对状态共享有不同的需求:

  • Supervisor:子 Agent 的状态可以通过子图隔离
  • Swarm:所有 Agent 共享同一个状态(消息历史)
  • 协作:共享状态是核心机制,每个 Agent 写入不同的字段
  • 分层:每层有自己的状态,通过子图接口传递数据

17.10 小结

本章系统介绍了基于 LangGraph 实现的四种多 Agent 模式。每种模式都是底层原语(StateGraph、Send、Command、子图)的不同组合方式:

  • Supervisor 通过 Command(goto=agent) 实现中央调度
  • Swarm 通过 handoff 工具返回 Command 实现点对点交接
  • 分层 Agent 通过子图嵌套实现多级管理
  • 协作 Agent 通过共享 State 的不同字段实现接力式协作

选择哪种模式取决于具体需求:任务是否可分解、是否需要动态路由、Agent 数量和并行需求。在实践中,这些模式也可以混合使用——例如,顶层使用 Supervisor 模式,底层使用协作模式。

LangGraph 的优势在于它不绑定任何特定的 Agent 框架模式。无论选择哪种多 Agent 架构,底层都是相同的 StateGraph + Pregel 调度器,享有相同的 Checkpoint 持久化、中断恢复、流式输出和类型安全能力。这使得开发者可以根据业务需求自由组合,而不是被框架限制在某个特定的模式中。

下一章是本书的最后一章,我们将从更高的视角审视 LangGraph 的设计模式与架构决策——Pregel 计算模型的选择、Channel 版本追踪的巧妙、Checkpoint 时间旅行的实现、中断/恢复机制的全貌,以及如何基于这些思想构建你自己的工作流引擎。