深度代理可以创建子代理来委派工作。您可以在 subagents 参数中指定自定义子代理。子代理对于上下文隔离(保持主代理的上下文清洁)以及提供专门的指令非常有用。 本页涵盖同步子代理,即主管代理会阻塞直到子代理完成。对于长时间运行的任务、并行工作流,或需要在执行过程中进行引导和取消的情况,请参阅异步子代理。

为什么要使用子代理?

子代理解决了上下文膨胀问题。当代理使用具有大量输出的工具(网页搜索、文件读取、数据库查询)时,上下文窗口会迅速被中间结果填满。子代理隔离了这些详细工作——主代理只接收最终结果,而不是产生该结果的几十次工具调用。 何时使用子代理:
  • ✅ 可能会混淆主代理上下文的多步骤任务
  • ✅ 需要自定义指令或工具的专业领域
  • ✅ 需要不同模型能力的任务
  • ✅ 当您希望主代理专注于高级协调时
何时不使用子代理:
  • ❌ 简单的单步骤任务
  • ❌ 当您需要保留中间上下文时
  • ❌ 当开销大于收益时

配置

subagents 应该是一个字典列表或 CompiledSubAgent 对象。有两种类型:

默认子代理

深度代理会自动添加一个同步的 general-purpose 子代理,除非您已经提供了一个同名的同步子代理。 general-purpose 子代理默认拥有文件系统工具,并且可以通过额外的工具/中间件进行自定义。
  • 要替换它,请传递您自己的名为 general-purpose 的子代理。
  • 要重命名或重新提示自动添加的版本,请在活动驱动配置上设置 general_purpose_subagent=GeneralPurposeSubagentProfile(...)。
  • 要禁用它,请参阅下面的在没有子代理的情况下运行。

在没有子代理的情况下运行

要运行一个没有 task 工具的代理,需要做两件事:
  1. 在活动驱动配置上设置 general_purpose_subagent=GeneralPurposeSubagentProfile(enabled=False)。
  2. 通过 create_deep_agent 的 subagents= 参数不传递任何同步子代理。
深度代理仅在存在至少一个同步子代理时才附加 SubAgentMiddleware(以及 task 工具)。既没有默认子代理也没有调用者提供的子代理时,代理将无委派运行。 异步子代理不受影响——它们通过自己的中间件和工具流动,如异步子代理中所述。
这里不要使用 excluded_middleware——SubAgentMiddleware 是必需的脚手架,将其列入会引发 ValueError。general_purpose_subagent.enabled = False 是受支持的途径。

自定义子代理

您可以使用 subagents 参数定义具有特定工具的专业化子代理。例如,用作代码审查员、网络研究员或测试运行器。 对于大多数用例,请使用 SubAgent 字典将子代理定义为字典。对于复杂的工作流,请使用 CompiledSubAgent:

SubAgent(基于字典)

将子代理定义为匹配 SubAgent 规范的字典,包含以下字段:

CompiledSubAgent

对于复杂的工作流,可以使用预构建的 LangGraph 图作为 CompiledSubAgent:

使用 SubAgent

使用 CompiledSubAgent

对于更复杂的用例,您可以使用 CompiledSubAgent 提供您的自定义子代理。 您可以使用 LangChain 的 create_agent 或通过图 API 制作自定义 LangGraph 图来创建自定义子代理。 如果您正在创建自定义 LangGraph 图,请确保该图具有一个名为 "messages" 的状态键:

动态子代理

默认情况下,主代理通过 task 工具调用委派给子代理(它可以在单个轮次中发出多个调用以并行运行它们)。附加了解释器后,代理可以改为从代码中派发子代理——使用循环、分支和并行批处理将工作分散到多个项目并以编程方式综合结果。这称为动态子代理。 当工作跨越许多独立单元(审查目录中的每个文件、分流一批工单)、需要多个视角,或受益于递归分析时,请考虑使用动态子代理。
动态子代理使用解释器运行时,目前处于 测试版。API 和生命周期行为可能在版本之间发生变化。

启用动态子代理

只要代理拥有子代理和解释器中间件,动态子代理就可用。安装 QuickJS 解释器包,然后将 CodeInterpreterMiddleware 添加到您的代理。
只要代理拥有子代理和解释器中间件,动态子代理派发默认开启。传递 CodeInterpreterMiddleware(subagents=False) 以要求通过常规 task 工具路径派发。解释器需要 langchain-quickjs>=0.2.0 和 Python >=3.11。

触发动态编排

动态派发是隐式的:代理根据任务的形状决定是否从代码中分散工作,而不是通过每次调用的标志。
“workflow”一词是一个有用的触发词。 内置的解释器系统提示将“workflow”视为一个信号,表示通过解释器组织工作——从代码中使用 task() 派发子代理。将请求表述为“workflow”是一个您可以在选择启用动态编排时拉动的有意杠杆:当您希望代理从代码中分散工作时,请包含它。对于单个直接委派,请直接明了地表述请求。
例如,将请求表述为“workflow”会启用从代码中的扇出:
python 使用 stream_events 获取类型化投影——用于子代理、消息、工具调用和值的独立迭代器——以便您可以独立消费每个。 :::

流式传输子代理进度

最简单的模式是迭代 stream.subagents 以跟踪每个委派的任务开始、运行和完成。每个子代理句柄都公开 .name、.messages、.tool_calls 和 .output。

LangSmith 追踪

当您的深度代理运行时,由子代理或协调器执行的所有运行都将在其元数据中的 lc_agent_name 键下拥有代理名称——例如 {'lc_agent_name': 'research-agent'}。这使您能够在 LangSmith 中按子代理识别和过滤运行。 显示元数据的 LangSmith 示例追踪
在 LangSmith 中打开运行以比较协调器追踪与每个子代理运行。请遵循可观测性快速入门进行设置。我们建议您还设置 LangSmith Engine,它会监控您的追踪、检测问题并提出修复建议。

在 LangSmith 中按子代理过滤

因为每个子代理的 name 都写入其产生的每个运行的 lc_agent_name 元数据键中,您可以使用 LangSmith 的元数据过滤来隔离来自特定子代理的所有运行——这对于调试、监控或比较子代理随时间的行为非常有用。

在 LangSmith UI 中过滤

  1. 在 LangSmith 中打开您的追踪项目。
  2. 在追踪项目页面将视图切换到 Runs 以查看单个跨度。
  3. 单击 Add filter 并选择 Metadata。
  4. 将 Key 设置为 lc_agent_name,将 Value 设置为子代理名称,例如 coordinator。
LangSmith Runs 视图,lc_agent_name 元数据过滤器设置为 coordinator 这将仅显示该子代理产生的运行。您可以将过滤器保存为命名视图以便重复使用。有关过滤选项的完整参考,请参阅过滤追踪。

使用 SDK 以编程方式过滤

在 LangSmith 过滤查询语言中使用 has 比较器通过元数据键值对匹配运行:
要获取来自任何命名子代理(不包括主代理)的运行,请过滤具有 lc_agent_name 键的运行:
有关完整的过滤查询语言参考,请参阅追踪查询语法。

结构化输出

子代理支持结构化输出,因此父代理接收到的是可预测、可解析的 JSON,而不是自由格式文本。
子代理的结构化输出需要 deepagents>=0.5.3。
在子代理配置上传递 response_format。当子代理完成时,其结构化响应将被 JSON 序列化并作为 ToolMessage 内容返回给父代理。该模式接受 create_agent 支持的任何内容:Pydantic 模型、ToolStrategy(...)、ProviderStrategy(...) 或原始模式类型。
如果没有 response_format,父代理会原样接收子代理最后的消息文本。使用它时,父代理总是获得符合模式的有效 JSON,这在父代理需要以编程方式处理结果或将其传递给下游工具时非常有用。 有关模式类型和策略(工具调用与原生提供程序)的完整详细信息,请参阅结构化输出。

通用子代理

除了任何用户定义的子代理之外,每个深度代理在任何时候都可以访问一个 general-purpose 子代理。此子代理:

覆盖通用子代理

在您的 subagents 列表中包含一个 name="general-purpose" 的子代理以替换默认的。使用此方法可以为通用子代理配置不同的模型、工具或系统提示:
当您提供一个具有通用名称的子代理时,默认的通用子代理不会被添加。您的规范完全替换了它。 要完全移除内置的通用子代理而不是替换它,请将活动驱动配置文件的通用子代理 enabled 标志设置为 False。

何时使用它

通用子代理非常适合在没有专门行为的情况下进行上下文隔离。主代理可以将一个复杂的多步骤任务委派给此子代理,并获得简洁的结果,而不会因为中间工具调用而膨胀。

示例

主代理不是自己进行 10 次网络搜索并用结果填满其上下文,而是委派给通用子代理:task(name="general-purpose", task="研究量子计算趋势")。子代理在内部执行所有搜索,并仅返回摘要。

技能继承

使用 create_deep_agent 配置技能时:
  • 通用子代理:自动从主代理继承技能
  • 自定义子代理:默认不继承技能——使用 skills 参数赋予它们自己的技能
只有配置了技能的子代理才会获得 SkillsMiddleware 实例——没有 skills 参数的自定义子代理则不会。存在时,技能状态在两个方向上都是完全隔离的:父代理的技能对子代理不可见,子代理的技能也不会传播回父代理。

最佳实践

编写清晰的描述

主代理使用描述来决定调用哪个子代理。要具体: ✅ 好: "分析金融数据并生成带有置信度分数的投资洞察" ❌ 差: "处理金融事务"

保持系统提示详细

包含关于如何使用工具和格式化输出的具体指导:

最小化工具集

只给子代理他们需要的工具。这可以提高专注度和安全性:

✅ 正面示例:聚焦的工具集

按任务选择模型

不同的模型擅长不同的任务:

返回简洁的结果

指示子代理返回摘要,而不是原始数据:

常见模式

多个专业化子代理

为不同的领域创建专业化的子代理:
工作流:
  1. 主代理创建高级计划
  2. 将数据收集委派给 data-collector
  3. 将结果传递给 data-analyzer
  4. 将洞察发送给 report-writer
  5. 编译最终输出
每个子代理都在干净的上下文中工作,只专注于其任务。

上下文管理

当您使用运行时上下文调用父代理时,该上下文会自动传播到所有子代理。每个子代理运行都会接收到您在父代理 invoke / ainvoke 调用中传递的相同运行时上下文。 这意味着在任何子代理内部运行的工具都可以访问您提供给父代理的相同上下文值:

每个子代理的上下文

所有子代理都接收相同的父上下文。要传递特定于某个子代理的配置,请在扁平的 context 映射中使用命名空间键(用子代理名称为键添加前缀,例如 researcher:max_depth),或者将这些设置建模为上下文类型上的单独字段:

识别哪个子代理调用了工具

当同一个工具在父代理和多个子代理之间共享时,您可以使用 lc_agent_name 元数据(与流式传输中使用的值相同)来确定是哪个代理发起了调用:
您可以结合两种模式——从 runtime.context 读取代理特定设置,并在分支工具行为时从 runtime.config 元数据读取 lc_agent_name。

故障排除

子代理未被调用

问题:主代理试图自己完成工作而不是委派。 解决方案:
  1. 使描述更具体:
  2. 指示主代理委派:

上下文仍然膨胀

问题:尽管使用了子代理,上下文仍然填满。 解决方案:
  1. 指示子代理返回简洁结果:
  2. 对大数据使用文件系统:

选择了错误的子代理

问题:主代理为任务调用了不合适的子代理。 解决方案:在描述中清晰地区分子代理: