第 5 章 · 调度循环:Supervisor 派活
一句话结论:多 agent 协调的「执行中枢」是一个 Supervisor(监督者)——它遍历 Plan,把每一步分派给合适的 Worker,再把结果收回来。它是「平级聊天」和「可靠流水线」之间的分水岭:必须有一个节点对「任务是否完成」负最终责任。
1. 先看「没有 Supervisor」会怎样
想象一下:研究员和程序员是平等的,大家共享一份 Plan,谁觉得哪步能干就干哪步。
# 糟:无监督,各自抢活
for agent in agents: # 所有 agent 同时开工
agent.pick_any_step(plan) # 自己挑想干的步骤问题接踵而至:
- 没人对结果负责:大家都干「容易的」,难步骤没人认领;或者两个人抢同一步。
- 没有收敛条件:什么时候算「研究做完了」?谁来判断「资料够了,可以去写报告了」?
- 错误会扩散:一个研究员跑偏了,没人发现、没人纠正,它就这么一直跑下去。
2. 思考的分叉:协调该用「监督式」还是「协作式」?
这是多 agent 架构最经典的一组分叉,DeerFlow 的选型很明确:
方向 A:协作式(collaborative,平级群聊)——AutoGen 的 GroupChat、CrewAI 的 Crew 是代表。
- 优点:灵活,agent 之间可以自由商量,涌现出计划外的协作。
- 致命伤:没有单一控制点,收敛和容错都难保证。生产环境里,你最怕的就是「这活到底干没干完」。
方向 B:监督式(orchestrator-workers,层级分派)——DeerFlow、LangGraph Supervisor 是代表。
- 优点:有一个明确的节点(Supervisor)负责「下一步该谁干、干完没干完」,收敛条件清晰,错误可被 Supervisor 拦截。
- 代价:灵活性略降,Supervisor 本身成了一个瓶颈点。
为什么 DeerFlow 选 B:
- 深度研究是一个目标明确、可分解、有截止点的任务,天然适合「有一个总指挥」的结构。
- 更本质的原因:「协调」要落地,就必须有个地方承载「任务的状态机」——哪些步骤待办、哪些进行中、哪些完成。协作式把这个状态机分散在每个 agent 脑子里,谁也说不清全局;监督式把它集中到 Supervisor 手里,全局状态一目了然。
类比:协作式是「一群自由职业者自发组队」,监督式是「一个项目经理带一支团队」。前者在创意型、小规模任务里好用;后者在「有明确交付物、要按时收敛」的任务里可靠。研究任务是要交付一份报告的,必须有项目经理。
3. 伪代码:Supervisor 的调度循环
DeerFlow 1.x 里,research_team 节点就是一个 Supervisor,它内部跑这样一个循环:
def research_team_node(state):
plan = state["plan"] # 拿到契约
for step in iterate_steps(plan): # 遍历 Plan 的每一步(含子步骤)
worker = route(step) # ① 路由:这一步该谁干?
result = worker.run(step) # ② 分派:让选中的 worker 干
state["step_results"][step.id] = result # ③ 回收:把结果写回共享状态
return state
def route(step):
# 路由规则:需要「搜索/检索」的交给 Researcher,需要「计算/画图」的交给 Coder
if step.requires_retrieval():
return RESEARCHER
if step.requires_code():
return CODER
return RESEARCHER # 默认给研究员这个循环看起来简单,但藏着三个关键的协调决策:
- 路由(route):Supervisor 必须能「看懂这一步需要什么能力,然后派给对应的人」。DeerFlow 的做法是基于步骤的属性(要不要检索、要不要算)做规则路由。这是最朴素的协调——能力的匹配。
- 回收(写回共享状态):worker 干完,结果不是直接递给下一个 worker,而是写回
step_results。这样任何一个后续角色(尤其是 Reporter)都能从共享状态里读到所有结果,而不是只能读到「上一个人递过来的那份」。 - 遍历顺序(iterate_steps):这是下一个要展开的问题——步骤之间,能不能并行?
4. 思考的分叉:顺序执行 vs 并行执行
Plan 是一棵树。同一父节点下的多个兄弟步骤,往往互相独立,可以并行;而父子步骤有依赖,必须串行。这就引出一个调度策略的选择:
方向 A:全部顺序执行——简单,但慢。20 个独立步骤要一个个跑,深度研究可能要跑几十分钟。
方向 B:无脑全并行——快,但违背依赖。如果一个步骤的输入依赖另一个步骤的输出,并行会拿到空数据或旧数据。
方向 C:按依赖关系并行(同一层的兄弟节点并行,父子串行)——既快又不违背依赖,但实现最复杂。
为什么 DeerFlow 倾向 C(并在 2.0 里强化了并行):
- 深度研究的瓶颈在「等待搜索、抓取、爬虫」这些 IO 密集操作,并行能把墙钟时间从「所有步骤之和」压到「最长的那条依赖链」。
- 依赖关系 Plan 树里已经写明了(父子 = 依赖,兄弟 = 独立),所以并行调度不需要额外信息,Plan 契约本身就是调度依据。
def iterate_steps(plan):
"""按依赖关系展开:同一层兄弟步骤并行,父子串行"""
for top_step in plan.steps: # 顶层步骤按序(通常有先后语义)
yield from run_level(top_step) # 每个顶层步骤内部,兄弟并行
def run_level(step):
results = parallel([worker.run(s) for s in step.substeps]) # 兄弟并行
return results一个值得记住的洞察:「并行」之所以可行,完全是因为第 3 章那个「树形 Plan」已经显式编码了依赖关系。 如果 Plan 是扁平的、没有依赖信息的,你根本无法安全地并行——这正是「结构化契约」的又一个红利:它同时是「协调的依据」和「调度的依据」。
思考题
Q:Supervisor 成了所有任务的中枢,那它会不会变成瓶颈?如果 Supervisor 自己判断错了(比如把该给 Coder 的步骤派给了 Researcher),系统会怎样?
参考答案:
会,这正是监督式架构必须正视的代价。 两个层面:
- 性能瓶颈:所有分派都过 Supervisor,如果它是「同步逐个派活」,并行收益会被它自己吃掉。解法是让 Supervisor 只做「轻量的路由决策」,把重活全甩给 worker,并让 worker 真正并行跑。
- 单点判断错误:Supervisor 路由错,轻则效率低(Researcher 硬啃代码步骤),重则任务失败。解法有两层——降低路由难度(Plan 步骤类型化,路由规则简单可靠)和容错兜底(第 9 章的重规划:发现执行结果不对,可以回头重来)。
更深一层的认识:架构上没有免费的午餐。 协作式把「单点瓶颈」分散了,却换来了「没有收敛条件」;监督式牺牲了灵活性,换来了「可靠的收敛」。关键不是找一个没有缺点的架构,而是看你的任务最不能容忍哪种失败——研究任务最不能容忍「不知道干没干完」,所以宁可接受 Supervisor 这个可控的瓶颈。