深度代理可以创建子代理来委托工作。您可以在 subagents 参数中指定自定义子代理。子代理适用于 上下文隔离 (保持主代理的上下文清洁)和提供专门指令。
本页面涵盖 **同步** 子代理,即主管阻塞直到子代理完成。对于长时间运行的任务、并行工作流或需要中途控制和取消的情况,请参阅 异步子代理.
graph TB
Main[Main Agent] --> |task tool| Sub[Subagent]
Sub --> Research[Research]
Sub --> Code[Code]
Sub --> General[General]
Research --> |isolated work| Result[Final Result]
Code --> |isolated work| Result
General --> |isolated work| Result
Result --> Main
为什么要使用子代理?
子代理解决 **上下文膨胀问题**。当代理使用产生大量输出的工具(网络搜索、文件读取、数据库查询)时,上下文窗口会迅速被中间结果填满。子代理将这项详细工作隔离开来——主代理只接收最终结果,而不是生成该结果的数十次工具调用。
何时使用子代理: - ✅ 会使主代理上下文变得混乱的多步骤任务 - ✅ 需要自定义指令或工具的专门领域 - ✅ 需要不同模型能力的任务 - ✅ 当您希望主代理专注于高级协调时
何时不使用子代理: - ❌ 简单的单步任务 - ❌ 当需要维护中间上下文时 - ❌ 当开销大于收益时
配置
subagents 应该是一个字典列表或CompiledSubAgent对象。有两种类型:
默认子代理
深度代理自动添加一个同步 general-purpose 子代理,除非您已经提供了一个具有该名称的同步子代理。
general-purpose subagent has filesystem tools by default and can be customized with additional tools/middleware.
- - 要替换它,请传递您自己的名为
general-purpose. - - 要重命名或重新提示自动添加的版本,请设置
general_purpose_subagent=GeneralPurposeSubagentProfile(...)在活动的 测试配置文件上. - - 要禁用它,请参阅 不使用子代理运行 below.
不使用子代理运行
要运行一个不带 task 工具的代理,请做两件事:
- 设置
general_purpose_subagent=GeneralPurposeSubagentProfile(enabled=False)在活动的 测试配置文件上. - 通过
subagents=oncreate_deep_agent.
深度代理仅在至少存在一个同步子代理时才会附加SubAgentMiddleware(以及 task 工具)。如果既没有默认的也没有调用者提供的,代理将不进行委托运行。
异步子代理不受影响——它们通过自己的中间件和工具流动,详见 异步子代理.
自定义子代理
您可以使用以下方式定义具有特定工具的专用子代理 subagents 参数。例如,作为代码审查员、网络研究员或测试运行器。
对于大多数用例,请使用字典定义子代理 SubAgent 字典。对于复杂工作流,请使用 CompiledSubAgent:
SubAgent(基于字典)
将子代理定义为与 SubAgent 规范匹配的字典,包含以下字段:
| 字段 | 类型 | 描述 |
|---|---|---|
name | str | 必填。子代理的唯一标识符。主代理使用此名称调用 task() 工具。子代理名称成为 AIMessage和流式处理的元数据,有助于区分不同代理。 |
description | str | 必填。对此子代理功能的描述。请具体且以动作为导向。主代理使用此描述来决定何时委托。 |
system_prompt | str | 必填。子代理的指令。自定义子代理必须定义自己的指令。包括工具使用指南和输出格式要求。<br></br>不从主代理继承。 |
tools | list[Callable] | 可选。子代理可用的工具。请保持最小化,仅包含所需内容。<br></br>默认从主代理继承。指定后,完全覆盖继承的工具。 |
model | str \ | BaseChatModel |
name | string | 必填。子代理的唯一标识符。主代理使用此名称调用 task() 工具。子代理名称成为 AIMessage和流式处理的元数据,有助于区分不同代理。 |
description | string | 必填。对此子代理功能的描述。请具体且以动作为导向。主代理使用此描述来决定何时委托。 |
systemPrompt | string | 必填。子代理的指令。自定义子代理必须定义自己的指令。包括工具使用指南和输出格式要求。<br></br>不从主代理继承。 |
tools | StructuredTool[] | 可选。子代理可用的工具。请保持最小化,仅包含所需内容。<br></br>默认从主代理继承。指定后,完全覆盖继承的工具。 |
model | `LanguageModelLike \ | string` |
middleware | AgentMiddleware[] | 可选。用于自定义行为、日志记录或速率限制的其他中间件。<br></br>不从主代理继承。追加到 默认子代理堆栈. |
interruptOn | `Record<string, boolean \ | InterruptOnConfig>` |
skills | string[] | 可选。 技能 源路径。指定后,子代理将从这些目录加载技能(例如, ["/skills/research/", "/skills/web-search/"])。这使得子代理可以拥有与主代理不同的技能集。<br></br>不从主代理继承。只有通用子代理继承主代理的技能。当子代理拥有技能时,它运行自己的独立SkillsMiddleware实例。技能状态完全隔离——子代理加载的技能对父代理不可见,反之亦然。 |
responseFormat | ResponseFormat | 可选。 结构化输出 子代理的结构化输出模式。设置后,父代理以JSON形式接收子代理的结果,而非自由格式文本。接受Zod模式、JSON模式对象、 toolStrategy(...), or providerStrategy(...)。请参阅 结构化输出. |
permissions | FilesystemPermission[] | 可选。 文件系统权限规则 子代理的文件系统权限规则。设置后, **完全替换** 父代理的权限。<br></br>默认从主代理继承。 |
CompiledSubAgent
对于复杂的工作流,请使用预构建的LangGraph图作为CompiledSubAgent:
| 字段 | 类型 | 描述 |
|---|---|---|
name | str | 必填。子代理的唯一标识符。子代理名称成为 AIMessage的元数据,并在流式传输时使用,有助于区分不同的代理。 |
description | str | 必填。此子代理的用途。 |
runnable | Runnable | 必填。已编译的LangGraph图(必须先调用 .compile() )。 |
使用 SubAgent
使用 CompiledSubAgent
对于更复杂的用例,您可以使用CompiledSubAgent. 创建自定义子代理。您可以使用LangChain的create_agent或使用 图API.
创建自定义LangGraph图来创建自定义子代理。如果您要创建自定义LangGraph图,请确保该图有一个名为 的状态键 "messages":
// Create a custom agent graph
const customGraph = createAgent({
model: yourModel,
tools: specializedTools,
prompt: "You are a specialized agent for data analysis...",
});
// Use it as a custom subagent
const customSubagent: CompiledSubAgent = {
name: "data-analyzer",
description: "Specialized agent for complex data analysis tasks",
runnable: customGraph,
};
const subagents = [customSubagent];
const agent = createDeepAgent({
model: "google_genai:gemini-3.5-flash",
tools: [internetSearch],
systemPrompt: researchInstructions,
subagents: subagents,
});
动态子代理
默认情况下,主代理通过 task 工具调用委托给子代理(它可以在单轮中发出多个工具调用以并行运行)。使用 解释器 后,代理可以改为从代码中 **分派子代理**——使用循环、分支和并行批次将工作分散到多个项目,并以编程方式综合结果。这称为 动态子代理.
当工作跨越多个独立单元(审查目录中的每个文件、分诊一批工单)、需要多个视角或受益于递归分析时,请使用动态子代理。
启用动态子代理
一旦代理同时拥有子代理和解释器中间件,动态子代理即可用。安装QuickJS解释器包,然后向您的代理添加 CodeInterpreterMiddleware 。
npm install deepagents @langchain/quickjs
pnpm add deepagents @langchain/quickjs
yarn add deepagents @langchain/quickjs
const agent = createDeepAgent({
model: "openai:gpt-5.5",
subagents: [{
name: "reviewer",
description: "Reviews code for security issues, citing lines and severity",
systemPrompt: "You are a security-focused code reviewer. Report issues with line numbers and severity.",
}],
middleware: [createCodeInterpreterMiddleware()],
});
触发动态编排
动态调度是隐式的:代理根据任务的形态决定从代码中分叉工作,而不是通过每次调用标志。
例如,将请求表述为 "workflow" 可以选择从代码中分叉:
const result = await agent.invoke({
messages: [{ role: "user", content: "Run a workflow that reviews every file in src/routes/ and summarizes the top risks." }],
});
有关配置、高级编排模式和安全注意事项,请参阅 动态子代理.
与编码代理配合使用
尝试动态子代理最快的方式是使用 dcode,这是构建在 Deep Agent 之上的 LangChain 终端编码代理。它默认启用了代码解释器,因此动态子代理开箱即用,无需任何配置。
安装 dcode:
curl -LsSf https://langch.in/dcode | bash
运行它:
dcode
要触发动态子代理,请请求一个 "workflow"。而不是自己埋头苦干或通过其原生的 task 工具,代理会编写一个调用内置 task() global and runs it in the code interpreter. For example: "Run a workflow to review every file in src/ for SQL injection."
当子代理生成时, dcode 会在动态子代理面板中实时显示它们,按调度阶段分组。
dcode 是尝试这个最快的方式,但你也可以在你选择的编码代理中使用动态子代理,通过 ACP (例如,Zed)。
流式传输
Deep Agents 支持来自协调器和每个委托子代理的流式更新。
使用 streamEvents 获取类型化投影——为子代理、消息、工具调用和值提供独立的迭代器——以便你可以独立消费每个流。
流式传输子代理进度
最简单的模式是迭代 stream.subagents 来跟踪每个委托任务的启动、运行和完成。每个子代理句柄暴露 .name, .messages, .tool_calls和 .output.
LangSmith 追踪
当你的 deep agent 运行时,所有由子代理或协调器执行的运行都将在其元数据中包含代理名称,键为 lc_agent_name ——例如, {'lc_agent_name': 'research-agent'}。这让你可以在 LangSmith 中按子代理识别和筛选运行。
在 LangSmith 中按子代理筛选
由于每个子代理的 name 被写入 lc_agent_name 元数据键到其产生的每次运行中,您可以使用 LangSmith 的元数据筛选功能隔离来自特定子代理的所有运行——这有助于调试、监控或比较子代理随时间的行为。
在 LangSmith UI 中筛选
- 在 LangSmith.
- 中打开您的追踪项目,将视图切换到 **运行** 在追踪项目页面上查看各个跨度。
- 点击 **添加筛选** 并选择 **元数据**.
- 将 **键** to
lc_agent_name和 **值** 设置为子代理名称(例如,research-agent).
这样就只会显示该子代理产生的运行。您可以将筛选保存为命名视图以便重复使用。完整的筛选选项参考,请参阅 筛选追踪.
使用 SDK 以编程方式筛选
使用 LangSmith 筛选查询语言中的 has 比较器按元数据键值对匹配运行:
from langsmith import Client
client = Client()
runs = client.list_runs(
project_name="<your-project>",
filter='has(metadata, \'{"lc_agent_name": "research-agent"}\')',
)
for run in runs:
print(run.name, run.start_time, run.status)
要获取 _任意_ 命名子代理(不包括主代理)的运行,请筛选具有 lc_agent_name 键的运行:
runs = client.list_runs(
project_name="<your-project>",
filter="has(metadata, 'lc_agent_name')",
)
完整的筛选查询语言参考,请参阅 追踪查询语法.
结构化输出
子代理支持 结构化输出,因此父代理接收的是可预测的、可解析的 JSON,而不是自由格式的文本。
在子代理配置上传递 responseFormat 。当子代理完成时,其结构化响应会被 JSON 序列化并作为 ToolMessage 内容传递给父代理。schema 支持任何 createAgent:Zod schema、JSON schema 对象、 toolStrategy(...), or providerStrategy(...).
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,
};
const agent = createDeepAgent({
model: "claude-sonnet-4-6",
subagents: [researchSubagent],
});
const result = await agent.invoke({
messages: [{ role: "user", content: "Research recent advances in quantum computing" }],
});
// The parent's ToolMessage contains JSON-serialized structured data:
// '{"summary": "...", "confidence": 0.87, "sources": ["https://..."]}'
没有 response_format,父代理接收子代理的最后一条消息文本原样返回。有了它,父代理始终获得符合 schema 的有效 JSON,这在父代理需要以编程方式处理结果或将其传递给下游工具时非常有用。
有关 schema 类型和策略的完整详细信息(工具调用 vs. 提供商原生),请参阅 结构化输出.
通用子代理
除了任何用户定义的子代理外,每个深度代理都可以访问一个 general-purpose 子代理,随时可用。此子代理:
- - 使用其自己的 应用了 profile 覆盖的默认系统提示词
- - 可以访问所有相同的工具
- - 使用相同的模型(除非被覆盖)
- - 从主代理继承技能(当技能被配置时)
覆盖通用子代理
在您的 name: "general-purpose" 列表中包含一个子代理 subagents 来替换默认项。使用此方法来为通用子代理配置不同的模型、工具或系统提示词:
// Main agent uses Gemini; general-purpose subagent uses GPT
const agent = await createDeepAgent({
model: "google_genai:gemini-3.5-flash",
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.5", // Different model for delegated tasks
},
],
});
当您提供一个具有通用名称的子代理时,默认的通用子代理不会被添加。您的配置会完全替换它。
要完全移除内置的通用子代理而不是替换它,请将活动 harness profile 的通用子代理 enabled 标志设置为 False.
使用场景
通用子代理非常适合需要上下文隔离且不需要专门行为的场景。主代理可以将复杂的多步骤任务委托给此子代理,并获得简洁的结果返回,而不会因中间工具调用而产生膨胀。
Example
主代理不需要进行 10 次网络搜索并用结果填充其上下文,而是委托给通用子代理: task(name="general-purpose", task="Research quantum computing trends")。子代理在内部执行所有搜索,并仅返回摘要。
技能继承
配置 技能 时 create_deep_agent:
- 通用子代理:自动从主代理继承技能
- 自定义子代理:默认不继承技能——使用
skills参数为它们配置自己的技能
// Research subagent with its own skills
const researchSubagent: SubAgent = {
name: "researcher",
description: "Research assistant with specialized skills",
systemPrompt: "You are a researcher.",
tools: [webSearch],
skills: ["/skills/research/", "/skills/web-search/"], // Subagent-specific skills
};
const agent = createDeepAgent({
model: "google_genai:gemini-3.5-flash",
skills: ["/skills/main/"], // Main agent and GP subagent get these
subagents: [researchSubagent], // Gets only /skills/research/ and /skills/web-search/
});
最佳实践
编写清晰的描述
主代理使用描述来决定调用哪个子代理。请具体说明:
✅ **Good:** "Analyzes financial data and generates investment insights with confidence scores"
❌ **Bad:** "Does finance stuff"
保持系统提示详细
包含关于如何使用工具和格式化输出的具体指导:
const researchSubagent = {
name: "research-agent",
description: "Conducts in-depth research using web search and synthesizes findings",
systemPrompt: `You are a thorough researcher. Your job is to:
1. Break down the research question into searchable queries
2. Use internet_search to find relevant information
3. Synthesize findings into a comprehensive but concise summary
4. Cite sources when making claims
Output format:
- Summary (2-3 paragraphs)
- Key findings (bullet points)
- Sources (with URLs)
Keep your response under 500 words to maintain clean context.`,
tools: [internetSearch],
};
最小化工具集
只给子代理它们需要的工具。这可以提高专注度和安全性:
// ✅ Good: Focused tool set
const emailAgent = {
name: "email-sender",
tools: [sendEmail, validateEmail], // Only email-related
};
// ❌ Bad: Too many tools
const emailAgentBad = {
name: "email-sender",
tools: [sendEmail, webSearch, databaseQuery, fileUpload], // Unfocused
};
按任务选择模型
不同的模型擅长不同的任务:
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.5-flash", // Large context for long documents
},
{
name: "financial-analyst",
description: "Analyzes financial data and market trends",
systemPrompt: "You are an expert financial analyst...",
tools: [getStockPrice, analyzeFundamentals],
model: "gpt-5.5", // Better for numerical analysis
},
];
返回简洁的结果
指示子代理返回摘要而不是原始数据:
const dataAnalyst = {
systemPrompt: `Analyze the data and return:
1. Key insights (3-5 bullet points)
2. Overall confidence score
3. Recommended next actions
Do NOT include:
- Raw data
- Intermediate calculations
- Detailed tool outputs
Keep response under 300 words.`,
};
常见模式
多个专业化的子代理
为不同领域创建专业化的子代理:
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.5-flash",
systemPrompt: "You coordinate data analysis and reporting. Use subagents for specialized tasks.",
subagents: subagents,
});
Workflow: 1. 主代理制定高级计划 2. 将数据收集委托给数据收集器 3. 将结果传递给数据分析器 4. 将洞察发送给报告撰写器 5. 编译最终输出
每个子代理使用干净的上下文,仅专注于其任务。
上下文管理
当使用调用父代理时 运行时上下文,该上下文会自动传播到所有子代理。每个子代理运行都接收您在父代理上传递的相同运行时上下文 invoke / ainvoke call.
这意味着在任何子代理内运行的工具都可以访问您提供给父代理的相同上下文值:
const contextSchema = z.object({
userId: z.string(),
sessionId: z.string(),
});
const getUserData = tool(
async (input, runtime: ToolRuntime<unknown, typeof contextSchema>) => {
const userId = runtime.context?.userId;
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.5-flash",
subagents: [researchSubagent],
contextSchema,
});
// Context flows to the researcher subagent and its tools automatically
const result = await agent.invoke(
{ messages: [new HumanMessage("Look up my recent activity")] },
{ context: { userId: "user-123", sessionId: "abc" } },
);
每个子代理的上下文
所有子代理都接收相同的父上下文。要传递特定于某个子代理的配置,请使用 **命名空间键** (用子代理名称作为键的前缀,例如 researcher:max_depth)在扁平 context 映射中, **or** 将这些设置建模为上下文类型上的单独字段:
const contextSchema = z.object({
userId: z.string(),
researcherMaxDepth: z.number().optional(),
factCheckerStrictMode: z.boolean().optional(),
});
const result = await agent.invoke(
{ messages: [new HumanMessage("Research this and verify the claims")] },
{
context: {
userId: "user-123", // shared by all agents
"researcher:maxDepth": 3, // only for researcher
"fact-checker:strictMode": true, // only for fact-checker
},
},
);
const verifyClaim = tool(
async (input, runtime: ToolRuntime<unknown, typeof contextSchema>) => {
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() }),
}
);
识别哪个子代理调用了工具
当同一个工具在父代理和多个子代理之间共享时,您可以使用 lc_agent_name 元数据(与在中使用的相同值 流式传输)来确定哪个代理发起了调用:
const sharedLookup = tool(
async (input, runtime: ToolRuntime) => {
const agentName = runtime.config?.metadata?.lc_agent_name;
if (agentName === "fact-checker") {
return strictLookup(input.query);
}
return generalLookup(input.query);
},
{
name: "shared_lookup",
description: "Look up information from various sources",
schema: z.object({ query: z.string() }),
}
);
您可以组合两种模式——从读取代理特定设置,从读取 runtime.context 并在 lc_agent_name 从 runtime.config 元数据中读取,以分支工具行为。
const flexibleSearch = tool(
async (input, runtime: ToolRuntime<unknown, typeof contextSchema>) => {
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() }),
}
);
故障排除
子代理未被调用
问题:主代理尝试自己完成任务而不是委托。
解决方案:
- **使描述更具体:**
// ✅ Good
{ name: "research-specialist", description: "Conducts in-depth research on specific topics using web search. Use when you need detailed information that requires multiple searches." }
// ❌ Bad
{ name: "helper", description: "helps with stuff" }
- **指示主代理委托:**
const agent = createDeepAgent({
systemPrompt: `...your instructions...
IMPORTANT: For complex tasks, delegate to your subagents using the task() tool.
This keeps your context clean and improves results.`,
subagents: [...]
});
上下文仍然变得臃肿
问题:尽管使用了子代理,上下文仍然填满。
解决方案:
- **指示子代理返回简洁的结果:**
systemPrompt: `...
IMPORTANT: Return only the essential summary.
Do NOT include raw data, intermediate search results, or detailed tool outputs.
Your response should be under 500 words.`
- **对大数据使用文件系统:**
systemPrompt: `When you gather large amounts of data:
1. Save raw data to /data/raw_results.txt
2. Process and analyze the data
3. Return only the analysis summary
This keeps context clean.`
错误选择了子代理
问题:主代理为任务调用了不合适的子代理。
解决方案:在描述中清晰区分子代理:
const subagents = [
{
name: "quick-researcher",
description: "For simple, quick research questions that need 1-2 searches. Use when you need basic facts or definitions.",
},
{
name: "deep-researcher",
description: "For complex, in-depth research requiring multiple searches, synthesis, and analysis. Use for comprehensive reports.",
}
];