第 11 章

异步子代理

启动后台子代理并发运行,同时 Supervisor 继续与用户交互

从"排队等结果"到"边聊边干活"的异步协作
Preview 功能:异步子代理是 deepagents 1.9.0 中提供的预览功能。预览功能正在积极开发中,API 可能会发生变化。请关注官方文档获取最新更新。

为什么需要它

你的 Supervisor 是不是经常"卡住"等子代理干完活才能继续?

同步子代理的困境

  • 启动子代理后 Supervisor 完全阻塞,无法响应用户
  • 长时间任务让用户干等着,体验极差
  • 多个子代理只能排队执行,无法并发
  • 任务启动后无法取消,也无法中途修正方向
  • 调用之间无状态,每次都从零开始

异步子代理的解法

  • 启动后立即返回任务 ID,Supervisor 继续对话
  • 用户可以随时查看进度,不用被动等待
  • 多个子代理真正并发运行,互不干扰
  • 支持 update 中途修正、cancel 取消任务
  • 每个子代理维护独立状态,跨交互持久化
问题核心:同步子代理像打电话——对方不挂断你就做不了别的;异步子代理像发微信——发完消息你可以继续干其他事,有空再看回复。

卸甲:上面用打电话和发微信来打比方,是为了帮你理解两种模式的差异。实际上同步子代理是主 Agent 调用 task 后,等待子 Agent 完全执行完毕才继续往下走,期间 LLM 不产生新回复;异步子代理则是通过 Agent Protocol 在服务端开启一个新线程,主 Agent 立即拿到任务 ID 继续处理用户消息——不涉及"通话"或"聊天"的概念。

架构概览

用户 → Supervisor → 异步子代理的协作架构

用户(User)
↓ 对话
Supervisor Agent(编排者)
↓ 启动 / 检查 / 更新 / 取消
研究代理(Researcher)
编码代理(Coder)
Agent Protocol 兼容说明:异步子代理通过 Agent Protocol 与服务端通信。你可以使用 LangSmith Deployments,也可以自行托管任何兼容 Agent Protocol 的服务端。每个子代理独立于 Supervisor 运行,Supervisor 通过 SDK 控制它们的启动、检查、更新和取消。
通信方式:Supervisor 并不直接"运行"子代理——它通过 Agent Protocol 向服务端发指令,服务端负责创建线程、管理运行和返回结果。这种解耦让子代理可以部署在任何地方。

何时使用它

同步 vs 异步子代理的适用场景对比

维度同步子代理异步子代理
执行模型 Supervisor 阻塞直到完成 立即返回任务 ID,继续执行
并发性 并行但阻塞 并行且非阻塞
中途更新 不可能 通过 update_async_task 发送新指令
取消 不可能 通过 cancel_async_task 取消
状态管理 无状态,调用之间不保持 有状态,跨交互维护持久状态
适用场景 需要等待结果才能继续 长时间运行、需交互式管理
选型口诀:短任务、等结果的用同步;长任务、可并发的用异步。当你需要"启动后就忘掉,想起来再查"时,就是异步子代理的舞台。

配置子代理

将异步子代理定义为 AsyncSubAgent 规范列表

每个异步子代理指向一个 Agent Protocol 服务端。Supervisor 根据描述决定把任务派给谁。

import { createDeepAgent, AsyncSubAgent } from "deepagents";

const asyncSubagents: AsyncSubAgent[] = [
  {
    name: "researcher",                     // Supervisor 用这个名字启动任务
    description: "用于信息收集和综合的研究代理",  // 描述决定 Supervisor 何时委派
    graphId: "researcher",                   // Agent Protocol 服务端上的图 ID
    // 省略 url → 使用进程内传输(同一部署内,零网络延迟)
  },
  {
    name: "coder",
    description: "用于代码生成和审查的编码代理",
    graphId: "coder",
    // url: "https://coder-deployment.langsmith.dev"
    // 设置 url → 使用 HTTP 传输(远程独立部署)
  },
];

const agent = createDeepAgent({
  model: "google-genai:gemini-3.1-pro-preview",
  subagents: [...asyncSubagents],  // 注册异步子代理,Supervisor 自动获得管理工具
});
字段类型说明
namestring必填。唯一标识符,启动任务时使用
descriptionstring必填。描述功能,Supervisor 据此委派任务
graphIdstring必填。Agent Protocol 服务端上的图 ID
urlstring可选。省略用进程内,设置用 HTTP
headersRecord可选。远程服务端的额外请求头

协同部署配置

在同一个 langgraph.json 中注册所有图实现进程内传输

对于基于 LangGraph 的部署,推荐将所有图注册在同一个 langgraph.json 中。这样子代理通过进程内传输协同部署,零网络延迟。

{
  "graphs": {
    "supervisor": "./src/supervisor.ts:graph",   // 编排者,负责管理子代理
    "researcher": "./src/researcher.ts:graph",   // 研究代理
    "coder": "./src/coder.ts:graph"              // 编码代理
  }
}

进程内传输的优势

  • 零网络延迟——进程内函数调用
  • 无需额外身份验证配置
  • 子代理仍独立运行,各有自己的线程和状态

何时用 HTTP 传输

  • 子代理需要独立扩展
  • 不同团队各自维护不同子代理
  • 需要不同的资源配置
推荐起点:先用进程内协同部署,一个服务端搞定所有代理。等真正遇到扩展瓶颈时再拆分为 HTTP 远程部署。

工具一览

中间件为 Supervisor 提供的五个异步管理工具

AsyncSubAgentMiddleware 自动为 Supervisor 注入五个工具。LLM 像调用其他工具一样使用它们,中间件在后台处理线程创建、运行管理和状态持久化。

start_async_task

启动新的后台任务

返回任务 ID(立即)

check_async_task

获取任务的当前状态和结果

状态 + 结果

update_async_task

向运行中的任务发送新指令

确认 + 更新后状态

cancel_async_task

停止运行中的任务

确认取消

list_async_tasks

列出所有已跟踪任务及状态

所有任务摘要

注意:Supervisor 的 LLM 自动决定何时调用这些工具。你不需要手动管理线程 ID 或运行状态——中间件在后台完成所有底层操作。

任务生命周期

从启动到获取结果,异步子代理的完整交互流程

1. 启动

在服务端创建新线程,以任务描述开始运行,返回线程 ID 作为任务 ID

2. 检查

获取当前运行状态;完成则提取最终输出,否则报告进度

3. 更新

中断当前运行,以新指令 + 完整历史重启,任务 ID 不变

4. 取消

调用 runs.cancel(),标记任务为 cancelled

5. 列出

遍历所有任务,非终止状态并行获取实时状态

交互时序

用户
Supervisor
Agent Protocol 服务端

用户 ── "研究主题 X" ──▶ Supervisor
Supervisor ── launch(researcher, "主题 X") ──▶ 服务端
服务端 ── task_id: abc123 ──▶ Supervisor
    ℹ 研究代理正在后台工作...
Supervisor ── "已启动任务 abc123" ──▶ 用户
    ℹ 用户继续与 Supervisor 对话其他话题
用户 ── "研究进展如何?" ──▶ Supervisor
Supervisor ── check(abc123) ──▶ 服务端
服务端 ── { status: success, result: "发现..." } ──▶ Supervisor
Supervisor ── "以下是研究结果" ──▶ 用户
关键设计:启动后 Supervisor 不会轮询等待完成。它把任务 ID 交给用户,继续处理其他请求。用户主动询问时才去检查状态——这就是"异步"的核心。

状态管理机制

为什么任务元数据不能存在消息历史里?

任务元数据存储在 Supervisor 图上的专用状态通道 asyncTasks 中,与消息历史分开。这不是随意设计——而是为了对抗一个真实问题。

如果存在消息历史中

  • Deep Agent 在上下文窗口填满时会压缩消息历史
  • 任务 ID 存在工具消息中,压缩时会丢失
  • 压缩后 Supervisor "忘记"自己启动过哪些任务
  • list_async_tasks 无法回忆起之前的任务

存在专用通道中

  • asyncTasks 通道不受压缩影响
  • 任务 ID、状态、时间戳始终可访问
  • 即使经过多轮摘要化也能回忆起任务
  • list_async_tasks 随时返回完整任务列表
每个任务记录包含:任务 ID、代理名称、线程 ID、运行 ID、状态、时间戳(createdAt、checkedAt、updatedAt)。这些信息与消息历史完全隔离。
图:asyncTasks 专用通道与消息历史隔离

选择传输方式

进程内协同部署 vs HTTP 远程连接,各有适用场景

进程内传输(推荐默认)

省略 url 字段即启用。SDK 通过进程内函数调用路由,不走 HTTP。

  • 零网络延迟,性能最优
  • 无需额外身份验证配置
  • 所有图注册在同一 langgraph.json
  • 子代理仍有独立的线程和状态

HTTP 传输(远程部署)

设置 url 字段即启用。SDK 通过网络连接远程 Agent Protocol 服务端。

  • 子代理可独立扩展和配置
  • 不同团队可各自维护
  • LangGraph 部署自动用 API Key 认证
  • 自托管服务端可用 headers 自定义认证
// HTTP 传输示例:子代理部署在远程服务器上
const asyncSubagents: AsyncSubAgent[] = [
  {
    name: "researcher",
    description: "研究代理",
    graphId: "researcher",
    url: "https://my-research-deployment.langsmith.dev",  // 设置 url 即启用 HTTP 传输
  },
];
安全提示:HTTP 传输时,LangGraph SDK 自动使用环境变量中的 LANGSMITH_API_KEY 或 LANGGRAPH_API_KEY 进行认证。自托管服务端需通过 headers 字段配置。

部署拓扑选择

单一部署、分离部署、混合部署——按需选择

单一部署

所有代理在同一服务端,使用进程内传输。一个 langgraph.json 注册所有图。

  • 管理简单,只维护一个服务端
  • 代理间零网络延迟
  • 推荐作为起点

分离部署

Supervisor 在一个服务端,子代理通过 HTTP 在另一个服务端。

  • 子代理可独立扩展
  • 可分配不同的计算资源
  • 适合不同团队维护的场景

混合部署

部分子代理进程内协同部署,其他 HTTP 远程部署——灵活组合。

  • 核心代理零延迟,外部分离扩展
  • 最灵活但配置稍复杂
  • 按实际需求渐进式拆分
// 混合部署示例:部分协同部署,部分远程部署
const asyncSubagents: AsyncSubAgent[] = [
  {
    name: "researcher",
    description: "研究代理",
    graphId: "researcher",
    // 省略 url → 进程内协同部署(零延迟,推荐默认方式)
  },
  {
    name: "coder",
    description: "编码代理",
    graphId: "coder",
    url: "https://coder-deployment.langsmith.dev",
    // 设置 url → HTTP 远程部署(独立扩展,适合不同团队维护)
  },
];
图:三种部署拓扑对比

最佳实践

三个关键实践帮你用好异步子代理

调整工作线程池

本地开发时每个活跃运行占用一个线程槽。3 个并发子代理 + 1 个 Supervisor = 4 个槽位。线程不足会导致任务排队。

langgraph dev --n-jobs-per-worker 10

编写清晰的描述

Supervisor 根据描述决定委派。描述要具体且面向行动,让 Supervisor 准确判断何时该用哪个子代理。

不好的示例

{
  name: "helper",
  description: "帮忙处理各种事情",  // 太模糊
  graphId: "helper",
}

太模糊——Supervisor 不知道该把什么任务委派给它

好的示例

{
  name: "researcher",
  description:
    "使用网络搜索进行深入研究。" +
    "适用于需要多次搜索和综合分析的问题。",  // 具体且面向行动
  graphId: "researcher",
}

具体且面向行动——明确说明能力和适用场景

利用线程 ID 追踪

每个异步运行都是标准的 LangGraph 运行,在 LangSmith 中完全可见。用线程 ID(即任务 ID)关联 Supervisor 编排追踪与子代理执行追踪。

常见问题排查

四个高频问题及其解决方案

Supervisor 启动后立即轮询

表现:Supervisor 在启动任务后立即循环调用 check,将异步执行变成了阻塞式。

内置防护:中间件会注入系统提示规则来防止这种行为。

手动加强:如果轮询仍然持续,可以在系统提示中强化此行为:

const agent = createDeepAgent({
  systemPrompt: `...你的指令...
    启动异步子代理后,始终将控制权返回给用户。
    绝对不要在启动后立即调用 check_async_task。`,  // 防止 Supervisor 轮询等待
  subagents: [...asyncSubagents],
});

Supervisor 报告过时的状态

表现:Supervisor 引用对话历史中较早的任务状态,而不是发起新的 check 调用。

内置防护:中间件提示会指示模型"对话历史中的任务状态始终是过时的"。

手动加强:如果此问题仍然出现,添加明确的指令,要求在报告状态之前始终调用 check 或 list。

任务 ID 查找失败

表现:Supervisor 截断或重新格式化任务 ID,导致 check 或 cancel 失败。

内置防护:中间件提示会指示模型始终使用完整的任务 ID。

手动加强:如果截断问题持续存在,这通常是模型特定的问题——尝试使用不同的模型,或在系统提示中添加"始终显示完整的 task_id,绝不截断或缩写"。

子代理启动排队而非运行

表现:启动子代理时挂起或需要很长时间才能开始。

原因:工作线程池可能已耗尽。每个活跃运行占用一个线程槽位。

解决方案:使用 --n-jobs-per-worker 增加池大小。参见"调整工作线程池大小"最佳实践。

重要提示:中间件已内置了防止轮询和 ID 截断的系统提示规则。大多数情况下中间件已经足够,以上手动方案仅在特定模型或极端场景下才需要。如果某个问题反复出现且无法通过系统提示解决,考虑更换模型——不同模型对工具调用的遵循程度不同。

参考实现

完整的可运行示例,帮你快速上手

async-deep-agents GitHub 仓库

官方参考实现仓库,包含完整的可部署示例代码:

  • Python 示例——使用 LangGraph Python SDK 构建的 Supervisor + 研究代理 + 编码代理
  • TypeScript 示例——使用 deepagents SDK 构建的完整异步子代理编排
  • 可直接部署到 LangSmith Deployments
  • 演示了 Supervisor 管理多个后台任务的完整交互流程
快速开始:克隆仓库后按照 README 说明操作即可运行。这是学习异步子代理最直接的方式——看一个真正能跑的系统怎么组织 Supervisor、定义子代理描述、处理检查和更新操作。

本章回顾

掌握异步子代理的关键知识点

你学到了什么

  • 异步子代理让 Supervisor 启动后立即返回,继续与用户交互
  • 五个核心工具:start / check / update / cancel / list
  • 任务元数据存在专用通道 asyncTasks,不受消息压缩影响
  • 进程内传输零延迟,HTTP 传输支持独立扩展
  • 三种部署拓扑:单一 / 分离 / 混合
  • 防止轮询、ID 截断等常见问题的系统提示策略

关键决策指南

  • 短任务、需等结果 → 用同步子代理
  • 长任务、可并发 → 用异步子代理
  • 开始部署 → 先用 进程内单一部署
  • 遇到瓶颈 → 拆分为 HTTP 分离部署
  • 本地开发 → 记得调整 --n-jobs-per-worker
  • 排查问题 → 先检查系统提示和工作线程池
核心思想:异步子代理把 AI 协作从"同步阻塞"升级为"异步非阻塞"。Supervisor 不再是串行的任务调度器,而是一个能同时管理多个后台任务、持续与用户对话的真正的编排者。