子代理(Subagents)
将复杂任务委派给专门的子代理,保持主代理上下文整洁
从"一人扛所有"到"分工协作"的关键能力为什么需要它
你的代理上下文是不是经常被中间结果撑爆?
不用子代理的困境
- 网页搜索返回 50 条结果,上下文瞬间被填满
- 10 次工具调用的中间输出留在上下文里,有效信息被淹没
- 多步骤任务越跑越慢,token 消耗线性增长
- 所有任务共享同一套指令,无法针对优化
子代理的解法
- 将繁重工作隔离在独立上下文中
- 主代理只接收最终结果,不看到中间过程
- 每个子代理可以有专门的模型和工具
- 主代理专注高层协调,效率大幅提升
卸甲:上面说"像外包团队",这只是为了方便你理解。实际上子代理没有"人"的主动性——它只是主 Agent 调用
task 工具时,框架自动创建的一个新 Agent 实例。它没有团队协作意识,按系统提示执行完就结束,不会主动反馈或沟通。
使用场景判断
不是所有任务都需要子代理——用对地方才有效
适合使用子代理
- 会杂乱主代理上下文的多步骤任务
- 需要自定义指令或工具的专门领域
- 需要不同模型能力的任务
- 希望主代理专注高层协调时
不适合使用子代理
- 简单的单步骤任务(直接做更快)
- 需要保留中间上下文的任务
- 子代理的开销大于收益时
工作原理
主代理通过 task 工具将工作委派给子代理,子代理在隔离上下文中执行
基本配置
subagents 参数接受字典列表或 CompiledSubAgent 对象列表
Deep Agents 提供两种方式定义子代理:
字典方式(SubAgent)
适合大多数场景。用普通对象定义子代理的名称、描述、提示词和工具。
简单、推荐首选
编译方式(CompiledSubAgent)
适合复杂工作流。使用预编译的 LangGraph 图作为子代理的执行逻辑。
灵活、高级场景
general-purpose 子代理(具有文件系统工具),除非你已经提供了一个同名的子代理。要禁用它,设置 general_purpose_subagent.enabled = False,而不是用 excluded_middleware。
SubAgent 字段
字典方式定义子代理时,有 3 个必填字段和 7 个可选字段
| 字段 | 必填 | 说明 |
|---|---|---|
name |
是 | 唯一标识符,主代理调用 task() 时使用 |
description |
是 | 描述子代理功能,主代理据此决定何时委派 |
systemPrompt |
是 | 子代理的指令,不继承主代理的提示词 |
tools |
否 | 子代理可用的工具列表。默认从主代理继承。指定后会完全覆盖继承的工具。 |
model |
否 | 覆盖主代理模型。可传字符串如 "openai:gpt-5.4"("provider:model" 格式)或 LangChain 聊天模型对象(如 await initChatModel("gpt-5.4"))。默认从主代理继承。 |
middleware |
否 | 额外中间件,用于日志、限流等 |
skills |
否 | 技能源路径(如 ["/skills/research/"])。指定后子代理从这些目录加载技能,拥有独立技能集。不从主代理继承。技能状态完全隔离——子代理技能对父代理不可见,反之亦然。 |
responseFormat |
否 | 结构化输出模式,返回 JSON 而非自由文本 |
interruptOn |
否 | 为特定工具配置人机协作中断。子代理的值会覆盖主代理的设置。需要检查点(checkpointer)。默认从主代理继承。 |
permissions |
否 | 文件访问权限列表(FilesystemPermission[])。设置后完全替换父代理权限,而非追加。 |
tools 和 model 默认从主代理继承;systemPrompt、middleware、skills 则不继承,必须显式定义。记清楚哪些会自动继承,哪些需要手动设置。
创建研究子代理
实战:定义一个专门做网络调研的子代理
import { tool } from "langchain";
import { TavilySearch } from "@langchain/tavily";
import { createDeepAgent } from "deepagents";
import { z } from "zod";
// 1. 定义搜索工具:用 Tavily 搜索引擎封装为 LangChain 工具
const internetSearch = tool(
async ({
query,
maxResults = 5,
topic = "general",
includeRawContent = false,
}: {
query: string;
maxResults?: number;
topic?: "general" | "news" | "finance";
includeRawContent?: boolean;
}) => {
// 每次调用时创建 Tavily 实例,使用环境变量中的 API Key
const tavily = new TavilySearch({
maxResults,
tavilyApiKey: process.env.TAVILY_API_KEY,
includeRawContent,
topic,
});
return await tavily._call({ query }); // 执行搜索并返回结果
},
{
name: "internet_search",
// description 要写清楚工具做什么,让主 Agent 知道何时委派
description: "Run a web search",
// schema 定义参数结构,describe() 标注每个参数的含义
schema: z.object({
query: z.string().describe("The search query"),
maxResults: z.number().optional().default(5),
topic: z
.enum(["general", "news", "finance"])
.optional()
.default("general"),
includeRawContent: z.boolean().optional().default(false),
}),
}
);
// 2. 定义研究子代理:专门做网络调研,隔离搜索过程中的中间结果
const researchSubagent = {
name: "research-agent", // 主 Agent 用 task(name) 调用时传这个名字
description: "Used to research more in depth questions", // 主 Agent 据此决定何时委派
systemPrompt: "You are a great researcher", // 子代理的指令,不继承主代理的提示词
tools: [internetSearch], // 仅限搜索工具,保持子代理专注
model: "google-genai:gemini-3.1-pro-preview", // 可选:覆盖主代理模型
};
// 3. 创建主代理,注册子代理
const agent = createDeepAgent({
model: "google-genai:gemini-3.1-pro-preview", // 主代理使用的模型
subagents: [researchSubagent], // 主 Agent 会自动获得 task 工具来委派任务
});
各提供商完整代码(点击展开)
以下代码与上方示例基本相同,唯一区别是 model 字段的值。单击提供商名称查看完整代码:
OpenAI — "openai:gpt-5.4"
import { tool } from "langchain";
import { TavilySearch } from "@langchain/tavily";
import { createDeepAgent, type SubAgent } from "deepagents";
import { z } from "zod";
const internetSearch = tool(
async ({
query, maxResults = 5, topic = "general", includeRawContent = false,
}: {
query: string; maxResults?: number; topic?: "general" | "news" | "finance"; includeRawContent?: boolean;
}) => {
const tavilySearch = new TavilySearch({
maxResults, tavilyApiKey: process.env.TAVILY_API_KEY, includeRawContent, topic,
});
return await tavilySearch._call({ query });
},
{
name: "internet_search", description: "Run a web search",
schema: z.object({
query: z.string().describe("The search query"),
maxResults: z.number().optional().default(5),
topic: z.enum(["general", "news", "finance"]).optional().default("general"),
includeRawContent: z.boolean().optional().default(false),
}),
},
);
const researchSubagent: SubAgent = {
name: "research-agent",
description: "Used to research more in depth questions",
systemPrompt: "You are a great researcher",
tools: [internetSearch],
model: "openai:gpt-5.4",
};
const subagents = [researchSubagent];
const agent = createDeepAgent({
model: "google-genai:gemini-3.1-pro-preview",
subagents,
});
Anthropic — "anthropic:claude-sonnet-4-6"
import { tool } from "langchain";
import { TavilySearch } from "@langchain/tavily";
import { createDeepAgent, type SubAgent } from "deepagents";
import { z } from "zod";
const internetSearch = tool(
async ({
query, maxResults = 5, topic = "general", includeRawContent = false,
}: {
query: string; maxResults?: number; topic?: "general" | "news" | "finance"; includeRawContent?: boolean;
}) => {
const tavilySearch = new TavilySearch({
maxResults, tavilyApiKey: process.env.TAVILY_API_KEY, includeRawContent, topic,
});
return await tavilySearch._call({ query });
},
{
name: "internet_search", description: "Run a web search",
schema: z.object({
query: z.string().describe("The search query"),
maxResults: z.number().optional().default(5),
topic: z.enum(["general", "news", "finance"]).optional().default("general"),
includeRawContent: z.boolean().optional().default(false),
}),
},
);
const researchSubagent: SubAgent = {
name: "research-agent",
description: "Used to research more in depth questions",
systemPrompt: "You are a great researcher",
tools: [internetSearch],
model: "anthropic:claude-sonnet-4-6",
};
const subagents = [researchSubagent];
const agent = createDeepAgent({
model: "google-genai:gemini-3.1-pro-preview",
subagents,
});
OpenRouter — "openrouter:anthropic/claude-sonnet-4-6"
import { tool } from "langchain";
import { TavilySearch } from "@langchain/tavily";
import { createDeepAgent, type SubAgent } from "deepagents";
import { z } from "zod";
const internetSearch = tool(
async ({
query, maxResults = 5, topic = "general", includeRawContent = false,
}: {
query: string; maxResults?: number; topic?: "general" | "news" | "finance"; includeRawContent?: boolean;
}) => {
const tavilySearch = new TavilySearch({
maxResults, tavilyApiKey: process.env.TAVILY_API_KEY, includeRawContent, topic,
});
return await tavilySearch._call({ query });
},
{
name: "internet_search", description: "Run a web search",
schema: z.object({
query: z.string().describe("The search query"),
maxResults: z.number().optional().default(5),
topic: z.enum(["general", "news", "finance"]).optional().default("general"),
includeRawContent: z.boolean().optional().default(false),
}),
},
);
const researchSubagent: SubAgent = {
name: "research-agent",
description: "Used to research more in depth questions",
systemPrompt: "You are a great researcher",
tools: [internetSearch],
model: "openrouter:anthropic/claude-sonnet-4-6",
};
const subagents = [researchSubagent];
const agent = createDeepAgent({
model: "google-genai:gemini-3.1-pro-preview",
subagents,
});
Fireworks — "fireworks:accounts/fireworks/models/qwen3p5-397b-a17b"
import { tool } from "langchain";
import { TavilySearch } from "@langchain/tavily";
import { createDeepAgent, type SubAgent } from "deepagents";
import { z } from "zod";
const internetSearch = tool(
async ({
query, maxResults = 5, topic = "general", includeRawContent = false,
}: {
query: string; maxResults?: number; topic?: "general" | "news" | "finance"; includeRawContent?: boolean;
}) => {
const tavilySearch = new TavilySearch({
maxResults, tavilyApiKey: process.env.TAVILY_API_KEY, includeRawContent, topic,
});
return await tavilySearch._call({ query });
},
{
name: "internet_search", description: "Run a web search",
schema: z.object({
query: z.string().describe("The search query"),
maxResults: z.number().optional().default(5),
topic: z.enum(["general", "news", "finance"]).optional().default("general"),
includeRawContent: z.boolean().optional().default(false),
}),
},
);
const researchSubagent: SubAgent = {
name: "research-agent",
description: "Used to research more in depth questions",
systemPrompt: "You are a great researcher",
tools: [internetSearch],
model: "fireworks:accounts/fireworks/models/qwen3p5-397b-a17b",
};
const subagents = [researchSubagent];
const agent = createDeepAgent({
model: "google-genai:gemini-3.1-pro-preview",
subagents,
});
Baseten — "baseten:zai-org/GLM-5"
import { tool } from "langchain";
import { TavilySearch } from "@langchain/tavily";
import { createDeepAgent, type SubAgent } from "deepagents";
import { z } from "zod";
const internetSearch = tool(
async ({
query, maxResults = 5, topic = "general", includeRawContent = false,
}: {
query: string; maxResults?: number; topic?: "general" | "news" | "finance"; includeRawContent?: boolean;
}) => {
const tavilySearch = new TavilySearch({
maxResults, tavilyApiKey: process.env.TAVILY_API_KEY, includeRawContent, topic,
});
return await tavilySearch._call({ query });
},
{
name: "internet_search", description: "Run a web search",
schema: z.object({
query: z.string().describe("The search query"),
maxResults: z.number().optional().default(5),
topic: z.enum(["general", "news", "finance"]).optional().default("general"),
includeRawContent: z.boolean().optional().default(false),
}),
},
);
const researchSubagent: SubAgent = {
name: "research-agent",
description: "Used to research more in depth questions",
systemPrompt: "You are a great researcher",
tools: [internetSearch],
model: "baseten:zai-org/GLM-5",
};
const subagents = [researchSubagent];
const agent = createDeepAgent({
model: "google-genai:gemini-3.1-pro-preview",
subagents,
});
Ollama — "ollama:devstral-2"
import { tool } from "langchain";
import { TavilySearch } from "@langchain/tavily";
import { createDeepAgent, type SubAgent } from "deepagents";
import { z } from "zod";
const internetSearch = tool(
async ({
query, maxResults = 5, topic = "general", includeRawContent = false,
}: {
query: string; maxResults?: number; topic?: "general" | "news" | "finance"; includeRawContent?: boolean;
}) => {
const tavilySearch = new TavilySearch({
maxResults, tavilyApiKey: process.env.TAVILY_API_KEY, includeRawContent, topic,
});
return await tavilySearch._call({ query });
},
{
name: "internet_search", description: "Run a web search",
schema: z.object({
query: z.string().describe("The search query"),
maxResults: z.number().optional().default(5),
topic: z.enum(["general", "news", "finance"]).optional().default("general"),
includeRawContent: z.boolean().optional().default(false),
}),
},
);
const researchSubagent: SubAgent = {
name: "research-agent",
description: "Used to research more in depth questions",
systemPrompt: "You are a great researcher",
tools: [internetSearch],
model: "ollama:devstral-2",
};
const subagents = [researchSubagent];
const agent = createDeepAgent({
model: "google-genai:gemini-3.1-pro-preview",
subagents,
});
关键点
name是主代理委派任务时的唯一标识description要具体,让主代理知道何时调用tools只给需要的,保持专注
各提供商 model 值
子代理的 model 字段使用 "provider:model" 格式。省略则继承主代理。
| 提供商 | model 值示例 |
|---|---|
"google-genai:gemini-3.1-pro-preview" | |
| OpenAI | "openai:gpt-5.4" |
| Anthropic | "anthropic:claude-sonnet-4-6" |
| OpenRouter | "openrouter:anthropic/claude-sonnet-4-6" |
| Fireworks | "fireworks:accounts/fireworks/models/qwen3p5-397b-a17b" |
| Baseten | "baseten:zai-org/GLM-5" |
| Ollama | "ollama:devstral-2" |
编译式子代理
用预编译的 LangGraph 图作为子代理——适合复杂工作流
当字典方式不够灵活时,你可以用 CompiledSubAgent 包装一个完整的 LangGraph 图。
import { createDeepAgent } from "deepagents";
import { createAgent } from "langchain";
// 1. 创建自定义代理图:用 LangGraph 构建多节点工作流
const customGraph = createAgent({
model: yourModel, // 自定义模型
tools: specializedTools, // 专门的数据分析工具集
prompt: "You are a specialized agent for data analysis...",
});
// 2. 包装为 CompiledSubAgent,用 runnable 字段传入已编译的图
const customSubagent = {
name: "data-analyzer", // 子代理标识
description: "Specialized agent for complex data analysis tasks",
runnable: customGraph, // 必须是已编译的 LangGraph 图
};
// 3. 注册到主代理,子代理即可通过 task 工具被调用
const agent = createDeepAgent({
model: "google-genai:gemini-3.1-pro-preview",
tools: [internetSearch],
systemPrompt: researchInstructions,
subagents: [customSubagent],
});
字典方式 vs 编译方式
- 字典方式:适合简单的"给提示词+工具"场景
- 编译方式:适合需要自定义图结构、多节点流转的复杂工作流
CompiledSubAgent 必填字段
name— 子代理的唯一标识description— 功能描述runnable— 已编译的 LangGraph 图(必须先.compile())
"messages" 的状态键,否则子代理无法正确与主代理通信。
结构化输出
让子代理返回可预测的 JSON,而非自由文本
默认情况下,父代理接收子代理最后一条消息的文本。设置 responseFormat 后,父代理获得符合 schema 的有效 JSON。
import { z } from "zod";
import { createDeepAgent } from "deepagents";
// 定义结构化输出的 schema:子代理返回值必须符合此结构
const ResearchFindings = z.object({
summary: z.string().describe("Summary of findings"), // 研究摘要
confidence: z.number().describe("Confidence score from 0 to 1"), // 置信度
sources: z.array(z.string()).describe("List of source URLs"), // 来源列表
});
const researchSubagent = {
name: "researcher",
description: "Researches topics and returns structured findings",
systemPrompt: "Research the given topic thoroughly. Return your findings.",
tools: [webSearch],
responseFormat: ResearchFindings, // 设置后,子代理返回 JSON 而非自由文本
};
const agent = createDeepAgent({
model: "claude-sonnet-4-6",
subagents: [researchSubagent],
});
// 调用后,父代理收到 JSON 序列化的结构化数据(可编程处理):
// '{"summary": "...", "confidence": 0.87, "sources": ["https://..."]}'
何时使用
- 父代理需要编程方式处理结果时
- 结果需要传递给下游工具时
- 需要确保输出格式一致时
支持的 schema 类型
- Zod schema(推荐)
- JSON Schema 对象
toolStrategy(...)providerStrategy(...)
deepagents >= 1.8.4。检查你的版本:npm list deepagents
通用子代理
Deep Agents 自动提供的 general-purpose 子代理——适合无需专门行为的上下文隔离
除了自定义子代理,每个 Deep Agent 都能访问一个内置的通用子代理。
默认行为
- 继承主代理的系统提示词
- 继承主代理的所有工具
- 继承主代理的模型
- 继承主代理的技能
如何自定义
- 替换:传入
name: "general-purpose"的自定义子代理 - 禁用:设置
general_purpose_subagent.enabled = False - 重命名:在 harness 配置中设置
// 替换默认的 general-purpose 子代理(同名即覆盖内置的)
const agent = createDeepAgent({
model: "google-genai:gemini-3.1-pro-preview",
tools: [internetSearch],
subagents: [
{
name: "general-purpose", // 名称与内置子代理相同,会覆盖默认的
description: "General-purpose agent for research and multi-step tasks",
systemPrompt: "You are a general-purpose assistant.",
tools: [internetSearch], // 可以自定义工具集
model: "openai:gpt-5.4", // 使用不同于主代理的模型
},
],
});
task(name="general-purpose", task="Research quantum computing trends")。子代理在内部执行所有搜索,只返回摘要。
流式追踪
通过 lc_agent_name 元数据区分不同代理的执行记录
在流式传输追踪信息时,每个代理运行都带有 lc_agent_name 元数据,帮助你区分数据来自哪个代理。
const researchSubagent = {
name: "research-agent", // 这个名称会出现在元数据中
description: "Used to research more in depth questions",
systemPrompt: "You are a great researcher",
tools: [internetSearch],
model: "google-genai:gemini-3.1-pro-preview",
};
const agent = createDeepAgent({
model: "google-genai:gemini-3.1-pro-preview",
subagents: [researchSubagent],
name: "main-agent" // 给主代理也命名
});
// 在 LangSmith 追踪中:
// main-agent 的运行 → { lc_agent_name: "main-agent" }
// research-agent 的运行 → { lc_agent_name: "research-agent" }
Python 版本:
import os
from typing import Literal
from tavily import TavilyClient
from deepagents import create_deep_agent
tavily_client = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])
def internet_search(
query: str,
max_results: int = 5,
topic: Literal["general", "news", "finance"] = "general",
include_raw_content: bool = False,
):
"""Run a web search"""
return tavily_client.search(
query,
max_results=max_results,
include_raw_content=include_raw_content,
topic=topic,
)
research_subagent = {
"name": "research-agent",
"description": "Used to research more in depth questions",
"system_prompt": "You are a great researcher",
"tools": [internet_search],
"model": "google-genai:gemini-3.1-pro-preview", # 可选覆盖
}
subagents = [research_subagent]
agent = create_deep_agent(
model="google-genai:gemini-3.1-pro-preview",
subagents=subagents,
name="main-agent"
)
向 deep Agent 发送提示后,子代理或 deep Agent 执行的所有代理运行都会在其元数据中包含代理名称。例如,名为 research-agent 的子代理,在所有相关的代理运行元数据中将包含 {'lc_agent_name': 'research-agent'}:
在 LangSmith 追踪界面中,每个代理运行的元数据都包含 lc_agent_name 字段,你可以据此按代理过滤和分组追踪数据,清晰区分各个子代理的执行情况。
LangSmith 追踪示例(元数据视图):
┌─────────────────────────────────────────────────────────┐
│ Run: main-agent lc_agent_name: main-agent │
│ ├── Run: research-agent lc_agent_name: research-agent │
│ │ ├── Tool: internet_search │
│ │ └── Tool: internet_search │
│ └── Run: research-agent lc_agent_name: research-agent │
│ └── Tool: internet_search │
└─────────────────────────────────────────────────────────┘
追踪信息用途
- 在 LangSmith 中按代理过滤追踪数据
- 分析每个子代理的性能和成本
- 调试子代理的决策路径
最佳实践
- 给主代理和每个子代理都设置有意义的名称
- 名称用小写+连字符格式(如
data-collector) - 名称会出现在
AIMessage和流式传输的元数据中
写好描述
描述是主代理决定调用谁的唯一依据——必须具体且面向操作
差的描述(太模糊)
// ❌ 主代理无法判断何时该用
{ name: "helper", description: "helps with stuff" }
// ❌ 太笼统,无法区分用途
{ name: "finance-agent",
description: "做金融相关的事" }
好的描述(具体可操作)
// ✅ 明确说明做什么、何时用
{ name: "research-specialist",
description: "Conducts in-depth research " +
"using web search. Use when you need " +
"detailed information requiring " +
"multiple searches." }
// ✅ 包含具体功能描述
{ name: "financial-analyst",
description: "分析金融数据并生成带有" +
"置信度评分的投资洞察" }
[做什么] + [用什么工具] + [什么时候用]。好的描述让主代理能准确判断何时委派,避免调用错误的子代理或重复自己做。
提示词与工具
详细的系统提示词 + 最小化的工具集 = 高效的子代理
const researchSubagent = {
name: "research-agent",
description: "Conducts in-depth research using web search",
systemPrompt: `你是一位细致的研究员。你的工作是:
1. 将研究问题分解为可搜索的查询
2. 使用 internet_search 查找相关信息
3. 将发现综合成全面但简洁的摘要
4. 在提出论断时引用来源
输出格式:
- 摘要(2-3 段)
- 主要发现(要点列表)
- 来源(附 URL)
将回复控制在 500 字以内,以保持上下文整洁。`,
tools: [internetSearch], // 只给需要的工具
};
工具过多(不聚焦)
// ❌ 邮件代理不需要搜索和上传
const emailAgent = {
name: "email-sender",
tools: [sendEmail, webSearch,
databaseQuery, fileUpload],
};
最小工具集(聚焦)
// ✅ 只有邮件相关的工具
const emailAgent = {
name: "email-sender",
tools: [sendEmail, validateEmail],
};
模型选择策略
不同任务用不同模型——大上下文、数值分析各有擅长
const subagents = [
{
name: "contract-reviewer",
description: "Reviews legal documents and contracts",
systemPrompt: "You are an expert legal reviewer...",
tools: [readDocument, analyzeContract],
model: "google-genai:gemini-3.1-pro-preview",
// 大上下文窗口,适合长文档审查
},
{
name: "financial-analyst",
description: "Analyzes financial data and market trends",
systemPrompt: "You are an expert financial analyst...",
tools: [getStockPrice, analyzeFundamentals],
model: "gpt-5.4",
// 更适合数值分析和推理
},
];
按任务选模型
- 长文档处理:选大上下文模型
- 数值分析:选推理能力强的模型
- 简单任务:用小模型降低成本
- 创意写作:选擅长语言的模型
模型覆盖格式
"openai:gpt-5.4""anthropic:claude-sonnet-4-6""google-genai:gemini-3.1-pro-preview""ollama:devstral-2"- 格式:
"provider:model"
多代理协作
创建多个专门子代理,分工协作完成复杂任务
const subagents = [
{
name: "data-collector",
description: "Gathers raw data from various sources",
systemPrompt: "Collect comprehensive data on the topic",
tools: [webSearch, apiCall, databaseQuery],
},
{
name: "data-analyzer",
description: "Analyzes collected data for insights",
systemPrompt: "Analyze data and extract key insights",
tools: [statisticalAnalysis],
},
{
name: "report-writer",
description: "Writes polished reports from analysis",
systemPrompt: "Create professional reports from insights",
tools: [formatDocument],
},
];
const agent = createDeepAgent({
model: "google-genai:gemini-3.1-pro-preview",
systemPrompt: "You coordinate data analysis and reporting.",
subagents,
});
1. 主代理规划
创建高层任务计划
2. 收集数据
委派给 data-collector
3. 分析数据
委派给 data-analyzer
4. 撰写报告
委派给 report-writer
5. 编译输出
主代理汇总最终结果
卸甲:说"主代理像项目经理"只是为了帮你理解分工关系。实际上主 Agent 只是一个更大的语言模型实例,它调用
task 工具时,框架为其创建一个新的 Agent 实例(子 Agent)。"协调"仅仅是主 Agent 按顺序调用不同子 Agent 并拼接它们的输出——这不是有意识的项目管理。
上下文管理
父代理的运行时上下文自动传播到所有子代理
当你调用父代理时传入的运行时上下文,会自动流到每个子代理及其工具中。
import { z } from "zod";
const contextSchema = z.object({
userId: z.string(),
sessionId: z.string(),
});
// 工具通过 runtime 参数获取运行时上下文(无需手动传递)
const getUserData = tool(
async (input, runtime) => {
const userId = runtime.context?.userId; // 从 context 中读取用户 ID
return `Data for user ${userId}: ${input.query}`;
},
{
name: "get_user_data",
description: "Fetch data for the current user",
schema: z.object({ query: z.string() }),
}
);
const researchSubagent = {
name: "researcher",
description: "Conducts research for the current user",
systemPrompt: "You are a research assistant.",
tools: [getUserData], // 子代理中使用需要上下文的工具
};
const agent = createDeepAgent({
model: "google-genai:gemini-3.1-pro-preview",
subagents: [researchSubagent],
contextSchema, // 声明上下文结构
});
// 调用时传入上下文,会自动流到 researcher 子代理及其 getUserData 工具
const result = await agent.invoke(
{ messages: [{ role: "user", content: "Look up my recent activity" }] },
{ context: { userId: "user-123", sessionId: "abc" } },
);
invoke 时传入的 context,所有子代理的工具都能通过 runtime.context 访问。
按代理配置
用命名空间键为不同子代理传递不同的配置
const result = await agent.invoke(
{ messages: [{ role: "user", content: "Research and verify" }] },
{
context: {
userId: "user-123", // 所有代理共享
"researcher:maxDepth": 3, // 仅限 researcher
"fact-checker:strictMode": true, // 仅限 fact-checker
},
},
);
// 在工具中识别调用者
const sharedLookup = tool(
async (input, runtime) => {
const agentName = runtime.config?.metadata?.lc_agent_name;
if (agentName === "fact-checker") {
return strictLookup(input.query); // fact-checker 用严格模式
}
return generalLookup(input.query); // 其他代理用通用模式
},
{
name: "shared_lookup",
description: "Look up information",
schema: z.object({ query: z.string() }),
}
);
Typed contextSchema 替代方案:也可以将代理特定设置建模为 contextSchema 中的独立字段,并用专用工具读取:
// 带 typed fields 的 contextSchema
const contextSchema = z.object({
userId: z.string(),
researcherMaxDepth: z.number().optional(),
factCheckerStrictMode: z.boolean().optional(),
});
// 根据上下文字段分支的工具
const verifyClaim = tool(
async (input, runtime) => {
const strictMode = runtime.context?.factCheckerStrictMode ?? false;
if (strictMode) {
return strictVerification(input.claim);
}
return basicVerification(input.claim);
},
{
name: "verify_claim",
description: "Verify a factual claim",
schema: z.object({ claim: z.string() }),
}
);
组合模式:context + lc_agent_name
你可以结合 runtime.context 和 runtime.config.metadata.lc_agent_name,在同一个工具中根据调用代理分支行为:
const flexibleSearch = tool(
async (input, runtime) => {
const agentName = runtime.config?.metadata?.lc_agent_name ?? "unknown";
const ctx = runtime.context;
const maxResults =
agentName === "researcher" ? ctx?.researcherMaxDepth ?? 5 : 5;
const includeRaw = false;
return performSearch(input.query, { maxResults, includeRaw });
},
{
name: "flexible_search",
description: "Search with agent-specific settings",
schema: z.object({ query: z.string() }),
}
);
命名空间键
用 "子代理名:键名" 格式传递子代理特定配置。例如 "researcher:maxDepth" 只有 researcher 子代理能读到。
识别调用者
通过 runtime.config?.metadata?.lc_agent_name 获取调用工具的代理名称,据此分支行为。
技能继承
general-purpose 子代理继承主代理技能,自定义子代理默认不继承
import { createDeepAgent } from "deepagents";
// 拥有自己技能的研究子代理
const researchSubagent = {
name: "researcher",
description: "Research assistant with specialized skills",
systemPrompt: "You are a researcher.",
tools: [webSearch],
skills: ["/skills/research/", "/skills/web-search/"],
};
const agent = createDeepAgent({
model: "google-genai:gemini-3.1-pro-preview",
skills: ["/skills/main/"], // 主代理和 GP 子代理获得这些
subagents: [researchSubagent], // 仅获得自己的技能路径
});
General-purpose 子代理
- 自动继承主代理的技能
- 运行自己的 SkillsMiddleware 实例
- 技能状态与主代理隔离
自定义子代理
- 默认不继承技能
- 需用
skills参数显式配置 - 技能状态双向隔离
常见问题排查
子代理未被调用?上下文仍然膨胀?选错了子代理?
子代理未被调用
原因:主代理觉得自己能搞定。
- 让
description更具体,明确何时使用 - 在主代理 systemPrompt 中指示其委派任务
// ✅ 好的描述
{ name: "research-specialist",
description: "Conducts in-depth research " +
"requiring multiple searches." }
// ❌ 差的描述
{ name: "helper",
description: "helps with stuff" }
额外方案——在 systemPrompt 中明确指示委派:
const agent = createDeepAgent({
systemPrompt: `...你的指令...
重要提示:对于复杂任务,请使用 task() 工具
委派给你的子代理。这可以保持你的上下文
整洁并改善结果。`,
subagents: [...]
});
上下文仍然膨胀
原因:子代理返回了太多内容。
- 在 systemPrompt 中指示子代理返回简洁摘要
- 用文件系统处理大数据,只返回分析结果
systemPrompt: `...
仅返回必要的摘要。
不要包含原始数据或中间结果。
回复控制在 500 字以内。`
选错了子代理
原因:子代理描述不够区分。
- 在描述中清晰区分不同子代理的适用场景
- 使用"简单/快速"vs"复杂/深度"等关键词
不使用子代理运行
如果不需要委派功能:
- 设置
general_purpose_subagent.enabled = False - 不在
subagents中传入任何同步子代理 - 不要用
excluded_middleware(会报错)
返回简洁结果
教导子代理只返回摘要,不返回原始数据——这是上下文管理的关键
白话:子代理的输出会直接进入主 Agent 的上下文。如果子代理返回了 100 行搜索结果,这些结果全部占据主 Agent 的 Token 预算。所以你要在子代理的提示词里明确说"只给摘要"。
const dataAnalyst = {
name: "data-analyst",
description: "Analyzes data and returns concise insights",
systemPrompt: `分析数据并返回:
1. 关键洞察(3-5 个要点)
2. 总体置信度评分
3. 建议的下一步行动
不要包含:
- 原始数据
- 中间计算过程
- 详细的工具输出
将回复控制在 300 字以内。`,
tools: [statisticalAnalysis],
};
// 大数据处理策略
const bigDataAgent = {
systemPrompt: `当你收集大量数据时:
1. 将原始数据保存到 /data/raw_results.txt
2. 处理和分析数据
3. 仅返回分析摘要
这可以保持上下文整洁。`,
};
本章回顾
子代理是 Deep Agent 从"单兵作战"到"团队协作"的核心机制
你学到了什么
- 子代理解决上下文膨胀问题——主代理只看结果
- 两种定义方式:字典(SubAgent)和编译(CompiledSubAgent)
- 内置的 general-purpose 子代理适合通用委派
- 结构化输出让父代理获得可解析的 JSON
- 运行时上下文自动传播到所有子代理
- 技能继承规则:GP 子代理继承,自定义子代理不继承
后续章节预告
- 异步子代理:长时间运行、并行工作流、中途取消
- 人机协作:interrupt_on 配置与人机交互
- 权限管理:控制子代理可以执行哪些操作
- 内存管理:短期和长期记忆机制
- 技能系统:为子代理加载专门的技能集