第 5 章 · 调度循环:Supervisor 派活

一句话结论:多 agent 协调的「执行中枢」是一个 Supervisor(监督者)——它遍历 Plan,把每一步分派给合适的 Worker,再把结果收回来。它是「平级聊天」和「可靠流水线」之间的分水岭:必须有一个节点对「任务是否完成」负最终责任。


1. 先看「没有 Supervisor」会怎样

想象一下:研究员和程序员是平等的,大家共享一份 Plan,谁觉得哪步能干就干哪步。

# 糟:无监督,各自抢活
for agent in agents:                # 所有 agent 同时开工
    agent.pick_any_step(plan)       # 自己挑想干的步骤

问题接踵而至:

  1. 没人对结果负责:大家都干「容易的」,难步骤没人认领;或者两个人抢同一步。
  2. 没有收敛条件:什么时候算「研究做完了」?谁来判断「资料够了,可以去写报告了」?
  3. 错误会扩散:一个研究员跑偏了,没人发现、没人纠正,它就这么一直跑下去。

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   # 默认给研究员

这个循环看起来简单,但藏着三个关键的协调决策:

  1. 路由(route):Supervisor 必须能「看懂这一步需要什么能力,然后派给对应的人」。DeerFlow 的做法是基于步骤的属性(要不要检索、要不要算)做规则路由。这是最朴素的协调——能力的匹配。
  2. 回收(写回共享状态):worker 干完,结果不是直接递给下一个 worker,而是写回 step_results。这样任何一个后续角色(尤其是 Reporter)都能从共享状态里读到所有结果,而不是只能读到「上一个人递过来的那份」。
  3. 遍历顺序(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),系统会怎样?

参考答案:

会,这正是监督式架构必须正视的代价。 两个层面:

  1. 性能瓶颈:所有分派都过 Supervisor,如果它是「同步逐个派活」,并行收益会被它自己吃掉。解法是让 Supervisor 只做「轻量的路由决策」,把重活全甩给 worker,并让 worker 真正并行跑。
  2. 单点判断错误:Supervisor 路由错,轻则效率低(Researcher 硬啃代码步骤),重则任务失败。解法有两层——降低路由难度(Plan 步骤类型化,路由规则简单可靠)和容错兜底(第 9 章的重规划:发现执行结果不对,可以回头重来)。

更深一层的认识:架构上没有免费的午餐。 协作式把「单点瓶颈」分散了,却换来了「没有收敛条件」;监督式牺牲了灵活性,换来了「可靠的收敛」。关键不是找一个没有缺点的架构,而是看你的任务最不能容忍哪种失败——研究任务最不能容忍「不知道干没干完」,所以宁可接受 Supervisor 这个可控的瓶颈。