总结之LangGraph 多智能体 Supervisor

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())