使用子智能体构建个人助理系统 (Build a personal assistant with subagents)
概述 (Overview)
主管模式(Supervisor pattern)是一种经典的多智能体(Multi-agent)系统架构:由一个处于中心地位的主管智能体统一调度和协调专门的工作子智能体。当业务任务需要不同领域的专业能力时,这种方案表现尤为出色。与其构建一个需要跨领域挑选庞杂工具的单体庞大智能体,不如创建多个职责聚焦的专家智能体,并由一个通晓全局业务流程的主管智能体进行协调。
在本教程中,你将通过真实的业务工作流构建一个个人助理系统。该系统将调度协调两个职责截然不同的专家智能体:
- 日历智能体 (Calendar agent):负责日程安排、空闲时段查询与会议事件管理。
- 邮件智能体 (Email agent):负责邮件往来、起草消息以及发送外部通知。
我们还将接入人在回路审查(Human-in-the-loop)机制,允许用户按需对关键操作(如正式发送外发邮件)进行人工审批、修改或驳回。
为什么使用主管架构? (Why use a supervisor?)
多智能体架构允许你将工具按业务边界隔离分配给不同工作者,每个工作者拥有独立的系统提示词与执行指令。设想一个直接拥有全部日历与邮件 API 访问权限的单体智能体:它必须在海量相似工具中做出抉择,理解每个 API 的严格传参格式,并同时跨多个业务域维持上下文。当智能体准确率下降时,将相关工具与关联提示词拆分为独立的逻辑分组(便于单独迭代优化)是极佳的工程实践。
核心概念 (Concepts)
本教程将涵盖以下技术概念:
Setup
Installation
This tutorial requires the langchain package:
npm install langchainyarn add langchainpnpm add langchainFor more details, see our Installation guide.
LangSmith
Set up LangSmith to inspect what is happening inside your agent. Then set the following environment variables:
export LANGSMITH_TRACING="true"
export LANGSMITH_API_KEY="..."process.env.LANGSMITH_TRACING = "true";
process.env.LANGSMITH_API_KEY = "...";Components
We will need to select a chat model from LangChain's suite of integrations:
OpenAI
👉 Read the OpenAI chat model integration docs
npm install @langchain/openaipnpm install @langchain/openaiyarn add @langchain/openaibun add @langchain/openaiimport { initChatModel } from "langchain";
process.env.OPENAI_API_KEY = "your-api-key";
const model = await initChatModel("gpt-5.5");import { ChatOpenAI } from "@langchain/openai";
const model = new ChatOpenAI({
model: "gpt-5.5",
apiKey: "your-api-key"
});Anthropic
👉 Read the Anthropic chat model integration docs
npm install @langchain/anthropicpnpm install @langchain/anthropicyarn add @langchain/anthropicpnpm add @langchain/anthropicimport { initChatModel } from "langchain";
process.env.ANTHROPIC_API_KEY = "your-api-key";
const model = await initChatModel("claude-sonnet-4-6");import { ChatAnthropic } from "@langchain/anthropic";
const model = new ChatAnthropic({
model: "claude-sonnet-4-6",
apiKey: "your-api-key"
});Azure
👉 Read the Azure chat model integration docs
npm install @langchain/azurepnpm install @langchain/azureyarn add @langchain/azurebun add @langchain/azureimport { initChatModel } from "langchain";
process.env.AZURE_OPENAI_API_KEY = "your-api-key";
process.env.AZURE_OPENAI_ENDPOINT = "your-endpoint";
process.env.OPENAI_API_VERSION = "your-api-version";
const model = await initChatModel("azure_openai:gpt-5.5");import { AzureChatOpenAI } from "@langchain/openai";
const model = new AzureChatOpenAI({
model: "gpt-5.5",
azureOpenAIApiKey: "your-api-key",
azureOpenAIApiEndpoint: "your-endpoint",
azureOpenAIApiVersion: "your-api-version"
});Google Gemini
👉 Read the ChatGoogle chat model integration docs
npm install @langchain/googlepnpm install @langchain/googleyarn add @langchain/googlebun add @langchain/googleimport { initChatModel } from "langchain";
process.env.GOOGLE_API_KEY = "your-api-key";
const model = await initChatModel("google:gemini-3.7-flash");import { ChatGoogle } from "@langchain/google/node";
const model = new ChatGoogle({
model: "gemini-3.7-flash",
apiKey: "your-api-key"
});Bedrock Converse
👉 Read the AWS Bedrock chat model integration docs
npm install @langchain/awspnpm install @langchain/awsyarn add @langchain/awsbun add @langchain/awsimport { initChatModel } from "langchain";
// Follow the steps here to configure your credentials:
// https://docs.aws.amazon.com/bedrock/latest/userguide/getting-started.html
const model = await initChatModel("bedrock:gpt-5.5");import { ChatBedrockConverse } from "@langchain/aws";
// Follow the steps here to configure your credentials:
// https://docs.aws.amazon.com/bedrock/latest/userguide/getting-started.html
const model = new ChatBedrockConverse({
model: "gpt-5.5",
region: "us-east-2"
});1. 定义领域工具 (Define tools)
Start by defining the tools that require structured inputs. In real applications, these would call actual APIs (Google Calendar, SendGrid, etc.). For this tutorial, you'll use stubs to demonstrate the pattern.
import { tool } from "langchain";
import { z } from "zod";
const createCalendarEvent = tool(
async ({ title, startTime, endTime, attendees, location }) => {
// Stub: In practice, this would call Google Calendar API, Outlook API, etc.
return `Event created: ${title} from ${startTime} to ${endTime} with ${attendees.length} attendees`;
},
{
name: "create_calendar_event",
description: "Create a calendar event. Requires exact ISO datetime format.",
schema: z.object({
title: z.string(),
startTime: z.string().describe("ISO format: '2024-01-15T14:00:00'"),
endTime: z.string().describe("ISO format: '2024-01-15T15:00:00'"),
attendees: z.array(z.string()).describe("email addresses"),
location: z.string().optional(),
}),
}
);
const sendEmail = tool(
async ({ to, subject, body, cc }) => {
// Stub: In practice, this would call SendGrid, Gmail API, etc.
return `Email sent to ${to.join(', ')} - Subject: ${subject}`;
},
{
name: "send_email",
description: "Send an email via email API. Requires properly formatted addresses.",
schema: z.object({
to: z.array(z.string()).describe("email addresses"),
subject: z.string(),
body: z.string(),
cc: z.array(z.string()).optional(),
}),
}
);
const getAvailableTimeSlots = tool(
async ({ attendees, date, durationMinutes }) => {
// Stub: In practice, this would query calendar APIs
return ["09:00", "14:00", "16:00"];
},
{
name: "get_available_time_slots",
description: "Check calendar availability for given attendees on a specific date.",
schema: z.object({
attendees: z.array(z.string()),
date: z.string().describe("ISO format: '2024-01-15'"),
durationMinutes: z.number(),
}),
}
);2. 创建专用子智能体 (Create specialized sub-agents)
Next, we'll create specialized sub-agents that handle each domain.
创建日历智能体 (Create a calendar agent)
The calendar agent understands natural language scheduling requests and translates them into precise API calls. It handles date parsing, availability checking, and event creation.
import { createAgent } from "langchain";
const now = new Date();
const today = [
now.getFullYear(),
String(now.getMonth() + 1).padStart(2, "0"),
String(now.getDate()).padStart(2, "0"),
].join("-");
const CALENDAR_AGENT_PROMPT = `
Today's date is ${today}.
You are a calendar scheduling assistant.
Parse natural language scheduling requests (e.g., 'next Tuesday at 2pm')
into proper ISO datetime formats.
Use get_available_time_slots to check availability when needed.
If there is no suitable time slot, stop and confirm unavailability in your response.
Use create_calendar_event to schedule events.
Always confirm what was scheduled in your final response.
`.trim();
const calendarAgent = createAgent({
model: llm,
tools: [createCalendarEvent, getAvailableTimeSlots],
systemPrompt: CALENDAR_AGENT_PROMPT,
});Test the calendar agent to see how it handles natural language scheduling:
const query = "Schedule a team meeting next Tuesday at 2pm for 1 hour";
const stream = await calendarAgent.streamEvents(
{ messages: [{ role: "user", content: query }] },
{ version: "v3" }
);
await Promise.all([
(async () => {
for await (const message of stream.messages) {
for await (const token of message.text) {
process.stdout.write(token);
}
}
})(),
(async () => {
for await (const call of stream.toolCalls) {
console.log(`\nTool call: ${call.name}(${JSON.stringify(call.input)})`);
console.log(`Tool result: ${await call.output}`);
}
})(),
]);================================== 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: Team Meeting
start_time: 2024-06-18T14:00:00
end_time: 2024-06-18T15:00:00
attendees: []
================================= Tool Message =================================
Name: create_calendar_event
Event created: Team Meeting from 2024-06-18T14:00:00 to 2024-06-18T15:00:00 with 0 attendees
================================== Ai Message ==================================
The team meeting has been scheduled for next Tuesday, June 18th, at 2:00 PM and will last for 1 hour. If you need to add attendees or a location, please let me know!The agent parses "next Tuesday at 2pm" into ISO format ("2024-01-16T14:00:00"), calculates the end time, calls create_calendar_event, and returns a natural language confirmation.
创建邮件智能体 (Create an email agent)
The email agent handles message composition and sending. It focuses on extracting recipient information, crafting appropriate subject lines and body text, and managing email communication.
const EMAIL_AGENT_PROMPT = `
You are an email assistant.
Compose professional emails based on natural language requests.
Extract recipient information and craft appropriate subject lines and body text.
Use send_email to send the message.
Always confirm what was sent in your final response.
`.trim();
const emailAgent = createAgent({
model: llm,
tools: [sendEmail],
systemPrompt: EMAIL_AGENT_PROMPT,
});Test the email agent with a natural language request:
const query = "Send the design team a reminder about reviewing the new mockups";
const stream = await emailAgent.streamEvents(
{ messages: [{ role: "user", content: query }] },
{ version: "v3" }
);
await Promise.all([
(async () => {
for await (const message of stream.messages) {
for await (const token of message.text) {
process.stdout.write(token);
}
}
})(),
(async () => {
for await (const call of stream.toolCalls) {
console.log(`\nTool call: ${call.name}(${JSON.stringify(call.input)})`);
console.log(`Tool result: ${await call.output}`);
}
})(),
]);================================== Ai Message ==================================
Tool Calls:
send_email (call_OMl51FziTVY6CRZvzYfjYOZr)
Call ID: call_OMl51FziTVY6CRZvzYfjYOZr
Args:
to: ['design-team@example.com']
subject: Reminder: Please Review the New Mockups
body: Hi Design Team,
This is a friendly reminder to review the new mockups at your earliest convenience. Your feedback is important to ensure that we stay on track with our project timeline.
Please let me know if you have any questions or need additional information.
Thank you!
Best regards,
================================= Tool Message =================================
Name: send_email
Email sent to design-team@example.com - Subject: Reminder: Please Review the New Mockups
================================== Ai Message ==================================
I've sent a reminder to the design team asking them to review the new mockups. If you need any further communication on this topic, just let me know!The agent infers the recipient from the informal request, crafts a professional subject line and body, calls send_email, and returns a confirmation. Each sub-agent has a narrow focus with domain-specific tools and prompts, allowing it to excel at its specific task.
3. 将子智能体封装为工具 (Wrap sub-agents as tools)
Now wrap each sub-agent as a tool that the supervisor can invoke. This is the key architectural step that creates the layered system. The supervisor will see high-level tools like "schedule_event", not low-level tools like "create_calendar_event".
const scheduleEvent = tool(
async ({ request }) => {
const result = await calendarAgent.invoke({
messages: [{ role: "user", content: request }]
});
const lastMessage = result.messages[result.messages.length - 1];
return lastMessage.text;
},
{
name: "schedule_event",
description: `
Schedule calendar events using natural language.
Use this when the user wants to create, modify, or check calendar appointments.
Handles date/time parsing, availability checking, and event creation.
Input: Natural language scheduling request (e.g., 'meeting with design team next Tuesday at 2pm')
`.trim(),
schema: z.object({
request: z.string().describe("Natural language scheduling request"),
}),
}
);
const manageEmail = tool(
async ({ request }) => {
const result = await emailAgent.invoke({
messages: [{ role: "user", content: request }]
});
const lastMessage = result.messages[result.messages.length - 1];
return lastMessage.text;
},
{
name: "manage_email",
description: `
Send emails using natural language.
Use this when the user wants to send notifications, reminders, or any email communication.
Handles recipient extraction, subject generation, and email composition.
Input: Natural language email request (e.g., 'send them a reminder about the meeting')
`.trim(),
schema: z.object({
request: z.string().describe("Natural language email request"),
}),
}
);The tool descriptions help the supervisor decide when to use each tool, so make them clear and specific. We return only the sub-agent's final response, as the supervisor doesn't need to see intermediate reasoning or tool calls.
4. 创建主管智能体 (Create the supervisor agent)
Now create the supervisor that orchestrates the sub-agents. The supervisor only sees high-level tools and makes routing decisions at the domain level, not the individual API level.
const SUPERVISOR_PROMPT = `
You are a helpful personal assistant.
You can schedule calendar events and send emails.
Break down user requests into appropriate tool calls and coordinate the results.
When a request involves multiple actions, use multiple tools in sequence or in parallel as appropriate.
`.trim();
const supervisorAgent = createAgent({
model: llm,
tools: [scheduleEvent, manageEmail],
systemPrompt: SUPERVISOR_PROMPT,
});5. 运行主管智能体 (Use the supervisor)
Now test your complete system with complex requests that require coordination across multiple domains:
示例 1:简单的单领域请求 (Simple single-domain request)
const query = "Schedule a team standup for tomorrow at 9am";
const stream = await supervisorAgent.streamEvents(
{ messages: [{ role: "user", content: query }] },
{ version: "v3" }
);
await Promise.all([
(async () => {
for await (const message of stream.messages) {
for await (const token of message.text) {
process.stdout.write(token);
}
}
})(),
(async () => {
for await (const call of stream.toolCalls) {
console.log(`\nTool call: ${call.name}(${JSON.stringify(call.input)})`);
console.log(`Tool result: ${await call.output}`);
}
})(),
]);================================== Ai Message ==================================
Tool Calls:
schedule_event (call_mXFJJDU8bKZadNUZPaag8Lct)
Call ID: call_mXFJJDU8bKZadNUZPaag8Lct
Args:
request: Schedule a team standup for tomorrow at 9am with Alice and Bob.
================================= Tool Message =================================
Name: schedule_event
The team standup has been scheduled for tomorrow at 9:00 AM with Alice and Bob. If you need to make any changes or add more details, just let me know!
================================== Ai Message ==================================
The team standup with Alice and Bob is scheduled for tomorrow at 9:00 AM. If you need any further arrangements or adjustments, please let me know!The supervisor identifies this as a calendar task, calls schedule_event, and the calendar agent handles date parsing and event creation.
TIP
For full transparency into the information flow, including prompts and responses for each chat model call, check out the LangSmith trace for the above run.
示例 2:跨领域协同复杂请求 (Complex multi-domain request)
const query =
"Schedule a meeting with the design team next Tuesday at 2pm for 1 hour, " +
"and send them an email reminder about reviewing the new mockups.";
const stream = await supervisorAgent.streamEvents(
{ messages: [{ role: "user", content: query }] },
{ version: "v3" }
);
await Promise.all([
(async () => {
for await (const message of stream.messages) {
for await (const token of message.text) {
process.stdout.write(token);
}
}
})(),
(async () => {
for await (const call of stream.toolCalls) {
console.log(`\nTool call: ${call.name}(${JSON.stringify(call.input)})`);
console.log(`Tool result: ${await call.output}`);
}
})(),
]);================================== Ai Message ==================================
Tool Calls:
schedule_event (call_YA68mqF0koZItCFPx0kGQfZi)
Call ID: call_YA68mqF0koZItCFPx0kGQfZi
Args:
request: meeting with the design team next Tuesday at 2pm for 1 hour
manage_email (call_XxqcJBvVIuKuRK794ZIzlLxx)
Call ID: call_XxqcJBvVIuKuRK794ZIzlLxx
Args:
request: send the design team an email reminder about reviewing the new mockups
================================= Tool Message =================================
Name: schedule_event
Your meeting with the design team is scheduled for next Tuesday, June 18th, from 2:00pm to 3:00pm. Let me know if you need to add more details or make any changes!
================================= Tool Message =================================
Name: manage_email
I've sent an email reminder to the design team requesting them to review the new mockups. If you need to include more information or recipients, just let me know!
================================== Ai Message ==================================
Your meeting with the design team is scheduled for next Tuesday, June 18th, from 2:00pm to 3:00pm.
I've also sent an email reminder to the design team, asking them to review the new mockups.
Let me know if you'd like to add more details to the meeting or include additional information in the email!The supervisor recognizes this requires both calendar and email actions, calls schedule_event for the meeting, then calls manage_email for the reminder. Each sub-agent completes its task, and the supervisor synthesizes both results into a coherent response.
NOTE
The supervisor dispatches tasks to subagents sequentially by default. Each tool call completes before the next one starts. However, many LLMs will issue multiple tool calls in a single response (as shown in the trace above, where both schedule_event and manage_email are called together), which the runtime executes in parallel. You can also configure explicit parallel dispatch. See the create_supervisor reference docs for details.
TIP
Refer to the LangSmith trace to see the detailed information flow for the above run, including individual chat model prompts and responses.
完整可运行代码示例 (Complete working example)
Here's everything together in a runnable script:
View complete code
/**
* Personal Assistant Supervisor Example
*
* This example demonstrates the tool calling pattern for multi-agent systems.
* A supervisor agent coordinates specialized sub-agents (calendar and email)
* that are wrapped as tools.
*/
import { tool, createAgent } from "langchain";
import { ChatAnthropic } from "@langchain/anthropic";
import { z } from "zod";
// ============================================================================
// Step 1: Define low-level API tools (stubbed)
// ============================================================================
const createCalendarEvent = tool(
async ({ title, startTime, endTime, attendees, location }) => {
// Stub: In practice, this would call Google Calendar API, Outlook API, etc.
return `Event created: ${title} from ${startTime} to ${endTime} with ${attendees.length} attendees`;
},
{
name: "create_calendar_event",
description: "Create a calendar event. Requires exact ISO datetime format.",
schema: z.object({
title: z.string(),
startTime: z.string().describe("ISO format: '2024-01-15T14:00:00'"),
endTime: z.string().describe("ISO format: '2024-01-15T15:00:00'"),
attendees: z.array(z.string()).describe("email addresses"),
location: z.string().optional().default(""),
}),
}
);
const sendEmail = tool(
async ({ to, subject, body, cc }) => {
// Stub: In practice, this would call SendGrid, Gmail API, etc.
return `Email sent to ${to.join(", ")} - Subject: ${subject}`;
},
{
name: "send_email",
description:
"Send an email via email API. Requires properly formatted addresses.",
schema: z.object({
to: z.array(z.string()).describe("email addresses"),
subject: z.string(),
body: z.string(),
cc: z.array(z.string()).optional().default([]),
}),
}
);
const getAvailableTimeSlots = tool(
async ({ attendees, date, durationMinutes }) => {
// Stub: In practice, this would query calendar APIs
return ["09:00", "14:00", "16:00"];
},
{
name: "get_available_time_slots",
description:
"Check calendar availability for given attendees on a specific date.",
schema: z.object({
attendees: z.array(z.string()),
date: z.string().describe("ISO format: '2024-01-15'"),
durationMinutes: z.number(),
}),
}
);
// ============================================================================
// Step 2: Create specialized sub-agents
// ============================================================================
const llm = new ChatAnthropic({
model: "gpt-5.5",
});
const now = new Date();
const today = [
now.getFullYear(),
String(now.getMonth() + 1).padStart(2, "0"),
String(now.getDate()).padStart(2, "0"),
].join("-");
const calendarAgent = createAgent({
model: llm,
tools: [createCalendarEvent, getAvailableTimeSlots],
systemPrompt: `
Today's date is ${today}.
You are a calendar scheduling assistant.
Parse natural language scheduling requests (e.g., 'next Tuesday at 2pm')
into proper ISO datetime formats.
Use get_available_time_slots to check availability when needed.
If there is no suitable time slot, stop and confirm unavailability in your response.
Use create_calendar_event to schedule events.
Always confirm what was scheduled in your final response.
`.trim(),
});
const emailAgent = createAgent({
model: llm,
tools: [sendEmail],
systemPrompt: `
You are an email assistant.
Compose professional emails based on natural language requests.
Extract recipient information and craft appropriate subject lines and body text.
Use send_email to send the message.
Always confirm what was sent in your final response.
`.trim(),
});
// ============================================================================
// Step 3: Wrap sub-agents as tools for the supervisor
// ============================================================================
const scheduleEvent = tool(
async ({ request }) => {
const result = await calendarAgent.invoke({
messages: [{ role: "user", content: request }],
});
const lastMessage = result.messages[result.messages.length - 1];
return lastMessage.text;
},
{
name: "schedule_event",
description: `
Schedule calendar events using natural language.
Use this when the user wants to create, modify, or check calendar appointments.
Handles date/time parsing, availability checking, and event creation.
Input: Natural language scheduling request (e.g., 'meeting with design team next Tuesday at 2pm')
`.trim(),
schema: z.object({
request: z.string().describe("Natural language scheduling request"),
}),
}
);
const manageEmail = tool(
async ({ request }) => {
const result = await emailAgent.invoke({
messages: [{ role: "user", content: request }],
});
const lastMessage = result.messages[result.messages.length - 1];
return lastMessage.text;
},
{
name: "manage_email",
description: `
Send emails using natural language.
Use this when the user wants to send notifications, reminders, or any email communication.
Handles recipient extraction, subject generation, and email composition.
Input: Natural language email request (e.g., 'send them a reminder about the meeting')
`.trim(),
schema: z.object({
request: z.string().describe("Natural language email request"),
}),
}
);
// ============================================================================
// Step 4: Create the supervisor agent
// ============================================================================
const supervisorAgent = createAgent({
model: llm,
tools: [scheduleEvent, manageEmail],
systemPrompt: `
You are a helpful personal assistant.
You can schedule calendar events and send emails.
Break down user requests into appropriate tool calls and coordinate the results.
When a request involves multiple actions, use multiple tools in sequence.
`.trim(),
});
// ============================================================================
// Step 5: Use the supervisor
// ============================================================================
// Example: User request requiring both calendar and email coordination
const userRequest =
"Schedule a meeting with the design team next Tuesday at 2pm for 1 hour, " +
"and send them an email reminder about reviewing the new mockups.";
console.log("User Request:", userRequest);
console.log(`\n${"=".repeat(80)}\n`);
const stream = await supervisorAgent.streamEvents(
{ messages: [{ role: "user", content: userRequest }] },
{ version: "v3" }
);
await Promise.all([
(async () => {
for await (const message of stream.messages) {
for await (const token of message.text) {
process.stdout.write(token);
}
}
})(),
(async () => {
for await (const call of stream.toolCalls) {
console.log(`\nTool call: ${call.name}(${JSON.stringify(call.input)})`);
console.log(`Tool result: ${await call.output}`);
}
})(),
]);深入理解系统架构 (Understanding the architecture)
Your system has three layers. The bottom layer contains rigid API tools that require exact formats. The middle layer contains sub-agents that accept natural language, translate it to structured API calls, and return natural language confirmations. The top layer contains the supervisor that routes to high-level capabilities and synthesizes results.
This separation of concerns provides several benefits: each layer has a focused responsibility, you can add new domains without affecting existing ones, and you can test and iterate on each layer independently.
6. 引入人在回路审批机制 (Add human-in-the-loop review)
It can be prudent to incorporate human-in-the-loop review of sensitive actions. LangChain includes built-in middleware to review tool calls, in this case the tools invoked by sub-agents.
Let's add human-in-the-loop review to both sub-agents:
- We configure the
create_calendar_eventandsend_emailtools to interrupt, permitting all response types (approve,edit,reject) - We add a checkpointer only to the top-level agent. This is required to pause and resume execution.
import { createAgent, humanInTheLoopMiddleware } from "langchain";
import { MemorySaver } from "@langchain/langgraph";
const calendarAgent = createAgent({
model: llm,
tools: [createCalendarEvent, getAvailableTimeSlots],
systemPrompt: CALENDAR_AGENT_PROMPT,
middleware: [
humanInTheLoopMiddleware({
interruptOn: { create_calendar_event: true },
descriptionPrefix: "Calendar event pending approval",
}),
],
});
const emailAgent = createAgent({
model: llm,
tools: [sendEmail],
systemPrompt: EMAIL_AGENT_PROMPT,
middleware: [
humanInTheLoopMiddleware({
interruptOn: { send_email: true },
descriptionPrefix: "Outbound email pending approval",
}),
],
});
const supervisorAgent = createAgent({
model: llm,
tools: [scheduleEvent, manageEmail],
systemPrompt: SUPERVISOR_PROMPT,
checkpointer: new MemorySaver(),
});Let's repeat the query. Note that we gather interrupt events into a list to access downstream:
const query =
"Schedule a meeting with the design team next Tuesday at 2pm for 1 hour, " +
"and send them an email reminder about reviewing the new mockups.";
const config = { configurable: { thread_id: "6" } };
const interrupts: any[] = [];
const stream = await supervisorAgent.streamEvents(
{ messages: [{ role: "user", content: query }] },
{ ...config, version: "v3" }
);
await Promise.all([
(async () => {
for await (const message of stream.messages) {
for await (const token of message.text) {
process.stdout.write(token);
}
}
})(),
(async () => {
for await (const call of stream.toolCalls) {
console.log(`\nTool call: ${call.name}(${JSON.stringify(call.input)})`);
}
})(),
]);
if (stream.interrupted) {
for (const interrupt of stream.interrupts) {
interrupts.push(interrupt);
console.log(`\nINTERRUPTED: ${interrupt.interruptId}`);
}
}================================== Ai Message ==================================
Tool Calls:
schedule_event (call_t4Wyn32ohaShpEZKuzZbl83z)
Call ID: call_t4Wyn32ohaShpEZKuzZbl83z
Args:
request: Schedule a meeting with the design team next Tuesday at 2pm for 1 hour.
manage_email (call_JWj4vDJ5VMnvkySymhCBm4IR)
Call ID: call_JWj4vDJ5VMnvkySymhCBm4IR
Args:
request: Send an email reminder to the design team about reviewing the new mockups before our meeting next Tuesday at 2pm.
INTERRUPTED: 4f994c9721682a292af303ec1a46abb7
INTERRUPTED: 2b56f299be313ad8bc689eff02973f16This time we've interrupted execution. Let's inspect the interrupt events:
for (const interrupt of interrupts) {
for (const request of interrupt.payload.actionRequests) {
console.log(`INTERRUPTED: ${interrupt.interruptId}`);
console.log(`${request.description}\n`);
}
}INTERRUPTED: 4f994c9721682a292af303ec1a46abb7
Calendar event pending approval
Tool: create_calendar_event
Args: {'title': 'Meeting with the Design Team', 'start_time': '2024-06-18T14:00:00', 'end_time': '2024-06-18T15:00:00', 'attendees': ['design team']}
INTERRUPTED: 2b56f299be313ad8bc689eff02973f16
Outbound email pending approval
Tool: send_email
Args: {'to': ['designteam@example.com'], 'subject': 'Reminder: Review New Mockups Before Meeting Next Tuesday at 2pm', 'body': "Hello Team,\n\nThis is a reminder to review the new mockups ahead of our meeting scheduled for next Tuesday at 2pm. Your feedback and insights will be valuable for our discussion and next steps.\n\nPlease ensure you've gone through the designs and are ready to share your thoughts during the meeting.\n\nThank you!\n\nBest regards,\n[Your Name]"}We can specify decisions for each interrupt by referring to its ID using a Command. Refer to the human-in-the-loop guide for additional details. For demonstration purposes, here we will accept the calendar event, but edit the subject of the outbound email:
import { Command } from "@langchain/langgraph";
const resume: Record<string, any> = {};
for (const interrupt of interrupts) {
const actionRequest = interrupt.payload.actionRequests[0];
if (actionRequest.name === "send_email") {
// Edit email
const editedAction = { ...actionRequest };
editedAction.args.subject = "Mockups reminder";
resume[interrupt.interruptId] = {
decisions: [{ type: "edit", editedAction }]
};
} else {
resume[interrupt.interruptId] = { decisions: [{ type: "approve" }] };
}
}
const resumeStream = await supervisorAgent.streamEvents(
new Command({ resume }),
{ ...config, version: "v3" }
);
await Promise.all([
(async () => {
for await (const message of resumeStream.messages) {
for await (const token of message.text) {
process.stdout.write(token);
}
}
})(),
(async () => {
for await (const call of resumeStream.toolCalls) {
console.log(`\nTool call: ${call.name}(${JSON.stringify(call.input)})`);
}
})(),
]);================================= Tool Message =================================
Name: schedule_event
Your meeting with the design team has been scheduled for next Tuesday, June 18th, from 2:00 pm to 3:00 pm.
================================= Tool Message =================================
Name: manage_email
Your email reminder to the design team has been sent. Here’s what was sent:
- Recipient: designteam@example.com
- Subject: Mockups reminder
- Body: A reminder to review the new mockups before the meeting next Tuesday at 2pm, with a request for feedback and readiness for discussion.
Let me know if you need any further assistance!
================================== Ai Message ==================================
- Your meeting with the design team has been scheduled for next Tuesday, June 18th, from 2:00 pm to 3:00 pm.
- An email reminder has been sent to the design team about reviewing the new mockups before the meeting.
Let me know if you need any further assistance!The run proceeds with our input.
7. 进阶:精准控制信息流 (Control information flow)
By default, sub-agents receive only the request string from the supervisor. You might want to pass additional context, such as conversation history or user preferences.
向子智能体传递补充对话上下文 (Pass additional conversational context to sub-agents)
import { getCurrentTaskInput } from "@langchain/langgraph";
import { type BuiltInState, HumanMessage } from "langchain";
const scheduleEvent = tool(
async ({ request }, config) => {
// Customize context received by sub-agent
// Access full thread messages from the config
const currentMessages = getCurrentTaskInput<BuiltInState>(config).messages;
const originalUserMessage = currentMessages.find(HumanMessage.isInstance);
const prompt = `
You are assisting with the following user inquiry:
${originalUserMessage?.content || "No context available"}
You are tasked with the following sub-request:
${request}
`.trim();
const result = await calendarAgent.invoke({
messages: [{ role: "user", content: prompt }],
});
const lastMessage = result.messages[result.messages.length - 1];
return lastMessage.text;
},
{
name: "schedule_event",
description: "Schedule calendar events using natural language.",
schema: z.object({
request: z.string().describe("Natural language scheduling request"),
}),
}
);This allows sub-agents to see the full conversation context, which can be useful for resolving ambiguities like "schedule it for the same time tomorrow" (referencing a previous conversation).
TIP
You can see the full context received by the sub agent in the chat model call of the LangSmith trace.
控制主管智能体接收的返回信息 (Control what supervisor receives)
You can also customize what information flows back to the supervisor:
const scheduleEvent = tool(
async ({ request }) => {
const result = await calendarAgent.invoke({
messages: [{ role: "user", content: request }]
});
const lastMessage = result.messages[result.messages.length - 1];
// Option 1: Return just the confirmation message
return lastMessage.text;
// Option 2: Return structured data
// return JSON.stringify({
// status: "success",
// event_id: "evt_123",
// summary: lastMessage.text
// });
},
{
name: "schedule_event",
description: "Schedule calendar events using natural language.",
schema: z.object({
request: z.string().describe("Natural language scheduling request"),
}),
}
);Important: Make sure sub-agent prompts emphasize that their final message should contain all relevant information. A common failure mode is sub-agents that perform tool calls but don't include the results in their final response.
TIP
For a complete working example that demonstrates the full supervisor pattern with human-in-the-loop review and advanced information flow control, check out supervisor_complete.ts in the LangChain.js examples.
8. 核心总结 (Key takeaways)
The supervisor pattern creates layers of abstraction where each layer has a clear responsibility. When designing a supervisor system, start with clear domain boundaries and give each sub-agent focused tools and prompts. Write clear tool descriptions for the supervisor, test each layer independently before integration, and control information flow based on your specific needs.
TIP
When to use the supervisor pattern
Use the supervisor pattern when you have multiple distinct domains (calendar, email, CRM, database), each domain has multiple tools or complex logic, you want centralized workflow control, and sub-agents don't need to converse directly with users.
For simpler cases with just a few tools, use a single agent. When agents need to have conversations with users, use handoffs instead. For peer-to-peer collaboration between agents, consider other multi-agent patterns.
后续进阶 (Next steps)
Learn about handoffs for agent-to-agent conversations, explore context engineering to fine-tune information flow, read the multi-agent overview to compare different patterns, and use LangSmith to debug and monitor your multi-agent system.
