🎯 多 Agent:Supervisor 编排 ★
单个 Agent 做复杂任务会上下文爆炸、角色混乱。多 Agent 协作把任务拆给分工明确的多个 Agent。本章深入 Supervisor 拓扑——一个中心协调者分配任务给专家 Agent,精读 LangGraph 的子图机制和 LangChain 的 create_agent middleware 系统,并手写一个完整的 Supervisor 图。
本章目标
- 掌握三种多 Agent 拓扑(Network / Supervisor / Hierarchical)及各自的适用场景
- 精读
create_agent的 middleware 系统与子图嵌入机制 - 理解
Command(graph=Command.PARENT)父子图通信原理 - 用 StateGraph 手写一个完整的 Supervisor 图(路由 + 专家子图 + 状态共享)
- 澄清本仓库不包含
create_supervisor(在独立 contrib 包)的事实
本章假设你已完成 LangGraph 八章(尤其 LG1 StateGraph、LG3 条件边、LG8 预构建 Agent)。多 Agent 是 LangGraph 图能力的综合应用。
为什么需要多 Agent
回顾 OC5 章的核心洞察:单 Agent 跑长任务会上下文爆炸。但上下文压缩只是事后补救,更主动的方案是结构性拆分——让不同的 Agent 各管一摊:
| 痛点(单 Agent) | 多 Agent 的解法 |
|---|---|
| 上下文爆炸(一个 Agent 装所有信息) | 各 Agent 独立上下文,只共享必要信息 |
| 角色混乱(一个 Agent 既写代码又写文档又测试) | 专家分工,每个 Agent 专注一域 |
| 提示词臃肿(一个 prompt 装所有工具说明) | 每个 Agent 只看自己相关工具 |
| 难并行(串行处理慢) | 独立子任务可并行 |
| 错误传播(一步错全盘乱) | 子 Agent 失败可隔离、重试 |
多 Agent 也有代价:协调开销(Agent 间通信消耗 token)、复杂度上升(调试更难)、延迟增加(多一层路由)。经验法则:任务边界清晰、各子任务较重时用多 Agent;任务连贯轻量时单 Agent 更好。OpenCode 默认就是单主 Agent(build),只在需要时通过 task 工具派生子 Agent(OC5)。
三种多 Agent 拓扑
业界归纳出三种经典拓扑。理解它们的差异是设计多 Agent 系统的基础:
| 拓扑 | 通信方式 | 优点 | 缺点 | 典型场景 |
|---|---|---|---|---|
| Network | 任意 Agent 间直接通信 | 灵活、无单点 | 协调复杂、易混乱 | Agent 数量少且对等 |
| Supervisor ★ | 都经中心 Supervisor | 清晰、可控、易调试 | Supervisor 是瓶颈/单点 | 最常用,分工明确的任务 |
| Hierarchical | 树形逐层下发 | 可伸缩、隔离好 | 层级深时延迟高 | 大型复杂项目 |
Supervisor 的工作机制
Supervisor 拓扑的核心是一个协调者 Agent,它不直接干活,而是决定"把任务派给哪个专家 Agent":
这本质上是把"路由"也变成一次 LLM 调用——Supervisor 是一个特殊的 Agent,它的"工具"就是其他 Agent。注意它和 LG3 条件边的区别:条件边是硬编码的路由逻辑,Supervisor 是LLM 动态决策的路由,更灵活但更贵。
create_agent 与 middleware 系统
现代 LangChain 用 create_agent(替代旧的 create_react_agent)构建 Agent,它的扩展点是 middleware。先精读签名:
def create_agent(
model: str | BaseChatModel,
tools: Sequence[BaseTool | Callable | dict] | None = None,
*,
system_prompt: str | SystemMessage | None = None,
middleware: Sequence[AgentMiddleware] = (), # ★ 扩展点:中间件链
response_format: ... = None,
state_schema: type[AgentState] | None = None,
context_schema: type[ContextT] | None = None,
checkpointer: Checkpointer | None = None,
store: BaseStore | None = None,
interrupt_before: list[str] | None = None,
interrupt_after: list[str] | None = None,
name: str | None = None, # ★ name 用于子图嵌入
...
) -> CompiledStateGraph:
"""创建一个循环调用工具直到停止的 agent 图。"""
注意返回类型是 CompiledStateGraph(继承 Pregel)。这意味着每个 create_agent 的产物本身就是一个图,而图可以作为另一个图的节点——这就是多 Agent 嵌套的基础。name 参数(factory.py:893 doc 明确说)"自动用作把 agent 加进另一个图作为子图节点,对构建多 Agent 系统特别有用"。
middleware:能力增强的钩子链
middleware 不是多 Agent 编排,而是单 Agent 的能力增强。它提供一组生命周期钩子:
| 内置 middleware | 作用 |
|---|---|
HumanInTheLoopMiddleware | 人在回路(工具执行前批准) |
SummarizationMiddleware | 自动摘要历史(上下文管理) |
ModelFallbackMiddleware | 模型降级(主模型失败换备用) |
ToolCallLimitMiddleware | 限制工具调用次数 |
TodoListMiddleware | 自动维护待办列表 |
钩子点(factory.py:1062):before_agent / before_model / after_model / after_agent / wrap_model_call / wrap_tool_call。理解这套钩子是写自定义 middleware 的基础。
子图与 Command.PARENT 通信
多 Agent 的核心机制是子图嵌套。一个编译好的图可以作为另一个图的节点。关键是父子图如何通信——Command(graph=Command.PARENT):
@dataclass
class Command(Generic[N], ToolOutputMixin):
"""更新图状态并发送消息的指令。"""
graph: GraphType = None
"""发送到哪个图:
- None:当前图
- Command.PARENT:最近的父图 ← 子图向父图发命令"""
update: ... = None # 状态更新
resume: ... = None # 恢复中断
goto: ... = None # 跳转到节点
PARENT: ClassVar[Literal["__parent__"]] = "__parent__" # :808 常量定义
# 在 _control_branch 内:
if command.graph == Command.PARENT:
raise ParentCommand(command)
# 子图无法自己处理 PARENT 命令,所以【抛异常】
# 异常会冒泡到父图,由父图的 _get_root (state.py:1780/1791) 接收处理
# 这就是子→父通信的底层机制
重要澄清:create_supervisor 不在主仓库
很多教程提到 from langgraph.prebuilt import create_supervisor——但本仓库的 prebuilt 不包含它(prebuilt/__init__.py:14 的 __all__ 只有 create_react_agent / ToolNode 等)。create_supervisor / create_swarm / create_handoff 在独立的 contrib 包(langgraph-supervisor、langgraph-swarm,需 pip install)。
本教程选择手写 Supervisor,这样你能真正理解机制,而不是调一个黑盒函数。理解原理后,用 contrib 包只是语法糖。
实战:手写一个 Supervisor 图
pip install langgraph langchain-openai。这是一个研究-编码-测试三专家协作的 Supervisor 系统。
# pip install langgraph langchain-openai
# export OPENAI_API_KEY="sk-..."
import operator
from typing import Annotated, Literal
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4o-mini")
# ============ ① 定义共享状态 ============
class State(TypedDict):
messages: Annotated[list, operator.add] # 消息累积(各 Agent 的输出追加)
next: str # Supervisor 决定的下一个 Agent
# ============ ② 定义专家 Agent(用 create_react_agent 构建)============
# 每个 Agent 有自己的工具和系统提示,专注一域
research_agent = create_react_agent(
model, tools=[], # 实际可加搜索工具
prompt="你是研究专家。负责调研技术方案、收集信息。简洁回答。",
)
coding_agent = create_react_agent(
model, tools=[], # 实际可加文件读写工具
prompt="你是编码专家。负责写代码实现。只输出关键代码。",
)
def research_node(state: State) -> dict:
"""把专家 Agent 包成图节点。"""
result = research_agent.invoke({"messages": state["messages"]})
# 只取最后一条 AI 消息,标注来源
last = result["messages"][-1]
last.content = f"[研究专家] {last.content}"
return {"messages": [last]}
def coding_node(state: State) -> dict:
result = coding_agent.invoke({"messages": state["messages"]})
last = result["messages"][-1]
last.content = f"[编码专家] {last.content}"
return {"messages": [last]}
# ============ ③ Supervisor 节点(用 LLM 做路由决策)============
from langchain_core.messages import HumanMessage, SystemMessage
def supervisor_node(state: State) -> dict:
"""Supervisor:用 LLM 决定下一步派给谁,或结束。"""
decision = model.invoke([
SystemMessage(content=(
"你是项目主管,协调研究专家和编码专家。"
"根据当前进度,决定下一步:\n"
"- 需要调研 → 回复 FINISH 或 RESEARCH 或 CODE\n"
"- 需要编码 → 回复 CODE\n"
"- 任务完成 → 回复 FINISH\n"
"只回复一个词。"
)),
*state["messages"],
])
choice = decision.content.strip().upper()
# 映射到节点名
next_node = {"RESEARCH": "research", "CODE": "coding", "FINISH": FINISH}.get(choice, FINISH)
return {"next": next_node} # 条件边会读这个字段
# ============ ④ 构建图 ============
graph = StateGraph(State)
graph.add_node("supervisor", supervisor_node)
graph.add_node("research", research_node)
graph.add_node("coding", coding_node)
# 入口:先问 Supervisor
graph.add_edge(START, "supervisor")
# 条件边:Supervisor 根据 next 字段路由
def route_from_supervisor(state: State) -> str:
return state["next"] # 返回 "research" / "coding" / FINISH
graph.add_conditional_edges(
"supervisor", route_from_supervisor,
{"research": "research", "coding": "coding", "FINISH": END},
)
# 专家执行完回到 Supervisor 继续决策(形成循环)
graph.add_edge("research", "supervisor")
graph.add_edge("coding", "supervisor")
app = graph.compile()
# ============ ⑤ 运行 ============
result = app.invoke({
"messages": [HumanMessage(content="帮我做一个简单的待办事项 CLI 工具")],
"next": "",
})
print("\n===== 协作过程 =====")
for m in result["messages"]:
print(f" {m.content[:80]}")
核心是 supervisor ↔ {research, coding} 的循环:Supervisor 决策 → 专家执行 → 回到 Supervisor 再决策,直到 Supervisor 说 FINISH。这就是 LG8 agent↔tools 循环的升级版——把"工具"换成了"专家 Agent"。每个专家有自己的上下文和工具集,互不干扰。
用 contrib 包简化(了解)
理解原理后,可以用 contrib 包简化上面的代码:
# pip install langgraph-supervisor ← 独立包,不在主仓库
from langgraph_supervisor import create_supervisor
# create_supervisor 把上面的手写逻辑封装成一行
# 但理解了手写版,你才知道它在做什么
进阶模式
1. 共享状态 vs 私有状态
上面例子所有 Agent 共享 messages。生产中常需要私有状态(每个 Agent 有自己的中间变量)。用子图 schema隔离:
# 父图状态
class ParentState(TypedDict):
messages: Annotated[list, operator.add]
next: str
# 子图状态(可以和父图不同!只暴露必要字段)
class ResearchState(TypedDict):
messages: Annotated[list, operator.add]
research_notes: list # ← 私有字段,不回传父图
# 子图编译时,只共享 schema 重叠的字段(messages)
2. 并行派发多个专家
用 LG3 的 Send 让 Supervisor 一次派给多个专家并行:
def supervisor_with_parallel(state):
"""Supervisor 决定并行派给多个专家。"""
return [
Send("research", {"messages": state["messages"]}),
Send("coding", {"messages": state["messages"]}),
] # 两个专家并发执行,结果由 operator.add reducer 聚合
与生产实践对照
| Supervisor 概念 | OpenCode 对应 | |
|---|---|---|
| Supervisor 路由决策 | → | 主 build agent 用 LLM 决定调 task 工具(OC2 runLoop) |
| 专家 Agent 子图 | → | explore/general 子 agent(agent.ts:182/196) |
| create_agent 产物=图 | → | 每个内置 agent 是一套配置(不是图,是 runLoop 参数) |
| Command.PARENT 子→父 | → | task 工具结果回流主会话(task.ts:199) |
| middleware 增强 | → | compaction/permission/subagent 等横切逻辑 |
OpenCode 的 build agent 本质就是 Supervisor——它自己不直接探索代码库,而是调用 task 工具派生 explore 子 agent去探索(OC5)。区别是:OpenCode 的"专家 Agent"是通过工具调用隐式触发的(LLM 决定调 task),而本节的 Supervisor 是显式图结构。两种风格各有优劣——图结构更可控可观测,工具调用更灵活自然。
小结
- 多 Agent 解决单 Agent 的上下文爆炸/角色混乱;但增加协调成本,任务边界清晰时才用。
- 三种拓扑:Network(去中心)/ Supervisor(星形,最常用)/ Hierarchical(树形,AD2 详讲)。
- Supervisor 用 LLM 做路由决策;create_agent 产物是图,可嵌套;Command.PARENT 实现子→父通信。
create_supervisor不在主仓库(独立 contrib 包),理解手写原理更重要。- OpenCode 的 build + task 是"隐式 Supervisor"的工业实现。
下一章 AD2 · 多 Agent:层级树 + Handoff:深入层级拓扑与 Handoff(控制权移交)语义,精读 OpenCode task 工具如何建立父子会话树、防递归、回流结果。