异步子代理
启动后台子代理并发运行,同时 Supervisor 继续与用户交互
从"排队等结果"到"边聊边干活"的异步协作deepagents 1.9.0 中提供的预览功能。预览功能正在积极开发中,API 可能会发生变化。请关注官方文档获取最新更新。
为什么需要它
你的 Supervisor 是不是经常"卡住"等子代理干完活才能继续?
同步子代理的困境
- 启动子代理后 Supervisor 完全阻塞,无法响应用户
- 长时间任务让用户干等着,体验极差
- 多个子代理只能排队执行,无法并发
- 任务启动后无法取消,也无法中途修正方向
- 调用之间无状态,每次都从零开始
异步子代理的解法
- 启动后立即返回任务 ID,Supervisor 继续对话
- 用户可以随时查看进度,不用被动等待
- 多个子代理真正并发运行,互不干扰
- 支持
update中途修正、cancel取消任务 - 每个子代理维护独立状态,跨交互持久化
卸甲:上面用打电话和发微信来打比方,是为了帮你理解两种模式的差异。实际上同步子代理是主 Agent 调用
task 后,等待子 Agent 完全执行完毕才继续往下走,期间 LLM 不产生新回复;异步子代理则是通过 Agent Protocol 在服务端开启一个新线程,主 Agent 立即拿到任务 ID 继续处理用户消息——不涉及"通话"或"聊天"的概念。
架构概览
用户 → Supervisor → 异步子代理的协作架构
何时使用它
同步 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 自动获得管理工具
});
| 字段 | 类型 | 说明 |
|---|---|---|
name | string | 必填。唯一标识符,启动任务时使用 |
description | string | 必填。描述功能,Supervisor 据此委派任务 |
graphId | string | 必填。Agent Protocol 服务端上的图 ID |
url | string | 可选。省略用进程内,设置用 HTTP |
headers | Record | 可选。远程服务端的额外请求头 |
协同部署配置
在同一个 langgraph.json 中注册所有图实现进程内传输
对于基于 LangGraph 的部署,推荐将所有图注册在同一个 langgraph.json 中。这样子代理通过进程内传输协同部署,零网络延迟。
{
"graphs": {
"supervisor": "./src/supervisor.ts:graph", // 编排者,负责管理子代理
"researcher": "./src/researcher.ts:graph", // 研究代理
"coder": "./src/coder.ts:graph" // 编码代理
}
}
进程内传输的优势
- 零网络延迟——进程内函数调用
- 无需额外身份验证配置
- 子代理仍独立运行,各有自己的线程和状态
何时用 HTTP 传输
- 子代理需要独立扩展
- 不同团队各自维护不同子代理
- 需要不同的资源配置
工具一览
中间件为 Supervisor 提供的五个异步管理工具
AsyncSubAgentMiddleware 自动为 Supervisor 注入五个工具。LLM 像调用其他工具一样使用它们,中间件在后台处理线程创建、运行管理和状态持久化。
start_async_task
启动新的后台任务
返回任务 ID(立即)
check_async_task
获取任务的当前状态和结果
状态 + 结果
update_async_task
向运行中的任务发送新指令
确认 + 更新后状态
cancel_async_task
停止运行中的任务
确认取消
list_async_tasks
列出所有已跟踪任务及状态
所有任务摘要
任务生命周期
从启动到获取结果,异步子代理的完整交互流程
1. 启动
在服务端创建新线程,以任务描述开始运行,返回线程 ID 作为任务 ID
2. 检查
获取当前运行状态;完成则提取最终输出,否则报告进度
3. 更新
中断当前运行,以新指令 + 完整历史重启,任务 ID 不变
4. 取消
调用 runs.cancel(),标记任务为 cancelled
5. 列出
遍历所有任务,非终止状态并行获取实时状态
交互时序
状态管理机制
为什么任务元数据不能存在消息历史里?
任务元数据存储在 Supervisor 图上的专用状态通道 asyncTasks 中,与消息历史分开。这不是随意设计——而是为了对抗一个真实问题。
如果存在消息历史中
- Deep Agent 在上下文窗口填满时会压缩消息历史
- 任务 ID 存在工具消息中,压缩时会丢失
- 压缩后 Supervisor "忘记"自己启动过哪些任务
list_async_tasks无法回忆起之前的任务
存在专用通道中
asyncTasks通道不受压缩影响- 任务 ID、状态、时间戳始终可访问
- 即使经过多轮摘要化也能回忆起任务
list_async_tasks随时返回完整任务列表
createdAt、checkedAt、updatedAt)。这些信息与消息历史完全隔离。
选择传输方式
进程内协同部署 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 传输
},
];
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 增加池大小。参见"调整工作线程池大小"最佳实践。
参考实现
完整的可运行示例,帮你快速上手
async-deep-agents GitHub 仓库
官方参考实现仓库,包含完整的可部署示例代码:
- Python 示例——使用 LangGraph Python SDK 构建的 Supervisor + 研究代理 + 编码代理
- TypeScript 示例——使用
deepagentsSDK 构建的完整异步子代理编排 - 可直接部署到 LangSmith Deployments
- 演示了 Supervisor 管理多个后台任务的完整交互流程
本章回顾
掌握异步子代理的关键知识点
你学到了什么
- 异步子代理让 Supervisor 启动后立即返回,继续与用户交互
- 五个核心工具:start / check / update / cancel / list
- 任务元数据存在专用通道
asyncTasks,不受消息压缩影响 - 进程内传输零延迟,HTTP 传输支持独立扩展
- 三种部署拓扑:单一 / 分离 / 混合
- 防止轮询、ID 截断等常见问题的系统提示策略
关键决策指南
- 短任务、需等结果 → 用同步子代理
- 长任务、可并发 → 用异步子代理
- 开始部署 → 先用 进程内单一部署
- 遇到瓶颈 → 拆分为 HTTP 分离部署
- 本地开发 → 记得调整
--n-jobs-per-worker - 排查问题 → 先检查系统提示和工作线程池