LangGraph 多智能体 Supervisor 架构学习笔记
一、什么是 Supervisor 模式
Supervisor(主管)模式是 LangGraph 中实现多智能体协作的核心架构。它的核心思想是:
一个"主管"Agent 负责协调多个"子"Agent,根据用户任务自动分派给最合适的专家执行。
类比现实场景:一个项目经理(Supervisor)管理团队中的研究员和程序员,根据任务性质决定让谁来干。
#mermaid-svg-yrN8CvFgYlbp3rbJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yrN8CvFgYlbp3rbJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yrN8CvFgYlbp3rbJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yrN8CvFgYlbp3rbJ .error-icon{fill:#552222;}#mermaid-svg-yrN8CvFgYlbp3rbJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yrN8CvFgYlbp3rbJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yrN8CvFgYlbp3rbJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yrN8CvFgYlbp3rbJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yrN8CvFgYlbp3rbJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yrN8CvFgYlbp3rbJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yrN8CvFgYlbp3rbJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yrN8CvFgYlbp3rbJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yrN8CvFgYlbp3rbJ .marker.cross{stroke:#333333;}#mermaid-svg-yrN8CvFgYlbp3rbJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yrN8CvFgYlbp3rbJ p{margin:0;}#mermaid-svg-yrN8CvFgYlbp3rbJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yrN8CvFgYlbp3rbJ .cluster-label text{fill:#333;}#mermaid-svg-yrN8CvFgYlbp3rbJ .cluster-label span{color:#333;}#mermaid-svg-yrN8CvFgYlbp3rbJ .cluster-label span p{background-color:transparent;}#mermaid-svg-yrN8CvFgYlbp3rbJ .label text,#mermaid-svg-yrN8CvFgYlbp3rbJ span{fill:#333;color:#333;}#mermaid-svg-yrN8CvFgYlbp3rbJ .node rect,#mermaid-svg-yrN8CvFgYlbp3rbJ .node circle,#mermaid-svg-yrN8CvFgYlbp3rbJ .node ellipse,#mermaid-svg-yrN8CvFgYlbp3rbJ .node polygon,#mermaid-svg-yrN8CvFgYlbp3rbJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yrN8CvFgYlbp3rbJ .rough-node .label text,#mermaid-svg-yrN8CvFgYlbp3rbJ .node .label text,#mermaid-svg-yrN8CvFgYlbp3rbJ .image-shape .label,#mermaid-svg-yrN8CvFgYlbp3rbJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-yrN8CvFgYlbp3rbJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yrN8CvFgYlbp3rbJ .rough-node .label,#mermaid-svg-yrN8CvFgYlbp3rbJ .node .label,#mermaid-svg-yrN8CvFgYlbp3rbJ .image-shape .label,#mermaid-svg-yrN8CvFgYlbp3rbJ .icon-shape .label{text-align:center;}#mermaid-svg-yrN8CvFgYlbp3rbJ .node.clickable{cursor:pointer;}#mermaid-svg-yrN8CvFgYlbp3rbJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yrN8CvFgYlbp3rbJ .arrowheadPath{fill:#333333;}#mermaid-svg-yrN8CvFgYlbp3rbJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yrN8CvFgYlbp3rbJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yrN8CvFgYlbp3rbJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yrN8CvFgYlbp3rbJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yrN8CvFgYlbp3rbJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yrN8CvFgYlbp3rbJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yrN8CvFgYlbp3rbJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yrN8CvFgYlbp3rbJ .cluster text{fill:#333;}#mermaid-svg-yrN8CvFgYlbp3rbJ .cluster span{color:#333;}#mermaid-svg-yrN8CvFgYlbp3rbJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yrN8CvFgYlbp3rbJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yrN8CvFgYlbp3rbJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-yrN8CvFgYlbp3rbJ .icon-shape,#mermaid-svg-yrN8CvFgYlbp3rbJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yrN8CvFgYlbp3rbJ .icon-shape p,#mermaid-svg-yrN8CvFgYlbp3rbJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yrN8CvFgYlbp3rbJ .icon-shape .label rect,#mermaid-svg-yrN8CvFgYlbp3rbJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yrN8CvFgYlbp3rbJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yrN8CvFgYlbp3rbJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yrN8CvFgYlbp3rbJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户
Supervisor 主管
research_expert 研究专家
code_expert 代码专家
二、create_supervisor 源码解析
2.1 没有隐藏的系统提示词
create_supervisor 不会添加任何隐藏 prompt ,你传入的 prompt 参数就是唯一的系统提示词。这意味着你完全掌控 Supervisor 的行为。
python
workflow = create_supervisor(
[research_agent, code_agent],
model=llm,
prompt="你是一个团队主管,管理两个专家...", # ← 这就是全部系统提示词
)
2.2 真正的"魔法":自动生成的 Handoff 工具
Supervisor 的分派能力不靠提示词,而是靠框架自动生成的转移工具:
| 自动生成的工具 | 生成给谁 | 作用 |
|---|---|---|
transfer_to_research_expert |
Supervisor | 路由到研究专家 |
transfer_to_code_expert |
Supervisor | 路由到代码专家 |
transfer_back_to_supervisor |
每个子 Agent | 任务完成后返回主管 |
源码核心逻辑(简化):
python
# 框架为每个子 Agent 自动创建 handoff 工具
@tool(name="transfer_to_research_expert", description="Ask agent 'research_expert' for help")
def handoff_to_agent(state, tool_call_id):
return Command(goto="research_expert", ...) # 路由到对应 Agent
# 框架为每个子 Agent 注入返回工具
@tool(name="transfer_back_to_supervisor")
def transfer_back(state, tool_call_id):
return Command(goto="supervisor", ...) # 路由回 Supervisor
2.3 Supervisor 本身就是一个 ReAct Agent
框架内部等价于:
python
supervisor_agent = create_react_agent(
name="supervisor",
model=model,
tools=[transfer_to_research_expert, transfer_to_code_expert],
prompt=prompt, # 你传入的提示词
)
2.4 完整的 StateGraph 路由
#mermaid-svg-UMkhM1K5EKQMnP4e{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UMkhM1K5EKQMnP4e .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UMkhM1K5EKQMnP4e .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UMkhM1K5EKQMnP4e .error-icon{fill:#552222;}#mermaid-svg-UMkhM1K5EKQMnP4e .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UMkhM1K5EKQMnP4e .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UMkhM1K5EKQMnP4e .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UMkhM1K5EKQMnP4e .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UMkhM1K5EKQMnP4e .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UMkhM1K5EKQMnP4e .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UMkhM1K5EKQMnP4e .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UMkhM1K5EKQMnP4e .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UMkhM1K5EKQMnP4e .marker.cross{stroke:#333333;}#mermaid-svg-UMkhM1K5EKQMnP4e svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UMkhM1K5EKQMnP4e p{margin:0;}#mermaid-svg-UMkhM1K5EKQMnP4e .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UMkhM1K5EKQMnP4e .cluster-label text{fill:#333;}#mermaid-svg-UMkhM1K5EKQMnP4e .cluster-label span{color:#333;}#mermaid-svg-UMkhM1K5EKQMnP4e .cluster-label span p{background-color:transparent;}#mermaid-svg-UMkhM1K5EKQMnP4e .label text,#mermaid-svg-UMkhM1K5EKQMnP4e span{fill:#333;color:#333;}#mermaid-svg-UMkhM1K5EKQMnP4e .node rect,#mermaid-svg-UMkhM1K5EKQMnP4e .node circle,#mermaid-svg-UMkhM1K5EKQMnP4e .node ellipse,#mermaid-svg-UMkhM1K5EKQMnP4e .node polygon,#mermaid-svg-UMkhM1K5EKQMnP4e .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UMkhM1K5EKQMnP4e .rough-node .label text,#mermaid-svg-UMkhM1K5EKQMnP4e .node .label text,#mermaid-svg-UMkhM1K5EKQMnP4e .image-shape .label,#mermaid-svg-UMkhM1K5EKQMnP4e .icon-shape .label{text-anchor:middle;}#mermaid-svg-UMkhM1K5EKQMnP4e .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UMkhM1K5EKQMnP4e .rough-node .label,#mermaid-svg-UMkhM1K5EKQMnP4e .node .label,#mermaid-svg-UMkhM1K5EKQMnP4e .image-shape .label,#mermaid-svg-UMkhM1K5EKQMnP4e .icon-shape .label{text-align:center;}#mermaid-svg-UMkhM1K5EKQMnP4e .node.clickable{cursor:pointer;}#mermaid-svg-UMkhM1K5EKQMnP4e .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UMkhM1K5EKQMnP4e .arrowheadPath{fill:#333333;}#mermaid-svg-UMkhM1K5EKQMnP4e .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UMkhM1K5EKQMnP4e .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UMkhM1K5EKQMnP4e .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UMkhM1K5EKQMnP4e .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UMkhM1K5EKQMnP4e .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UMkhM1K5EKQMnP4e .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UMkhM1K5EKQMnP4e .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UMkhM1K5EKQMnP4e .cluster text{fill:#333;}#mermaid-svg-UMkhM1K5EKQMnP4e .cluster span{color:#333;}#mermaid-svg-UMkhM1K5EKQMnP4e div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UMkhM1K5EKQMnP4e .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UMkhM1K5EKQMnP4e rect.text{fill:none;stroke-width:0;}#mermaid-svg-UMkhM1K5EKQMnP4e .icon-shape,#mermaid-svg-UMkhM1K5EKQMnP4e .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UMkhM1K5EKQMnP4e .icon-shape p,#mermaid-svg-UMkhM1K5EKQMnP4e .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UMkhM1K5EKQMnP4e .icon-shape .label rect,#mermaid-svg-UMkhM1K5EKQMnP4e .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UMkhM1K5EKQMnP4e .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UMkhM1K5EKQMnP4e .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UMkhM1K5EKQMnP4e :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} transfer_to_research_expert
transfer_to_code_expert
transfer_back_to_supervisor
transfer_back_to_supervisor
不再调用工具
START
Supervisor
research_expert
code_expert
END
2.5 一句话总结
Supervisor 的"智能分派"本质上就是:LLM 看到工具列表
[transfer_to_research_expert, transfer_to_code_expert],根据任务内容选择调用哪个工具,框架负责路由。
三、完整代码示例
3.1 定义工具
为子 Agent 定义专用工具。这里提供两个工具:计算器(calculator)和 Python 代码执行器(python_executor)。
python
from langchain_core.tools import tool
@tool
def calculator(expression: str) -> str:
"""计算数学表达式,如 '2 + 3 * 4',返回计算结果"""
try:
result = eval(expression, {"__builtins__": {}})
return f"{expression} = {result}"
except Exception as e:
return f"计算失败: {e}"
@tool
def python_executor(code: str) -> str:
"""执行 Python 代码并返回输出结果"""
import io, contextlib
output = io.StringIO()
try:
with contextlib.redirect_stdout(output):
exec(code, {"__builtins__": __builtins__})
result = output.getvalue()
return result if result else "(无输出)"
except Exception as e:
return f"执行错误: {e}"
3.2 创建子 Agent
每个子 Agent 有独立的模型、工具集和系统提示词。
python
from langgraph.prebuilt import create_react_agent
# 研究专家:纯知识回答,不使用任何工具
research_agent = create_react_agent(
model=llm,
tools=[],
name="research_expert",
prompt="你是一个研究专家,擅长分析问题、提供思路和方案。你不写代码,只负责分析和给出建议。",
)
# 代码专家:可以使用计算器和代码执行工具
code_agent = create_react_agent(
model=llm,
tools=[calculator, python_executor],
name="code_expert",
prompt="你是一个代码专家,擅长编程和计算。你可以使用 calculator 和 python_executor 工具。",
)
3.3 创建 Supervisor 并编译
python
from langgraph_supervisor import create_supervisor
from langgraph.checkpoint.memory import MemorySaver
# 创建 Supervisor 工作流
workflow = create_supervisor(
[research_agent, code_agent],
model=llm,
prompt=(
"你是一个团队主管,管理两个专家:\n"
"1. research_expert(研究专家):擅长分析问题、提供思路\n"
"2. code_expert(代码专家):擅长编程、计算\n"
"根据用户任务,合理分派给合适的专家。"
),
)
# 编译(带内存记忆,支持多轮对话)
memory = MemorySaver()
app = workflow.compile(checkpointer=memory)
3.4 运行测试
使用 astream 流式获取每一步执行过程:
python
import asyncio
from langchain_core.runnables import RunnableConfig
async def run():
config = RunnableConfig(
configurable={"thread_id": "test-001"},
recursion_limit=50,
)
async for chunk in app.astream(
input={"messages": [("user", "计算 (123 + 456) * 789")]},
config=config,
):
print(chunk)
asyncio.run(run())
四、执行流程分析
以任务 "计算 (123 + 456) * 789" 为例,完整的执行流程如下:
calculator code_expert Supervisor 用户 calculator code_expert Supervisor 用户 #mermaid-svg-Kn6eBrJPEjdRZesM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Kn6eBrJPEjdRZesM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Kn6eBrJPEjdRZesM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Kn6eBrJPEjdRZesM .error-icon{fill:#552222;}#mermaid-svg-Kn6eBrJPEjdRZesM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Kn6eBrJPEjdRZesM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Kn6eBrJPEjdRZesM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Kn6eBrJPEjdRZesM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Kn6eBrJPEjdRZesM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Kn6eBrJPEjdRZesM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Kn6eBrJPEjdRZesM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Kn6eBrJPEjdRZesM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Kn6eBrJPEjdRZesM .marker.cross{stroke:#333333;}#mermaid-svg-Kn6eBrJPEjdRZesM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Kn6eBrJPEjdRZesM p{margin:0;}#mermaid-svg-Kn6eBrJPEjdRZesM .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Kn6eBrJPEjdRZesM text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-Kn6eBrJPEjdRZesM .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Kn6eBrJPEjdRZesM .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-Kn6eBrJPEjdRZesM .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-Kn6eBrJPEjdRZesM .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-Kn6eBrJPEjdRZesM #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-Kn6eBrJPEjdRZesM .sequenceNumber{fill:white;}#mermaid-svg-Kn6eBrJPEjdRZesM #sequencenumber{fill:#333;}#mermaid-svg-Kn6eBrJPEjdRZesM #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-Kn6eBrJPEjdRZesM .messageText{fill:#333;stroke:none;}#mermaid-svg-Kn6eBrJPEjdRZesM .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Kn6eBrJPEjdRZesM .labelText,#mermaid-svg-Kn6eBrJPEjdRZesM .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-Kn6eBrJPEjdRZesM .loopText,#mermaid-svg-Kn6eBrJPEjdRZesM .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-Kn6eBrJPEjdRZesM .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Kn6eBrJPEjdRZesM .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Kn6eBrJPEjdRZesM .noteText,#mermaid-svg-Kn6eBrJPEjdRZesM .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-Kn6eBrJPEjdRZesM .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Kn6eBrJPEjdRZesM .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Kn6eBrJPEjdRZesM .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Kn6eBrJPEjdRZesM .actorPopupMenu{position:absolute;}#mermaid-svg-Kn6eBrJPEjdRZesM .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-Kn6eBrJPEjdRZesM .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Kn6eBrJPEjdRZesM .actor-man circle,#mermaid-svg-Kn6eBrJPEjdRZesM line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-Kn6eBrJPEjdRZesM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 计算 (123+456)*789LLM 分析任务类型transfer_to_code_expert()LLM 分析如何计算calculator("(123+456)*789")455,793transfer_back_to_supervisor()结果是 455,793
LLM 调用次数统计:
| 步骤 | 节点 | 动作 |
|---|---|---|
| 1 | supervisor | 分析任务,决定分派给 code_expert |
| 2 | code_expert | 决定调用 calculator 工具 |
| 3 | supervisor | 收到结果,生成最终回复 |
共 3 次 LLM 调用。
五、开发踩坑记录
5.1 DeepSeek 思考模型的 reasoning_content 问题
问题: 使用 DeepSeek 思考模型(如 deepseek-v4-pro)时,多智能体消息传递中报 400 错误:
The `reasoning_content` in the thinking mode must be passed back to the API.
原因: 思考模型在回复中附带 reasoning_content(思考过程),Supervisor 把子 Agent 的消息传递给下一个 Agent 时,这个字段在 LangChain 的消息序列化过程中丢失了。
尝试过的方案:
| 方案 | 结果 |
|---|---|
model_kwargs={"reasoning": {"enabled": False}} |
langchain-openai 识别为特殊参数,切换到 /responses API,404 |
extra_body={"reasoning": {"enabled": False}} |
参数传递正确但未生效,仍然返回 reasoning_content |
使用 deepseek-chat(非思考模型) |
成功 |
最终方案:
python
llm_no_think = ChatOpenAI(
api_key=api_key,
base_url=base_url,
model="deepseek-chat", # 非思考模型,不产生 reasoning_content
temperature=0,
)
5.2 子 Agent 回答过长导致重复调用
问题: 研究专家的回答被截断后,Supervisor 认为任务未完成,再次分派给研究专家。
解决方案:
- 增大
recursion_limit - 在子 Agent 的 prompt 中要求"回答简洁,控制在 500 字以内"
- 在 Supervisor 的 prompt 中加入"如果专家已经给出了部分回答,直接总结即可"
六、Supervisor vs 其他多智能体模式
| 模式 | 特点 | 适用场景 |
|---|---|---|
| Supervisor | 一个主管统一调度,子 Agent 不直接通信 | 任务分工明确、需要集中管控 |
| Swarm | Agent 之间可以直接互相转移 | 扁平化协作、流水线任务 |
| Hierarchical | 多层 Supervisor,主管管主管 | 大型复杂项目、多级分工 |
七、关键 API 速查
python
# 创建子 Agent
from langgraph.prebuilt import create_react_agent
agent = create_react_agent(model=llm, tools=[], name="agent_name", prompt="...")
# 创建 Supervisor
from langgraph_supervisor import create_supervisor
workflow = create_supervisor([agent1, agent2], model=llm, prompt="...")
# 编译并运行
app = workflow.compile(checkpointer=MemorySaver())
result = app.invoke({"messages": [("user", "你的任务")]}, config=config)
# 流式输出
async for chunk in app.astream(input={"messages": [("user", "任务")]}, config=config):
print(chunk)
八、完整代码
import sys
import os
import asyncio
# 将项目根目录加入 sys.path,支持直接运行
_project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
if _project_root not in sys.path:
sys.path.insert(0, _project_root)
from langchain_core.messages import convert_to_messages
from langchain_core.runnables import RunnableConfig
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import create_react_agent
from langgraph_supervisor import create_supervisor
from app.common import llm
# ============================================================
# 创建专用 LLM 实例(使用非思考模型,避免多智能体传递时 reasoning_content 丢失)
# ============================================================
# DeepSeek 思考模型(如 deepseek-v4-pro)会返回 reasoning_content,
# 在 Supervisor 多智能体消息传递中这个字段会丢失,导致 API 报 400 错误。
# 解决方案:直接使用 deepseek-chat(非思考模型)。
llm_no_think = ChatOpenAI(
api_key=llm.LLM_API_KEY,
base_url=llm.LLM_BASE_URL,
model="deepseek-chat",
temperature=llm.LLM_TEMPERATURE,
)
print(f"[INFO] Supervisor 使用模型: deepseek-chat(非思考模型)")
print(f"[INFO] 当前 Base URL: {llm.LLM_BASE_URL}")
# ============================================================
# 定义工具(供子 Agent 使用)
# ============================================================
@tool
def calculator(expression: str) -> str:
"""计算数学表达式,如 '2 + 3 * 4',返回计算结果"""
try:
result = eval(expression, {"__builtins__": {}})
return f"{expression} = {result}"
except Exception as e:
return f"计算失败: {e}"
@tool
def python_executor(code: str) -> str:
"""执行 Python 代码并返回输出结果"""
import io
import contextlib
output = io.StringIO()
try:
with contextlib.redirect_stdout(output):
exec(code, {"__builtins__": __builtins__})
result = output.getvalue()
return result if result else "(无输出)"
except Exception as e:
return f"执行错误: {e}"
# ============================================================
# 日志打印函数
# ============================================================
def pretty_print_messages(update, last_message=False):
"""格式化打印 Agent 执行过程中的消息"""
is_subgraph = False
if isinstance(update, tuple):
ns, update = update
# 跳过父图的更新(只看子图)
if len(ns) == 0:
return
graph_id = ns[-1].split(":")[0]
print(f"\n [子图: {graph_id}]")
is_subgraph = True
for node_name, node_update in update.items():
update_label = f" 节点: {node_name}"
if is_subgraph:
update_label = "\t" + update_label
print(update_label)
messages = convert_to_messages(node_update["messages"])
if last_message:
messages = messages[-1:]
for m in messages:
msg_type = m.__class__.__name__
content = str(m.content)[:200] if m.content else ""
prefix = "\t" if is_subgraph else " "
if msg_type == "HumanMessage":
print(f"{prefix} 用户: {content}")
elif msg_type == "AIMessage":
if hasattr(m, 'tool_calls') and m.tool_calls:
for tc in m.tool_calls:
print(f"{prefix} 调用工具: {tc['name']}({tc['args']})")
elif content:
print(f"{prefix} AI: {content}")
elif msg_type == "ToolMessage":
print(f"{prefix} 工具结果: {content}")
else:
print(f"{prefix} [{msg_type}]: {content}")
print()
# ============================================================
# 创建子 Agent
# ============================================================
# 研究专家:擅长分析问题、信息检索、方案规划,不做任何代码
research_agent = create_react_agent(
model=llm_no_think,
tools=[], # 不使用工具,纯知识回答
name="research_expert",
prompt="你是一个研究专家,擅长分析问题、提供思路和方案。你不写代码,只负责分析问题、制定方案、给出建议。回答用中文。",
)
# 代码专家:擅长编程、计算、执行代码
code_agent = create_react_agent(
model=llm_no_think,
tools=[calculator, python_executor],
name="code_expert",
prompt="你是一个代码专家,擅长编程和计算。你可以使用 calculator 工具计算数学表达式,使用 python_executor 工具执行 Python 代码。回答用中文。",
)
# ============================================================
# 创建 Supervisor 工作流
# ============================================================
workflow = create_supervisor(
[research_agent, code_agent],
model=llm_no_think,
prompt=(
"你是一个团队主管,管理两个专家:\n"
"1. research_expert(研究专家):擅长分析问题、提供思路、制定方案\n"
"2. code_expert(代码专家):擅长编程、计算、执行代码\n"
"根据用户的任务,合理分派给合适的专家。需要分析问题时用 research_expert,"
"需要写代码或计算时用 code_expert。回答用中文。"
),
)
# 编译工作流(带内存记忆)
memory = MemorySaver()
app = workflow.compile(checkpointer=memory)
# ============================================================
# 测试函数
# ============================================================
async def run_supervisor():
"""运行 Supervisor 多智能体测试"""
# 预定义测试任务(覆盖不同场景)
test_tasks = [
"分析一下学习 Python 的最佳路径是什么?",
"请计算 (123 + 456) * 789 的结果",
"用 Python 写一个冒泡排序算法,并测试一下",
]
config = RunnableConfig(
configurable={"thread_id": "supervisor-test-001"},
recursion_limit=50,
)
for i, task in enumerate(test_tasks, 1):
print("\n" + "=" * 60)
print(f"任务 {i}/{len(test_tasks)}: {task}")
print("=" * 60)
iteration_count = 0
async for chunk in app.astream(
input={"messages": [("user", task)]},
config=config,
):
iteration_count += 1
print(f"\n--- 第 {iteration_count} 步执行 ---")
pretty_print_messages(chunk)
print(f"--- 任务 {i} 完成,共执行 {iteration_count} 步 ---")
print("\n" + "=" * 60)
print("Supervisor 测试完成")
print("=" * 60)
# ============================================================
# 交互式对话模式(可选)
# ============================================================
async def run_interactive():
"""交互式多轮对话模式"""
config = RunnableConfig(
configurable={"thread_id": "supervisor-interactive-001"},
recursion_limit=50,
)
print("\n输入 'exit' 退出对话\n")
while True:
user_input = input("用户: ")
if user_input.lower() == "exit":
print("对话结束。")
break
print("\n--- Supervisor 正在协调工作... ---")
async for chunk in app.astream(
input={"messages": [("user", user_input)]},
config=config,
):
pretty_print_messages(chunk, last_message=True)
# ============================================================
# 运行
# ============================================================
if __name__ == "__main__":
print("=" * 60)
print("LangGraph 多智能体 Supervisor 架构测试")
print("=" * 60)
# 运行预设任务测试
asyncio.run(run_supervisor())
# 如需交互式对话,取消下方注释
# asyncio.run(run_interactive())