DeerFlow 子代理编排:task 工具与并行任务分解
用户要调研 Cursor、Windsurf、Cline 三款工具并各写约五百字分析时,单个 Lead Agent 只能串行:搜完 A 再写,再搜 B。总耗时接近单任务的三倍,搜索过程里的噪声还会堆进主上下文,后面的判断越来越糊。DeerFlow 用 Sub-Agents 把这件事拆开:主智能体当项目经理,多个子代理各自开工,最后只交回结构化结果。
为什么要拆,以及怎么派活
Lead Agent 负责拆解与汇总,自己不必亲自搜完全部材料。每个 Sub-Agent 带独立上下文,看不见别人在干什么,也不会把检索垃圾回灌主对话。派活入口是内置 task 工具(backend/packages/harness/deerflow/tools/builtins/task_tool.py)。一次调用里常见三个参数:description(三到五个字的短标签,例如「查 Cursor」)、prompt(给子代理的完整指令)、subagent_type(例如 general-purpose 或 bash)。模型先在输出里声明 tool call,框架再真正启动子代理。
独立上下文的价值很直接:子代理检索时吐出二十条结果,那些半相关链接不会污染主对话。主智能体看到的是成品段落,半成品搜索页留在子上下文里。对用户来说,原本可能要等三十秒的串行流程,并行后往往能压到接近单路耗时,报告也更干净。
SubagentConfig 与 SubagentExecutor
SubagentConfig(subagents/config.py)相当于岗位说明书,关键字段包括:name、description(什么时候该派它)、system_prompt、tools / disallowed_tools、skills、model(默认 inherit,继承父代理模型)、max_turns(默认 50)、timeout_seconds(默认 900,约十五分钟)。disallowed_tools 默认带上 task,子代理不能再派子代理。若不禁用,会出现 Lead → Sub1 → Sub2 → … 的无限套娃:token 指数涨、时间拖长、调试分不清出在哪一层。DeerFlow 的选择是:只有 Lead Agent 能派活。
SubagentExecutor 包住异步执行、超时、取消和 token 统计。构造时按配置过滤工具。真正跑任务时提交到隔离事件循环:主智能体已占用当前 asyncio 循环,Python 一线程通常只能有一个事件循环,子代理必须另开。创建子代理时中间件链走精简版(build_subagent_runtime_middlewares),没有标题生成、记忆更新、子代理限流这类主智能体专属职责。模型解析遵循配置:非 inherit 用指定模型,否则继承父代理。create_agent 时 checkpointer=False,子代理是一次性会话,跑完不落 checkpoint。
技能加载规则也写在配置里:skills is None 表示加载全部已启用技能;空列表表示一个都不要;给出名单则只加载名单内的。技能内容作为对话项注入(借鉴 Codex 做法),初始状态通常只留一条 SystemMessage,任务本身是 HumanMessage,避免部分 LLM API 不支持多条系统消息。
1 |
|
限流、状态契约与一轮执行
SubagentLimitMiddleware 在 after_model 里截断超额的 task 调用。默认并发上限是 MAX_CONCURRENT_SUBAGENTS = 3,配置会被 _clamp_subagent_limit 夹到 [2, 4]。模型一次吐出五个 task,中间件只保留前三个索引,后两个丢掉,下一轮模型再决定是否还要派。经验上三路能提速,又不至于把 LLM 并发配额打爆。
前后端状态走 status_contract:subagent_status / subagent_error 放进 ToolMessage.additional_kwargs,取值包括 completed、failed、cancelled、timed_out、polling_timed_out。老做法靠结果字符串前缀(例如是否以 “Task Succeeded” 开头)判断成败,后端改文案前端就断;契约字段把这件事做稳,类似快递单上的状态码,不必解析自然语言描述。
一轮执行大致如下。主智能体发出 task → task_tool 用 get_subagent_config 找类型,未知则返回错误 → get_available_tools(..., subagent_enabled=False) 拿工具并排除嵌套 task → 建 SubagentExecutor → execute_async 后台跑。轮询大约每五秒读一次后台结果:完成则 writer 推 task_completed 并返回成功文案;失败返回错误。执行中若 AI 消息条数增加,会推 task_running,前端能跟进度。
生命周期、取消与 token
状态机大致是 PENDING → RUNNING,再进入 COMPLETED / FAILED / CANCELLED / TIMED_OUT。后台任务存在全局 _background_tasks 字典,读写带锁,因为主线程轮询与子线程更新会并发碰到同一表。取消是协作式的:主侧设 cancel_event,子代理在 astream 迭代里自检退出。Python 线程不好强杀,只能让它主动收工。终态用 try_set_terminal:已是终态则拒绝覆盖,避免「超时」与「执行完成」竞态互相踩;先到终态的写入获胜。
token 方面,每个子代理挂 SubagentTokenCollector(LangChain BaseCallbackHandler),在 on_llm_end 记用量,caller 形如 subagent:{name},并打到 RunnableConfig 的 callbacks 与 tags。结束后 task_tool 经 RunJournal.record_external_llm_usage_records 合并进父代理统计,并用 usage_reported 防重复计数。用户最终能看到主智能体与子代理各自花了多少。
内置类型与自定义
general-purpose:除 task 外工具较全,适合多步调研与写作。bash:偏命令行,默认关闭,需显式启用,在沙箱里执行。config.yaml 的 subagents.custom_agents 可自定义角色,例如指定 system_prompt、工具列表、模型和超时,派活时写对应 subagent_type 即可。
场景走一遍
用户要调研三款工具时:Lead Agent 拆成三个子任务,连续发(或一批)task 调用;限流中间件保证同时跑的不超过配置上限;三个子代理各自搜索、写分析;结果经轮询回到主智能体再汇总成报告。主智能体不亲自搜;噪声留在子上下文里;前端靠 status_contract 与 task_running 事件看进度。并行收益来自独立上下文加限流;默认禁嵌套 task,把组织层级钉在「只有 Lead Agent 能派活」这一层。
DeerFlow 子代理编排:task 工具与并行任务分解