主题
Supervisor 模式
要点
- 前面几篇一直在写一张图里的节点怎么配合
- Supervisor 不是什么特殊 API,它更像一种组织方式
- Supervisor 层关心的是任务分派,而不是底层工具参数
- 专门 Agent 可以被包装成总控 Agent 可调用的高层工具
- 官方多 Agent 示例里常见做法是把专门 Agent 暴露成 supervisor 可调用的 tool
- 角色拆分能降低单个 Agent 同时处理工具、提示词和上下文的压力
内容
1. 一个人开始忙不过来时,就会想拆角色
前面几篇一直在写一张图里的节点怎么配合。
到了这里,图已经能暂停、恢复、拆子图,也能把一段流程包成模块。
但业务再往前走一步,问题就不只是「流程有多长」了,而是「是不是所有事都该让一个角色来做」。
比如一个内容创作系统,用户可能一口气说一句话:
「帮我写一篇 LangGraph 入门文章,先查资料,再把草稿改成适合公众号发布的版本。」
这句话里其实有两种完全不同的能力。一种是资料检索、案例补充,一种是结构调整、语气润色和发布前表达检查。把它们全塞给一个 Agent 当然也能写,但工具一多、提示词一长、上下文一杂,决策就开始变得不稳定。
更稳定的组织方式,是让一个总控角色先判断当前请求应该交给谁,再把具体工作交给对应的专门 Agent。这样每个角色看到的工具和上下文都会更窄。
这就是 Supervisor 模式。
我对照了 LangGraph 官方 multi-agent 文档。Supervisor 模式的常见实现,是把专门 Agent 包装成 supervisor 可以调用的工具,而不是把每个 Agent 都直接暴露给用户。总控 Agent 只看到「资料助理」「编辑助理」这类高层能力,不需要知道底层检索工具、润色工具各自有哪些参数。
用内容创作系统来理解:
| 层级 | 看到什么 | 负责什么 |
|---|---|---|
| Supervisor | ask_research_agent、ask_editor_agent | 判断任务交给谁,收集结果并回复用户 |
| Specialist Agent | 自己领域内的工具和提示词 | 查资料、润色、事实核对等具体能力 |
| Tool | 明确的 schema 和执行函数 | 真正调用搜索、估算、检查等外部能力 |
这种分层让「谁负责决策」和「谁负责执行」分开。后面新增 SEO Agent 或事实核查 Agent 时,通常只需要新增专门 Agent,再把它包装成 supervisor 的一个高层工具。
2. Supervisor 到底在做什么
先把名字放轻一点看。Supervisor 不是什么特殊 API,它更像一种组织方式。
它做的事情其实很简单。自己不直接处理所有细节,手里握着几个专门 Agent,先判断当前请求应该交给谁,拿到结果以后,再决定要不要继续找下一个角色,或者直接回复用户。
如果把前面那句用户请求拆开看,Supervisor 可能会先把「查 LangGraph 入门资料」交给资料 Agent,再把「改成适合公众号发布」交给编辑 Agent,最后把两边结果收回来,整理成一段最终回复。
这里的变化不只是「角色变多了」,而是上下文开始分层。资料 Agent 不用知道怎么润色标题和段落节奏,编辑 Agent 也不用关心所有检索工具的细节。每个角色只处理自己负责的那一块。
3. 先把两个专门 Agent 准备出来
这一篇先用两个最容易理解的角色来讲:一个是 researchAgent,只负责查资料和补案例;另一个是 editorAgent,只负责润色草稿和检查发布表达。
当前这套官方示例更常见的写法,是把专门 Agent 包成工具,让总控 Agent 去调用。因为总控 Agent 眼里不需要看见每个底层 API,只需要知道「这里有一个资料专家」和「这里有一个编辑专家」就够了。
这一层虽然写的是 createAgent(),但关注点已经从「单个 Agent 怎么调工具」转向「多个角色怎么分工,以及结果怎么回到总控」。
typescript
// specialist-agents.ts
import { createAgent, tool } from 'langchain'
import { ChatOpenAI } from '@langchain/openai'
import { z } from 'zod'
const model = new ChatOpenAI({
model: process.env.DEEPSEEK_MODEL || 'deepseek-chat',
apiKey: process.env.DEEPSEEK_API_KEY,
configuration: {
baseURL: process.env.DEEPSEEK_BASE_URL,
},
})
// 先准备底层工具。专门 Agent 真正做事时,还是靠这些工具落地。
const searchReferences = tool(
async ({ topic, angle }) => {
return `已围绕「${topic}」检索资料,建议从「${angle}」切入,并补充 StateGraph、状态合并和条件路由三个案例。`
},
{
name: 'search_references',
description: '围绕选题检索资料、案例和可引用要点',
schema: z.object({
topic: z.string(),
angle: z.string(),
}),
},
)
const polishDraft = tool(
async ({ draft, goal }) => {
return `已按「${goal}」优化草稿:${draft}。建议补一个从 outline 到 review 的完整流程示例。`
},
{
name: 'polish_draft',
description: '润色草稿、调整结构,并检查是否适合发布',
schema: z.object({
draft: z.string(),
goal: z.string(),
}),
},
)
// 资料 Agent 只看资料检索和案例补充的问题
const researchAgent = createAgent({
model,
tools: [searchReferences],
systemPrompt:
'你是一个资料助理,只处理资料检索、案例补充和事实核对相关的问题。',
})
// 编辑 Agent 只看草稿结构、语气和发布表达的问题
const editorAgent = createAgent({
model,
tools: [polishDraft],
systemPrompt: '你是一个内容编辑,只处理草稿润色、结构调整和发布前表达检查。',
})这里先注意一个边界:专门 Agent 不一定要暴露给用户。很多时候,用户只和总控 Agent 对话,专门 Agent 只是系统内部角色。
4. 把专门 Agent 包成总控可调用的工具
到了 Supervisor 这一层,总控不需要关心底层工具怎么用,它只关心:
「资料这件事交给谁。」
「编辑这件事交给谁。」
因此可以把 researchAgent 和 editorAgent 再包一层,变成总控 Agent 眼里的两个高层工具。
typescript
// wrap-subagents-as-tools.ts
import { tool } from 'langchain'
import { z } from 'zod'
const askResearchAgent = tool(
async ({ request }) => {
// 这里把一段自然语言请求交给资料 Agent
const result = await researchAgent.invoke({
messages: [{ role: 'user', content: request }],
})
// 总控 Agent 最终只需要看这个角色返回了什么,
// 不需要关心资料 Agent 内部到底调了哪些底层工具
return result.messages.at(-1)?.text ?? ''
},
{
name: 'ask_research_agent',
description: '把资料检索、案例补充和事实核对交给资料助理处理',
schema: z.object({
request: z.string(),
}),
},
)
const askEditorAgent = tool(
async ({ request }) => {
// 编辑 Agent 自己决定该怎么润色草稿、调用什么工具
const result = await editorAgent.invoke({
messages: [{ role: 'user', content: request }],
})
return result.messages.at(-1)?.text ?? ''
},
{
name: 'ask_editor_agent',
description: '把草稿润色、结构调整和发布前表达检查交给编辑助理处理',
schema: z.object({
request: z.string(),
}),
},
)这样包完以后,总控 Agent 根本不需要知道 search_references 和 polish_draft 的参数细节。它只会觉得自己手里有两个能力:
一个能处理资料,一个能处理编辑。
这也是 Supervisor 模式的主要价值:底层工具怎么变化,可以留在专门 Agent 内部演进,不需要每次都回头改总控。
5. 最上面这一层,就是 Supervisor
现在可以把总控 Agent 接起来了。
typescript
// supervisor-agent.ts
import { createAgent } from 'langchain'
const supervisor = createAgent({
model,
tools: [askResearchAgent, askEditorAgent],
systemPrompt: `
你是一个总控助理。
你的工作不是自己完成所有任务,而是判断当前请求应该交给哪个专门助理。
如果请求里同时包含多个任务,可以按顺序调用多个助理,再把结果整理给用户。
`.trim(),
})这时候,调用方式反而变简单了。用户还是只和一个入口对话。
typescript
// supervisor-run.ts
// 对用户来说,入口依然只有一个 supervisor
const result = await supervisor.invoke({
messages: [
{
role: 'user',
content:
'帮我写一篇 LangGraph 入门文章,先查资料,再把草稿改成适合公众号发布的版本。',
},
],
})
console.log(result.messages.at(-1)?.text)这段代码里,看上去只有一个 supervisor.invoke(...),但内部可能已经做了多步:
先找资料 Agent,拿到参考要点和案例,再找编辑 Agent 优化草稿,最后回到总控整理结果。
这也是为什么 Supervisor 模式会比「一个 Agent 手里挂十几个底层工具」更稳一些。总控层负责分工,专门角色负责执行,每一层看到的上下文都收得更干净。
6. 为什么它比把所有工具都塞到一个 Agent 里更顺
这个模式的关键差别,落在决策压力上。
如果只有一个 Agent,它每次都要同时面对: 检索工具、编辑工具、提示词、用户历史、格式要求和错误处理。
工具少的时候没问题,工具一多,模型开始做选择时就容易发散。它既要判断这句话属于什么领域,又要记住每个工具的参数要求,还要想怎么把多步任务串起来。
Supervisor 模式把这个压力拆开了。总控层先只做分工,专门层再各自处理自己的细节。这样每个 Agent 的工具范围会小很多,提示词也会短很多。
还有一个实际好处,是后面更容易加角色。今天只有资料和编辑,明天再加一个「SEO 助理」或「事实核查助理」,通常只需要:
- 新建一个专门 Agent。
- 把它包成一个总控可调用的工具。
- 挂到
supervisor的工具列表上。
7. 写这种模式时最容易踩的坑
最常见的问题,是把总控 Agent 写得太勤快。
如果总控层自己也开始直接查资料、直接润色草稿、直接做格式整理,那角色边界很快就会乱掉。写着写着,它又变回了一个什么都做的大 Agent,前面拆角色的意义就没了。
第二个常见问题,是让专门 Agent 看到太多不该看到的上下文。比如编辑 Agent 本来只需要知道「把这段草稿改成公众号发布语气」,你却把整段长长的历史对话、检索日志、无关用户偏好一股脑都塞给它。这样一来,专门 Agent 的上下文又开始膨胀,角色拆分的好处也被吃掉了。
还有一个问题,是把专门 Agent 当成长期记忆主体。当前这类 subagents 结构里,更稳的心智模型是:会话记忆主要留在总控层,专门 Agent 每次按任务被调用,处理完就把结果交回来。这样边界更清楚,也更容易排查问题。
8. 总结
Supervisor 模式本质上是在做一件很朴素的事:把一个开始忙不过来的 Agent,拆成一个总控角色和几个专门角色。
前面这一章写到现在,已经把流程控制、暂停恢复、子图模块化都接起来了。到了这里,关注点从「一张图怎么组织」延伸到「多个角色怎么分工」。
下一篇讲 Handoff 与 Swarm。那一篇会继续处理多 Agent 场景,但重点会从「总控负责分发任务」转向「角色之间怎样交接对话和控制权」。