概览

supervisor pattern 是一种 multi-agent 架构,其中中心 supervisor agent 会协调专门的工作 agent。当任务需要不同类型的专业能力时,这种方法尤其有效。与其构建一个跨领域管理工具选择的 agent,不如创建多个聚焦的专家,并由理解整体工作流的 supervisor 负责协调。 在本教程中,你将构建一个个人助手系统,并通过贴近真实场景的工作流展示这些优势。系统将协调两个职责根本不同的专家:
  • 一个处理日程安排、可用性检查和事件管理的 calendar agent
  • 一个管理沟通、起草消息和发送通知的 email agent
你还将加入 human-in-the-loop 审查,让用户可以按需批准、编辑和拒绝操作,例如外发电子邮件。

为什么使用 supervisor?

Multi-agent 架构允许你将工具分配给不同 worker,每个 worker 都有自己的 prompt 或指令。设想一个可以直接访问所有日历和电子邮件 API 的 agent:它必须从许多相似工具中做选择,理解每个 API 要求的精确格式,并同时处理多个领域。如果性能下降,将相关工具及其 prompt 拆分为逻辑分组会很有帮助,也便于迭代改进。

概念

本教程将介绍以下概念:

设置

安装

本教程需要 langchain 包:
pip install langchain
更多详情,请参阅安装指南

LangSmith

设置 LangSmith,以检查 agent 内部发生了什么。然后设置以下环境变量:
export LANGSMITH_TRACING="true"
export LANGSMITH_API_KEY="..."

组件

你需要从 LangChain 的集成套件中选择一个聊天模型:
👉 Read the OpenAI chat model integration docs
pip install -U "langchain[openai]"
import os
from langchain.chat_models import init_chat_model

os.environ["OPENAI_API_KEY"] = "sk-..."

model = init_chat_model("gpt-5.4")

1. 定义工具

首先定义需要结构化输入的工具。在真实应用中,这些工具会调用实际 API,例如 Google Calendar、SendGrid 等。在本教程中,你将使用 stub 来演示这种模式。
from langchain.tools import tool

@tool
def create_calendar_event(
    title: str,
    start_time: str,       # ISO 格式:"2024-01-15T14:00:00"
    end_time: str,         # ISO 格式:"2024-01-15T15:00:00"
    attendees: list[str],  # 电子邮件地址
    location: str = ""
) -> str:
    """创建日历事件。需要精确的 ISO 日期时间格式。"""
    # Stub:在实践中,这会调用 Google Calendar API、Outlook API 等。
    return f"Event created: {title} from {start_time} to {end_time} with {len(attendees)} attendees"


@tool
def send_email(
    to: list[str],  # 电子邮件地址
    subject: str,
    body: str,
    cc: list[str] = []
) -> str:
    """通过电子邮件 API 发送邮件。需要格式正确的地址。"""
    # Stub:在实践中,这会调用 SendGrid、Gmail API 等。
    return f"Email sent to {', '.join(to)} - Subject: {subject}"


@tool
def get_available_time_slots(
    attendees: list[str],
    date: str,  # ISO 格式:"2024-01-15"
    duration_minutes: int
) -> list[str]:
    """检查指定日期中给定参会者的日历可用性。"""
    # Stub:在实践中,这会查询日历 API
    return ["09:00", "14:00", "16:00"]

2. 创建专门的 sub-agents

接下来,你将创建处理各个领域的专门 sub-agents。

创建 calendar agent

Calendar agent 会理解自然语言日程安排请求,并将其转换为精确的 API 调用。它会处理日期解析、可用性检查和事件创建。
from langchain.agents import create_agent


CALENDAR_AGENT_PROMPT = (
    "你是日历日程安排助手。"
    "将自然语言日程安排请求(例如“下周二下午 2 点”)"
    "解析为正确的 ISO 日期时间格式。"
    "需要时使用 get_available_time_slots 检查可用性。"
    "如果没有合适的时间段,请停止并在回复中确认不可用。"
    "使用 create_calendar_event 安排事件。"
    "始终在最终回复中确认已安排的内容。"
)

calendar_agent = create_agent(
    model,
    tools=[create_calendar_event, get_available_time_slots],
    system_prompt=CALENDAR_AGENT_PROMPT,
)
测试 calendar agent,查看它如何处理自然语言日程安排:
query = "将团队会议安排在下周二下午 2 点,持续 1 小时"

for step in calendar_agent.stream(
    {"messages": [{"role": "user", "content": query}]}
):
    for update in step.values():
        for message in update.get("messages", []):
            message.pretty_print()
================================== Ai Message ==================================
Tool Calls:
  get_available_time_slots (call_EIeoeIi1hE2VmwZSfHStGmXp)
 Call ID: call_EIeoeIi1hE2VmwZSfHStGmXp
  Args:
    attendees: []
    date: 2024-06-18
    duration_minutes: 60
================================= Tool Message =================================
Name: get_available_time_slots

["09:00", "14:00", "16:00"]
================================== Ai Message ==================================
Tool Calls:
  create_calendar_event (call_zgx3iJA66Ut0W8S3NpT93kEB)
 Call ID: call_zgx3iJA66Ut0W8S3NpT93kEB
  Args:
    title: 团队会议
    start_time: 2024-06-18T14:00:00
    end_time: 2024-06-18T15:00:00
    attendees: []
================================= Tool Message =================================
Name: create_calendar_event

Event created: 团队会议 from 2024-06-18T14:00:00 to 2024-06-18T15:00:00 with 0 attendees
================================== Ai Message ==================================

团队会议已安排在下周二 6 月 18 日下午 2:00,持续 1 小时。如果需要添加参会者或地点,请告诉我!
Agent 将“下周二下午 2 点”解析为 ISO 格式("2024-01-16T14:00:00"),计算结束时间,调用 create_calendar_event,并返回自然语言确认。

创建 email agent

Email agent 负责消息撰写和发送。它专注于提取收件人信息、生成合适的主题行和正文,并管理电子邮件沟通。
EMAIL_AGENT_PROMPT = (
    "你是电子邮件助手。"
    "根据自然语言请求撰写专业邮件。"
    "提取收件人信息,并生成合适的主题行和正文。"
    "使用 send_email 发送消息。"
    "始终在最终回复中确认已发送的内容。"
)

email_agent = create_agent(
    model,
    tools=[send_email],
    system_prompt=EMAIL_AGENT_PROMPT,
)
使用自然语言请求测试 email agent:
query = "向设计团队发送提醒,请他们审阅新的 mockups"

for step in email_agent.stream(
    {"messages": [{"role": "user", "content": query}]}
):
    for update in step.values():
        for message in update.get("messages", []):
            message.pretty_print()
================================== Ai Message ==================================
Tool Calls:
  send_email (call_OMl51FziTVY6CRZvzYfjYOZr)
 Call ID: call_OMl51FziTVY6CRZvzYfjYOZr
  Args:
    to: ['design-team@example.com']
    subject: 提醒:请审阅新的 mockups
    body: 设计团队你好,

这是一封友好提醒,请你们尽早审阅新的 mockups。你们的反馈很重要,可以帮助确保项目时间线按计划推进。

如果有任何问题或需要更多信息,请告诉我。

谢谢!

此致,
================================= Tool Message =================================
Name: send_email

Email sent to design-team@example.com - Subject: 提醒:请审阅新的 mockups
================================== Ai Message ==================================

我已向设计团队发送提醒,请他们审阅新的 mockups。如果你还需要围绕这个主题进行其他沟通,请告诉我!
Agent 会从非正式请求中推断收件人,生成专业的主题行和正文,调用 send_email,并返回确认。每个 sub-agent 都聚焦于狭窄任务,并配有特定领域的工具和 prompt,因此能更好地完成自己的任务。

3. 将 sub-agents 包装成工具

现在,将每个 sub-agent 包装成 supervisor 可以调用的工具。这是创建分层系统的关键架构步骤。Supervisor 会看到 "schedule_event" 这样的高层工具,而不是 "create_calendar_event" 这样的低层工具。
@tool
def schedule_event(request: str) -> str:
    """使用自然语言安排日历事件。

    当用户想要创建、修改或检查日历约会时使用。
    处理日期/时间解析、可用性检查和事件创建。

    输入:自然语言日程安排请求(例如“下周二下午 2 点和设计团队开会”)
    """
    result = calendar_agent.invoke({
        "messages": [{"role": "user", "content": request}]
    })
    return result["messages"][-1].text


@tool
def manage_email(request: str) -> str:
    """使用自然语言发送电子邮件。

    当用户想要发送通知、提醒或任何电子邮件沟通时使用。
    处理收件人提取、主题生成和邮件撰写。

    输入:自然语言电子邮件请求(例如“向他们发送会议提醒”)
    """
    result = email_agent.invoke({
        "messages": [{"role": "user", "content": request}]
    })
    return result["messages"][-1].text
工具描述会帮助 supervisor 判断何时使用每个工具,因此要让描述清晰且具体。这里仅返回 sub-agent 的最终响应,因为 supervisor 不需要看到中间推理或工具调用。

4. 创建 supervisor agent

现在创建用于编排 sub-agents 的 supervisor。Supervisor 只会看到高层工具,并在领域层级做路由决策,而不是在单个 API 层级做决策。
SUPERVISOR_PROMPT = (
    "你是乐于助人的个人助手。"
    "你可以安排日历事件并发送电子邮件。"
    "将用户请求拆解为合适的工具调用,并协调结果。"
    "当请求涉及多个操作时,按顺序使用多个工具。"
)

supervisor_agent = create_agent(
    model,
    tools=[schedule_event, manage_email],
    system_prompt=SUPERVISOR_PROMPT,
)

5. 使用 supervisor

现在,使用需要跨多个领域协调的复杂请求测试完整系统:

示例 1:简单单领域请求

query = "将团队站会安排在明天上午 9 点"

for step in supervisor_agent.stream(
    {"messages": [{"role": "user", "content": query}]}
):
    for update in step.values():
        for message in update.get("messages", []):
            message.pretty_print()
================================== Ai Message ==================================
Tool Calls:
  schedule_event (call_mXFJJDU8bKZadNUZPaag8Lct)
 Call ID: call_mXFJJDU8bKZadNUZPaag8Lct
  Args:
    request: 将 Alice 和 Bob 的团队站会安排在明天上午 9 点。
================================= Tool Message =================================
Name: schedule_event

团队站会已安排在明天上午 9:00,参会者包括 Alice 和 Bob。如果你需要修改或添加更多详情,请告诉我!
================================== Ai Message ==================================

Alice 和 Bob 的团队站会已安排在明天上午 9:00。如果还需要其他安排或调整,请告诉我!
Supervisor 会识别出这是日历任务,调用 schedule_event,然后 calendar agent 处理日期解析和事件创建。
如需完整了解信息流,包括每次聊天模型调用的 prompt 和响应,请查看上述运行的 LangSmith trace

示例 2:复杂多领域请求

query = (
    "将和设计团队的会议安排在下周二下午 2 点,持续 1 小时,"
    "并给他们发送电子邮件提醒,请他们审阅新的 mockups。"
)

for step in supervisor_agent.stream(
    {"messages": [{"role": "user", "content": query}]}
):
    for update in step.values():
        for message in update.get("messages", []):
            message.pretty_print()
================================== Ai Message ==================================
Tool Calls:
  schedule_event (call_YA68mqF0koZItCFPx0kGQfZi)
 Call ID: call_YA68mqF0koZItCFPx0kGQfZi
  Args:
    request: 下周二下午 2 点和设计团队开会,持续 1 小时
  manage_email (call_XxqcJBvVIuKuRK794ZIzlLxx)
 Call ID: call_XxqcJBvVIuKuRK794ZIzlLxx
  Args:
    request: 给设计团队发送电子邮件提醒,请他们审阅新的 mockups
================================= Tool Message =================================
Name: schedule_event

你与设计团队的会议已安排在下周二 6 月 18 日下午 2:00 到 3:00。如果你需要添加更多详情或进行修改,请告诉我!
================================= Tool Message =================================
Name: manage_email

我已向设计团队发送电子邮件提醒,请他们审阅新的 mockups。如果需要包含更多信息或收件人,请告诉我!
================================== Ai Message ==================================

你与设计团队的会议已安排在下周二 6 月 18 日下午 2:00 到 3:00。

我也已向设计团队发送电子邮件提醒,请他们审阅新的 mockups。

如果你想向会议添加更多详情,或在邮件中加入其他信息,请告诉我!
Supervisor 识别出该请求同时需要日历和电子邮件操作,于是为会议调用 schedule_event,然后为提醒调用 manage_email。每个 sub-agent 完成自己的任务,supervisor 再将两个结果合成为连贯响应。
请参阅 LangSmith trace,查看上述运行的详细信息流,包括每次聊天模型调用的 prompt 和响应。

完整工作示例

以下是整合后的可运行脚本:

理解架构

你的系统有三层。底层包含需要精确格式的严格 API 工具。中间层包含 sub-agents,它们接收自然语言,将其转换为结构化 API 调用,并返回自然语言确认。顶层包含 supervisor,它会路由到高层能力并综合结果。 这种关注点分离带来多个好处:每一层都有聚焦的职责,你可以添加新领域而不影响现有领域,也可以独立测试和迭代每一层。

6. 添加 human-in-the-loop 审查

对于敏感操作,加入 human-in-the-loop 审查 是谨慎做法。LangChain 提供内置 middleware 用于审查工具调用,在本例中也就是 sub-agents 调用的工具。 为两个 sub-agents 添加 human-in-the-loop 审查:
  • create_calendar_eventsend_email 工具配置为触发中断,并允许所有响应类型approveeditreject
  • 仅向顶层 agent 添加 checkpointer。暂停和恢复执行需要它。
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware 
from langgraph.checkpoint.memory import InMemorySaver 


calendar_agent = create_agent(
    model,
    tools=[create_calendar_event, get_available_time_slots],
    system_prompt=CALENDAR_AGENT_PROMPT,
    middleware=[
        HumanInTheLoopMiddleware(
            interrupt_on={"create_calendar_event": True},
            description_prefix="日历事件待批准",
        ),
    ],
)

email_agent = create_agent(
    model,
    tools=[send_email],
    system_prompt=EMAIL_AGENT_PROMPT,
    middleware=[
        HumanInTheLoopMiddleware(
            interrupt_on={"send_email": True},
            description_prefix="外发电子邮件待批准",
        ),
    ],
)

supervisor_agent = create_agent(
    model,
    tools=[schedule_event, manage_email],
    system_prompt=SUPERVISOR_PROMPT,
    checkpointer=InMemorySaver(),
)
重复同一个查询。请注意,这里会将中断事件收集到列表中,便于后续访问:
query = (
    "将和设计团队的会议安排在下周二下午 2 点,持续 1 小时,"
    "并给他们发送电子邮件提醒,请他们审阅新的 mockups。"
)

config = {"configurable": {"thread_id": "6"}}

interrupts = []
for step in supervisor_agent.stream(
    {"messages": [{"role": "user", "content": query}]},
    config,
):
    for update in step.values():
        if isinstance(update, dict):
            for message in update.get("messages", []):
                message.pretty_print()
        else:
            interrupt_ = update[0]
            interrupts.append(interrupt_)
            print(f"\nINTERRUPTED: {interrupt_.id}")
================================== Ai Message ==================================
Tool Calls:
  schedule_event (call_t4Wyn32ohaShpEZKuzZbl83z)
 Call ID: call_t4Wyn32ohaShpEZKuzZbl83z
  Args:
    request: 将和设计团队的会议安排在下周二下午 2 点,持续 1 小时。
  manage_email (call_JWj4vDJ5VMnvkySymhCBm4IR)
 Call ID: call_JWj4vDJ5VMnvkySymhCBm4IR
  Args:
    request: 给设计团队发送电子邮件提醒,请他们在下周二下午 2 点的会议前审阅新的 mockups。

INTERRUPTED: 4f994c9721682a292af303ec1a46abb7

INTERRUPTED: 2b56f299be313ad8bc689eff02973f16
这一次执行被中断了。检查中断事件:
for interrupt_ in interrupts:
    for request in interrupt_.value["action_requests"]:
        print(f"INTERRUPTED: {interrupt_.id}")
        print(f"{request['description']}\n")
INTERRUPTED: 4f994c9721682a292af303ec1a46abb7
日历事件待批准

Tool: create_calendar_event
Args: {'title': '和设计团队开会', 'start_time': '2024-06-18T14:00:00', 'end_time': '2024-06-18T15:00:00', 'attendees': ['design team']}

INTERRUPTED: 2b56f299be313ad8bc689eff02973f16
外发电子邮件待批准

Tool: send_email
Args: {'to': ['designteam@example.com'], 'subject': '提醒:请在下周二下午 2 点会议前审阅新的 mockups', 'body': "团队你好,\n\n这是一封提醒,请在下周二下午 2 点的会议前审阅新的 mockups。你们的反馈和见解会对我们的讨论和后续步骤很有价值。\n\n请确保你们已经看过设计,并准备好在会议中分享想法。\n\n谢谢!\n\n此致,\n[你的名字]"}
你可以使用 Command 并通过 ID 引用每个中断,为其指定决策。更多细节请参阅 human-in-the-loop guide。为了演示,这里会接受日历事件,但编辑外发电子邮件的主题:
from langgraph.types import Command 

resume = {}
for interrupt_ in interrupts:
    if interrupt_.id == "2b56f299be313ad8bc689eff02973f16":
        # 编辑电子邮件
        edited_action = interrupt_.value["action_requests"][0].copy()
        edited_action["args"]["subject"] = "Mockups 提醒"
        resume[interrupt_.id] = {
            "decisions": [{"type": "edit", "edited_action": edited_action}]
        }
    else:
        resume[interrupt_.id] = {"decisions": [{"type": "approve"}]}

interrupts = []
for step in supervisor_agent.stream(
    Command(resume=resume),
    config,
):
    for update in step.values():
        if isinstance(update, dict):
            for message in update.get("messages", []):
                message.pretty_print()
        else:
            interrupt_ = update[0]
            interrupts.append(interrupt_)
            print(f"\nINTERRUPTED: {interrupt_.id}")
================================= Tool Message =================================
Name: schedule_event

你与设计团队的会议已安排在下周二 6 月 18 日下午 2:00 到 3:00。
================================= Tool Message =================================
Name: manage_email

你给设计团队的电子邮件提醒已发送。发送内容如下:

- 收件人:designteam@example.com
- 主题:Mockups 提醒
- 正文:提醒他们在下周二下午 2 点会议前审阅新的 mockups,并请求他们准备反馈和讨论内容。

如果你还需要其他帮助,请告诉我!
================================== Ai Message ==================================

- 你与设计团队的会议已安排在下周二 6 月 18 日下午 2:00 到 3:00。
- 已向设计团队发送电子邮件提醒,请他们在会议前审阅新的 mockups。

如果你还需要其他帮助,请告诉我!
运行会带着你的输入继续执行。

7. 高级:控制信息流

默认情况下,sub-agents 只会接收来自 supervisor 的请求字符串。你可能想传递额外上下文,例如对话历史或用户偏好。

向 sub-agents 传递额外对话上下文

from langchain.tools import tool, ToolRuntime

@tool
def schedule_event(
    request: str,
    runtime: ToolRuntime
) -> str:
    """使用自然语言安排日历事件。"""
    # 自定义 sub-agent 接收的上下文
    original_user_message = next(
        message for message in runtime.state["messages"]
        if message.type == "human"
    )
    prompt = (
        "你正在协助处理以下用户问题:\n\n"
        f"{original_user_message.text}\n\n"
        "你负责处理以下子请求:\n\n"
        f"{request}"
    )
    result = calendar_agent.invoke({
        "messages": [{"role": "user", "content": prompt}],
    })
    return result["messages"][-1].text
这允许 sub-agents 查看完整对话上下文,有助于消解“明天同一时间安排它”这类引用前文对话的歧义。
你可以在 LangSmith trace 的聊天模型调用中查看 sub-agent 接收到的完整上下文。

控制 supervisor 接收的信息

你还可以自定义哪些信息会回流到 supervisor:
import json

@tool
def schedule_event(request: str) -> str:
    """使用自然语言安排日历事件。"""
    result = calendar_agent.invoke({
        "messages": [{"role": "user", "content": request}]
    })

    # 选项 1:仅返回确认消息
    return result["messages"][-1].text

    # 选项 2:返回结构化数据
    # return json.dumps({
    #     "status": "success",
    #     "event_id": "evt_123",
    #     "summary": result["messages"][-1].text
    # })
重要: 确保 sub-agent prompts 强调最终消息应包含所有相关信息。常见失败模式是 sub-agents 执行了工具调用,但没有在最终响应中包含结果。

8. 关键要点

Supervisor pattern 会创建多层抽象,其中每一层都有明确职责。设计 supervisor 系统时,应从清晰的领域边界开始,并为每个 sub-agent 提供聚焦的工具和 prompt。为 supervisor 编写清晰的工具描述,在集成前独立测试每一层,并根据具体需求控制信息流。
何时使用 supervisor pattern当你有多个彼此不同的领域(日历、电子邮件、CRM、数据库)、每个领域都有多个工具或复杂逻辑、希望集中控制工作流,并且 sub-agents 不需要直接与用户对话时,请使用 supervisor pattern。对于只有少量工具的简单场景,请使用单个 agent。当 agents 需要与用户对话时,请改用 handoffs。对于 agents 之间的 peer-to-peer 协作,请考虑其他 multi-agent patterns。

后续步骤

了解用于 agent-to-agent 对话的 handoffs,探索 context engineering 以微调信息流,阅读 multi-agent overview 以比较不同模式,并使用 LangSmith 调试和监控你的 multi-agent 系统。