Skip to content

[RFC] 三种 Multi-Agent 模式(pipeline / graph / council)能否收敛为统一的 dynamic workflow? #153

Open

Description

@Liears

背景

在给 backend/modules/agent/workflow.py 补测试时,我注意到 pipeline / graph / council 这三种模式更像是三种已经固化的拓扑,不能简单看作三种能力:

  • pipeline = 严格线性链,只能串行;
  • graph = 任意 DAG,能并行、能按条件跳过,但没有轮次/迭代;
  • council = 固定的「全员陈述 → 全员互评」两层全连接。

而真实任务经常是混合的。比如「先并行调研 3 个方向 → 收敛 → 实现 → 评审不过就回炉再改一轮」——今天用 pipeline 没有并行,用 graph 没有回环,用 council 又表达不了实现阶段。最后只能靠主 Agent 在模式外面手动拼多次 workflow_run,每次都是一次独立的工作流,子 Agent 的上下文、取消、失败状态都不共享。

所以想先和 owner 确认方向:这三种模式能否收敛成一个统一的执行器 + 声明式 workflow spec,把 mode 降级为「预设」? 以及要不要再往前走一步,让 spec 由模型在运行时生成(也就是 dynamic workflow)。

这不是 bug 报告,方向确认后我再动手;如果判断方向不合适,我就只做兼容层的小改动。

现状盘点

能力矩阵

维度 pipeline graph council
依赖结构 严格线性 任意 DAG 两层全连接
并行 无 就绪节点并行 全员并行
条件执行 无 有(仅 output_contains / output_not_contains) 无
迭代 / 轮次 无 无 固定 2 轮
上游上下文 累积全部前序输出 仅直接依赖节点的输出 他人的第 1 轮立场
结果语义 全文拼接 每节点状态 + 全文 返回各成员立场,要求主 Agent 原样呈现
实现 workflow.py:366-427 workflow.py:429-588 workflow.py:590-719

可以对照 workflow.py:393(## Outputs from previous stages)和 workflow.py:539(## Outputs from upstream agents):两个模式对「下游能看到多少上文」的处理规则不同,这里涉及的并不只是命名差异。

模式作为一等公民带来的耦合

  1. 存储层是 union 类型:agent_teams.agents 里的 task / depends_on / condition / perspective 各自只在一个模式下有意义(backend/api/agent_teams.py:27-49),cross_review 也只在 council 下有效(backend/models/agent_team.py:43-44)。数据库层没有任何约束能把「模式 + 字段」的一致性表达出来。

  2. 调度靠字符串分支,且没有默认分支保护:backend/modules/agent/workflow.py:97-104 是 if pipeline / elif graph / else council。旁边虽然声明了 WorkflowMode 枚举(workflow.py:30),但从未被使用,也就没有参与校验。

  3. 三份重复的编排逻辑:每个模式都各自实现了一遍 prompt 拼装、取消检查、子任务失败聚合、输出 Markdown 格式。_invoke_agent(workflow.py:157)之外的部分没有复用。

  4. 前端把 union 复制了 4 份:TeamDetail.vue:146-152、MultiAgentConfig.vue:54-56、TeamPicker.vue:51-53、ConfigImportExport.vue:369,692。新增一种模式要同时改:pydantic 两处 pattern、dispatch、3 个表单、i18n、导入校验——约 8 处,还分散在前后端。

  5. 几乎没有行为测试:整个 tests/ 对这三种模式的引用只有 5 行,且只有一处真正调用 run_outcome("graph", …)(tests/test_child_outcome_propagation.py:377),验证的还是子任务 outcome 传播,而不是图调度本身;run_pipeline / run_council 没有任何调用方。719 行编排代码处于无护栏状态,这也是我建议「先补测试再重构」的原因。

提案:一个执行器 + 声明式 spec

把「拓扑」从代码里搬到数据里,执行器只认 spec:

{
  "version": 1,
  "goal": "为 X 做技术选型并给出结论",
  "nodes": [
    {"id": "plan",    "kind": "agent",  "role": "规划",   "task": "拆解调研方向"},
    {"id": "research","kind": "map",    "needs": ["plan"], "over": "plan.items",
     "role": "研究员", "task": "调研一个方向"},
    {"id": "merge",   "kind": "reduce", "needs": ["research"], "strategy": "merge"},
    {"id": "build",   "kind": "agent",  "needs": ["merge"], "role": "工程师",
     "task": "给出方案", "context": "all"},
    {"id": "review",  "kind": "review", "needs": ["build"],
     "members": ["critic-a", "critic-b"], "rounds": 2},
    {"id": "final",   "kind": "reduce", "needs": ["review"], "strategy": "synthesize"}
  ]
}

三个既有模式都是这个 spec 的预设编译结果:

预设 编译结果
pipeline plan → agent → agent …(链式 needs,context: all)
graph 用户给的 depends_on 直接映射为 needs,context: direct
council map(members) → review(rounds=2) → (可选)reduce

执行器复用现在 graph 的调度器即可:依赖解析、就绪并行、条件跳过、环检测、取消、workflow_agent_* 流式事件都已经具备(workflow.py:429-560),需要新增的只有 map / reduce / review / 有界 loop 四类节点,以及一个显式的 context 规则(none / direct / all)——正好把上表里三种不一致的上下文传递方式统一成配置项。

这样能拿到的东西:

  • 混合拓扑可表达:fan-out → converge → 迭代评审,这是目前三种模式都做不到的;
  • 只有一条取消 / 失败 / outcome 路径(现在三份),ChildOutcomeError 的传播逻辑只需维护一次;
  • 进度可观测:现在是「有哪些子 Agent 在跑」,统一后可以显示「阶段 2/4、评审第 1/2 轮」;
  • 前端只维护一个 DAG 编辑器,三个模式变成模板按钮,而不是三套表单;
  • dynamic workflow 自然成立:spec 既可由用户编排,也可由模型在运行时生成(planner 产出 spec → 校验 → 预览 → 执行)。

迁移路径(每步独立可 review、可回退)

  1. 补 characterization 测试(不改行为):把 pipeline / graph / council 现有行为固化下来,作为后续重构的等价性护栏。
  2. 抽执行器:run_outcome 改为「编译 spec → 执行」,pipeline / graph 编译成 spec;mode 入参、API、DB 全部保持不变。此步行为应零变化,用第 0 步的测试证明等价。
  3. council 改写成预设 spec:review 节点承接「注入他人立场」的语义;保持当前输出格式不变(或按下面的问题 4 讨论是否改成显式 synthesize)。
  4. 新增 map / reduce / loop 与 context 规则,补齐混合拓扑能力。
  5. 可选:planner。模型生成 spec,先展示计划再执行,带子调用数与轮次上限(这是 dynamic workflow 的真正增量)。
  6. 前端:单一 DAG 编辑器 + 三个预设模板;旧团队在读取时即时编译,不需要数据迁移。

风险与成本

  • council 的语义是隐式的:成员 prompt 里直接注入了其他人的立场(workflow.py:682),这在 spec 里必须变成 review 节点的显式契约,否则行为会漂移。
  • council 现在不做最终综合:它返回各成员立场并要求和提示「原样呈现」给用户(workflow.py:710-718),和 pipeline / graph「返回合并结果」的语义不一致。统一时这是个必须明确的决策点,不能默认。
  • 动态规划的可复现性与成本:如果 planner 每次跑都重新生成拓扑,同一个团队两次执行结果不同,用户会困惑;需要「计划可预览 / 可存为模板 / 可固定随机性」的设计。
  • 范围:第 0-2 步大概是一次中等规模重构(执行器 + council 改写 + 测试),不涉及子 Agent / 工具层;第 3-4 步是新能力,可以单独排期。

需要 owner 拍板的问题

  1. 定位:这三种模式是「有意设计的三种产品形态」(每种对应一个用户能立刻理解的心智模型,简单直接),还是「引擎侧抽象还没做完」?如果是前者,我建议只做第 0-1 步(内部统一、外部不变),不动 UI。
  2. dynamic 的层次:你想要的是「用户用声明式 spec 编排」(确定、可观测、可版本化),还是「模型运行时规划拓扑」(灵活,但需要预算与预览机制)?我倾向两者并存:planner 产出 spec,但 spec 是唯一执行入口。
  3. 兼容:mode 是否永久保留为预设别名?旧团队的 agents JSON 是「读取时即时编译」还是「一次性迁移」?
  4. council 的结果语义要不要保留?我倾向把「综合」变成显式 reduce/synthesize 节点可选开启,默认保持现有行为以免破坏用户预期。
  5. 成本上限:统一执行器里要不要内置硬上限(每个工作流子调用数、总 token 预算、loop 最大轮次)?现在三种模式都没有。
  6. 如果这套方向你认为太重,最小可接受版本是哪一步?(比如只做 context 规则统一 + mode 显式校验?)

附:读代码时发现的一个具体问题(与本提案相关,可单独修)

run_outcome 的 else 分支把任何非 pipeline / graph 的值都当成 council(workflow.py:101-104),而 workflow_run 的入参 schema 里 mode 不是必填(workflow_tool.py:117-171,required 只有 goal),execute_outcome(mode=None)(workflow_tool.py:193)会一路传下去。实测:

None        -> council
''          -> council
'Pipeline'  -> council
'dag'       -> council
'council'   -> council

也就是说模型漏传或者写错 mode 时,会静默跑一次 2N 次子调用的 council(N 个成员各 2 轮),而不是报错或落到默认的 pipeline。这类静默降级在模式越多时越危险,也是我倾向「spec + 显式校验」的一个现实理由。

如果需要,这个点我可以单独提一个小 PR:缺省/非法 mode 显式返回 VALIDATION(或落到显式声明的默认值),并把已有的 WorkflowMode 枚举接进 dispatch。不会混进本提案的重构里。

Activity

  1. countbot-ai commented on Nov 3, 2026

    @countbot-ai
    Owner

    感谢你做这么细致的分析,方向很好,我们非常乐意接受这个 PR,期待你的提交

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions