Repository navigation
Description
背景
在给 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):两个模式对「下游能看到多少上文」的处理规则不同,这里涉及的并不只是命名差异。
模式作为一等公民带来的耦合
-
存储层是 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)。数据库层没有任何约束能把「模式 + 字段」的一致性表达出来。 -
调度靠字符串分支,且没有默认分支保护:
backend/modules/agent/workflow.py:97-104是if pipeline / elif graph / else council。旁边虽然声明了WorkflowMode枚举(workflow.py:30),但从未被使用,也就没有参与校验。 -
三份重复的编排逻辑:每个模式都各自实现了一遍 prompt 拼装、取消检查、子任务失败聚合、输出 Markdown 格式。
_invoke_agent(workflow.py:157)之外的部分没有复用。 -
前端把 union 复制了 4 份:
TeamDetail.vue:146-152、MultiAgentConfig.vue:54-56、TeamPicker.vue:51-53、ConfigImportExport.vue:369,692。新增一种模式要同时改:pydantic 两处pattern、dispatch、3 个表单、i18n、导入校验——约 8 处,还分散在前后端。 -
几乎没有行为测试:整个
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、可回退)
- 补 characterization 测试(不改行为):把 pipeline / graph / council 现有行为固化下来,作为后续重构的等价性护栏。
- 抽执行器:
run_outcome改为「编译 spec → 执行」,pipeline / graph 编译成 spec;mode入参、API、DB 全部保持不变。此步行为应零变化,用第 0 步的测试证明等价。 - council 改写成预设 spec:
review节点承接「注入他人立场」的语义;保持当前输出格式不变(或按下面的问题 4 讨论是否改成显式synthesize)。 - 新增
map/reduce/loop与context规则,补齐混合拓扑能力。 - 可选:planner。模型生成 spec,先展示计划再执行,带子调用数与轮次上限(这是 dynamic workflow 的真正增量)。
- 前端:单一 DAG 编辑器 + 三个预设模板;旧团队在读取时即时编译,不需要数据迁移。
风险与成本
- council 的语义是隐式的:成员 prompt 里直接注入了其他人的立场(
workflow.py:682),这在 spec 里必须变成review节点的显式契约,否则行为会漂移。 - council 现在不做最终综合:它返回各成员立场并要求和提示「原样呈现」给用户(
workflow.py:710-718),和 pipeline / graph「返回合并结果」的语义不一致。统一时这是个必须明确的决策点,不能默认。 - 动态规划的可复现性与成本:如果 planner 每次跑都重新生成拓扑,同一个团队两次执行结果不同,用户会困惑;需要「计划可预览 / 可存为模板 / 可固定随机性」的设计。
- 范围:第 0-2 步大概是一次中等规模重构(执行器 + council 改写 + 测试),不涉及子 Agent / 工具层;第 3-4 步是新能力,可以单独排期。
需要 owner 拍板的问题
- 定位:这三种模式是「有意设计的三种产品形态」(每种对应一个用户能立刻理解的心智模型,简单直接),还是「引擎侧抽象还没做完」?如果是前者,我建议只做第 0-1 步(内部统一、外部不变),不动 UI。
- dynamic 的层次:你想要的是「用户用声明式 spec 编排」(确定、可观测、可版本化),还是「模型运行时规划拓扑」(灵活,但需要预算与预览机制)?我倾向两者并存:planner 产出 spec,但 spec 是唯一执行入口。
- 兼容:
mode是否永久保留为预设别名?旧团队的agentsJSON 是「读取时即时编译」还是「一次性迁移」? - council 的结果语义要不要保留?我倾向把「综合」变成显式
reduce/synthesize节点可选开启,默认保持现有行为以免破坏用户预期。 - 成本上限:统一执行器里要不要内置硬上限(每个工作流子调用数、总 token 预算、loop 最大轮次)?现在三种模式都没有。
- 如果这套方向你认为太重,最小可接受版本是哪一步?(比如只做
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
countbot-ai commentedon Nov 3, 2026
感谢你做这么细致的分析,方向很好,我们非常乐意接受这个 PR,期待你的提交