总结之LangGraph 多智能体 Supervisor

LangGraph 多智能体 Supervisor 架构学习笔记

一、什么是 Supervisor 模式

Supervisor(主管)模式是 LangGraph 中实现多智能体协作的核心架构。它的核心思想是:

一个"主管"Agent 负责协调多个"子"Agent,根据用户任务自动分派给最合适的专家执行。

类比现实场景:一个项目经理(Supervisor)管理团队中的研究员和程序员,根据任务性质决定让谁来干。

用户

Supervisor 主管

research_expert 研究专家

code_expert 代码专家


二、create_supervisor 源码解析

2.1 没有隐藏的系统提示词

create_supervisor 不会添加任何隐藏 prompt,你传入的 prompt 参数就是唯一的系统提示词。这意味着你完全掌控 Supervisor 的行为。

workflow = create_supervisor(
    [research_agent, code_agent],
    model=llm,
    prompt="你是一个团队主管,管理两个专家...",  # ← 这就是全部系统提示词
)

2.2 真正的"魔法":自动生成的 Handoff 工具

Supervisor 的分派能力不靠提示词,而是靠框架自动生成的转移工具

自动生成的工具生成给谁作用
transfer_to_research_expertSupervisor路由到研究专家
transfer_to_code_expertSupervisor路由到代码专家
transfer_back_to_supervisor每个子 Agent任务完成后返回主管

源码核心逻辑(简化):

# 框架为每个子 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

框架内部等价于:

supervisor_agent = create_react_agent(
    name="supervisor",
    model=model,
    tools=[transfer_to_research_expert, transfer_to_code_expert],
    prompt=prompt,  # 你传入的提示词
)

2.4 完整的 StateGraph 路由

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

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 有独立的模型、工具集和系统提示词。

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 并编译

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 流式获取每一步执行过程:

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" 为例,完整的执行流程如下:

calculatorcode_expertSupervisor用户calculatorcode_expertSupervisor用户计算 (123+456)*789LLM 分析任务类型transfer_to_code_expert()LLM 分析如何计算calculator("(123+456)*789")455,793transfer_back_to_supervisor()结果是 455,793

LLM 调用次数统计:

步骤节点动作
1supervisor分析任务,决定分派给 code_expert
2code_expert决定调用 calculator 工具
3supervisor收到结果,生成最终回复

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(非思考模型)成功

最终方案:

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 不直接通信任务分工明确、需要集中管控
SwarmAgent 之间可以直接互相转移扁平化协作、流水线任务
Hierarchical多层 Supervisor,主管管主管大型复杂项目、多级分工

七、关键 API 速查

# 创建子 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())

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值