“既然已经有了 Chain,为什么还要 Graph?既然 Chain 和 Graph 都有了,为什么还要 Workflow?”
这是我最近在几个技术社群里反复看到的问题,也是我自己第一次接触 Eino 时心里真实的嘀咕。说实话,我最早是带着“Graph 已经是最灵活的编排方式了,Workflow 八成是套壳”的偏见去用 Eino 的,直到我把一个真实项目从 Graph 迁移到 Workflow,又在另一个需求里被逼着用三种方式各实现了一遍,才彻底理解了 Eino 团队为什么把 Workflow 当成压轴设计。
这篇文章不聊套话,直接把我的理解、对比结论、选型思路和踩坑记录完整讲清楚,希望能帮到正在做 AI 大模型应用编排的朋友。
1. Chain、Graph 到底卡在了哪里
1.1 Chain 的局限:顺序执行的“线性牢笼”
Chain 是 Eino 里最朴素的编排范式,本质上就是一个按顺序执行的节点列表:上一个节点的输出,天然成为下一个节点的输入。写 demo 的时候特别舒服,代码短、逻辑直白,你可以一眼看到整条调用链路。
我见过很多人在项目初期用 Chain,因为业务逻辑还没复杂到需要分支和并行的程度。但一旦业务开始长出来,Chain 的问题就非常致命:
- 不支持并行。多个互不依赖的节点只能老老实实排队跑,延迟是叠加的。
- 不支持分支。Router 路由、条件判断这类需求,用 Chain 写起来非常别扭,只能在节点内部塞 if/else,代码很快就臭了。
- 不支持循环。重试、反思、多轮迭代这些真实需求,Chain 根本表达不了,只能在外部套 for 循环,把编排逻辑打在业务代码里。
我举个例子:做一个三模型投票审核的功能,三个模型的审核请求互不依赖,完全可以并行。但如果用 Chain 实现,就只能是模型 A 跑完再跑模型 B,再跑模型 C。我实测过一个项目,单次调用串行延迟大概 1.5 秒,换成并行之后直接降到 700 毫秒。这个差距在用户侧体感是非常明显的。
所以 Chain 的定位更像是“快速验证”和“极简链路”,一旦涉及多个分支、并行、动态路由,它就会成为瓶颈。
1.2 Graph 的“自由之痛”:图复杂之后没人看得懂
Graph 解决的就是 Chain 线性执行的痛点。它提供了一套节点加边的自由图结构,天然支持并行、分支、合并,你可以任意定义节点之间的依赖关系,只要图上无环就行。灵活性确实大幅提升了。
但是,Graph 最大的问题恰恰来自它的自由。
首先,图结构没有“职责分层”的概念。所有节点都是平等的普通节点,你在一个节点里既要做输入校验,又要组装 Prompt,又要调用模型,又要解析输出,代码全揉在一起。图小的时候还好,图一旦变大,节点之间的连接关系错综复杂,执行顺序只能靠人脑去推理边的关系。其次,状态管理是隐式的。Graph 里最常见的做法是定义一个外部变量或者用 context 在节点之间传数据,传着传着就乱了——你不知道哪个节点在哪个时刻覆盖了某个字段,也不知道为什么下游节点读到的数据是旧值。
还有一点是 DAG 的限制。Graph 要求图是无环的,这意味着“循环执行”这个非常常见的 Agent 需求(比如反思、迭代优化、兜底重试)在纯 Graph 里很难优雅表达,通常只能在外面再包一层循环逻辑。
我印象最深的一次:当时我用 Graph 搭一个内容生成 Agent,刚开始只有 10 个节点,还能应付。后来加上了意图识别、多模型并行、质量评估、重试修复,节点一下涨到了 30 多个。结果某天发现一个节点的输入数据不对,我对着拓扑理了半天,最后靠打印日志一步步猜才定位到问题。那一次之后我就明白了:Graph 能表达复杂逻辑,但在工程上很难维护复杂逻辑。
2. Workflow 到底多了什么:图之上的“执行纪律”
Workflow 的本质,是在 Graph 的拓扑结构之上,增加了一套完整的执行规则和生命周期管理机制。Graph 让节点能表达任意关系,Workflow 则进一步告诉引擎:这些节点分别是什么类型、在什么阶段执行、状态怎么管理、能不能循环。
我把它理解成“有纪律的 Graph”。
2.1 节点分类:把“干什么活”拆清楚
Workflow 里最直观的变化是节点不再是一视同仁的普通节点,而是被分成不同类型的角色。按照我在实践中接触到的 Eino 版本,节点大致可以分为 Processor(处理器)、Operator(算子)、State(状态)节点,还可以内嵌 Graph 或 SubGraph 子编排。
Processor 类节点负责轻量级的数据处理和准备工作,比如参数校验、Prompt 组装、输出格式化的解析。Operator 类节点承载核心计算逻辑,最典型的就是真正发起 LLM 调用的节点,它们需要访问真实的外部服务并拿回结果。
这样做的好处非常明显:你把原来揉在单个节点里的装配逻辑和调用逻辑拆开了。代码结构变清晰了,图的职责也边界化了。后面无论是调优还是排查问题,一眼就能看出某个节点到底在干什么——是准备数据,还是干重活。
我自己的编码习惯是:凡是涉及 LLM API 调用的节点,都单独设计成 Operator;凡是做 Prompt 组装、结果清洗这类工作的,拆成 Processor。这个习惯直接让图的模块化程度提升了一大截。
2.2 阶段化执行:让节点在正确的时机并行
这是 Workflow 最核心的机制,也是它和 Graph 拉开差距的关键。
Graph 里的并行是“通过边来表达的”:只要两个节点之间没有依赖边,引擎就会尝试并发执行,所有节点处于同一个执行层次上。而 Workflow 引入了“阶段(Phase)”的概念,整个工作流的执行被划分成一系列有序的阶段。每个节点都要声明自己在哪个阶段执行,同一阶段内的节点会被引擎统一调度、在满足依赖的前提下并行执行,阶段之间则按顺序推进。
你可以把它想象成一个餐厅后厨:不是每来一桌客人,厨师才临时跑去洗菜、切菜、炒菜,而是有一个固定的流程——先统一备菜(PreProcess),再统一烹饪(Operator 阶段),再统一装盘(PostProcess)。同一道工序里,多个厨师可以同时干活;工序之间则严格串行。这个模型天然贴合大模型应用的真实节奏:先统一校验和准备上下文、组装 Prompt,再一次性并发调用多个模型,最后统一解析和汇总结果。
这种设计带来的直接好处是性能和可预测性兼得。你不需要像在 Graph 里那样通过手动梳理边来确认并行关系,只要把节点正确归类到阶段,引擎就会按你定义的节奏去调度。这对生产环境非常重要。
2.3 状态管理、循环与可观测性:生产级编排的三大支柱
除了节点分类和阶段化执行,Workflow 还补上了三个 Graph 时代被我吐槽最多的能力。
状态管理方面,Workflow 把状态从“隐式传递”变成了“显式管理”。我可以在 Workflow 层面定义一个结构化的 State,让不同节点按声明好的规则读写它,而不是各个节点拿着一个外部变量乱改。状态变更变得可控,不容易出现诡异的覆盖问题。
循环能力方面,Workflow 突破了 Graph 的 DAG 无环限制。节点可以指回上游的节点,形成循环执行路径。这让“反思型 Agent”“迭代优化”“重试兜底”这类需求可以在编排层内部解决,不需要再包一层外部循环。
可观测性方面,Workflow 提供了更完善的生命周期钩子和执行追踪能力。每个节点在什么阶段启动、运行了多久、输入输出是什么,都能通过 Eino 的可观测机制监控起来,这对生产排障来说是刚需。
我把三种编排方式的差异整理成了一张对比表,方便你直接看结论:
| 维度 | Chain | Graph | Workflow |
|---|---|---|---|
| 执行方式 | 严格顺序 | 节点+边灵活编排 | 图结构+阶段化调度 |
| 分支支持 | 不支持 | 支持 | 支持 |
| 并行支持 | 不支持 | 支持 | 支持 |
| 循环支持 | 不支持,外部包一层 | 不支持(DAG受限) | 支持,节点可指回上游 |
| 状态管理 | 隐式透传 | 隐式透传,易乱 | 显式 State 管理 |
| 节点职责 | 无区分 | 无区分 | 区分 Processor / Operator 等 |
| 可维护性 | 简单场景很好 | 图大后急剧下降 | 结构清晰,适合生产维护 |
| 适用场景 | Demo、极简固定链路 | 中小规模并行/分支 | 生产级复杂 Agent、长流程 |
3. 怎么选:从 Chain 到 Graph 再到 Workflow 的判定标准
3.1 一条判断线:节点数、并行、循环、可维护性
有朋友问我最多的问题就是:“到底什么时候用 Chain,什么时候用 Graph,什么时候强行上 Workflow?”我给不出一个万能公式,但结合我自己的项目经验,可以总结一条非常实用的判断线。
- 如果流程固定、顺序线性、节点少于 5 个,未来基本不会加分支和并行,直接用 Chain,省事又直观。
- 如果流程有并行和分支,但整体规模可控(10 个节点以内),而且确认没有循环需求,用 Graph 完全够用。
- 只要目标是生产级应用,或者图已经肉眼可见地会变大,或者存在循环、状态共享、严格排障需求,我的建议是:不要犹豫,直接用 Workflow。
我见过太多人一开始图省事用 Graph 搭了个小原型,结果业务一扩张,重构成本高得惊人。反而是那些从一开始就用 Workflow 的项目,后面加需求的时候平滑很多。
还有一个经验值得分享:不要以“是否复杂”来判断要不要用 Workflow,而要以“是否面向生产持续演进”来判断。哪怕你现在的流程只有三个节点,但只要这个 Agent 要上线、要长期迭代,Workflow 带来的结构清晰度和可观测性收益,会远远超过初期多写的那几行配置。
3.2 实战案例:一个多模型内容审核 Agent 的 Workflow 设计
这里放一个我最近实际做过的案例:多模型内容审核 Agent。
需求是这样的:输入一段用户生成的内容,需要同时调用三个不同厂商的模型做独立审核——一个负责安全合规判断,一个负责情感倾向分析,一个负责敏感信息抽取。然后把三个模型的结果汇总,做一个投票或加权判断,输出最终结论。如果三个模型的分歧过大,则启动第二轮深入审查,调用一个更重的模型做定向分析。
如果换成 Chain,就只能三个模型串行跑,延迟爆炸,而且投票汇总逻辑不好放。如果换成 Graph,并行可以做到,但第二轮“分歧太大就循环进入深入审查”的需求,用 DAG 很难优雅表达,而且三个模型结果的中间状态管理会很痛苦。
用 Workflow 实现的话,我的设计分成了这样几个阶段:
- Input 阶段:接收输入内容,做格式校验,生成一个全局的审核 State。
- PreProcess 阶段:三个 Processor 节点并行工作,分别把原始内容组装成三个模型各自需要的 Prompt 格式。
- Operator 阶段:三个 Operator 节点并行发起模型调用,分别拿到三个模型的审核结果,写回 State。
- PostProcess 阶段:一个汇总节点读取三个结果,做投票合并,输出结论。同时判断是否需要进入第二轮深入审查。
- 如果判断需要第二轮,则通过循环节点回到深入审查的 Operator,再走一次后处理。
这里最香的地方在于:PreProcess 阶段三个节点天然并行,Operator 阶段三个模型调用天然并行,整个流程的并行度是阶段自动赋予的,不需要手动维护节点的边关系。而且循环是 Workflow 原生的,不需要外部包 for 循环。
4. 把 Graph 迁到 Workflow,我踩过的四个坑
4.1 迁移的第一步:给旧节点“定性”
从 Graph 迁到 Workflow,第一步不是写代码,而是重新审视已有的节点,给每个节点“定性”:它是 Processor 还是 Operator?它应该在哪一个阶段执行?它需要读写哪些状态字段?
我当时把旧 Graph 的 20 多个节点全部梳理了一遍,用表格列出来:节点名、职责、输入、输出、归属阶段。这一步看似繁琐,实际上特别重要,它帮我理清了原有的隐式逻辑,也让后续的迁移有了清晰的施工图。
4.2 坑一:把所有节点都当 Operator,并行度没提上来
我第一次迁移的时候偷了个懒,把所有节点都定义成 Operator,只是把边关系原样保留。结果跑下来,发现执行时长和原来 Graph 相比几乎没有提升,很多理论上可以并行的节点还是串行着跑。
后来我才意识到,阶段化执行模型里,真正决定并行度上限的是阶段机制。只有把数据准备类节点放进 PreProcess、把模型调用类节点放进 Operator,让它们落在同一个阶段,引擎才会在同一阶段内部调度并行。把所有节点都塞进同一个类型,等于抛弃了阶段机制的核心优势。
那次踩坑之后我总结了一个准则:先按“数据准备、核心调用、结果处理”三段去划分节点,再在每一段内部检查节点之间是否互不依赖。这样并行度自然就上来了。
4.3 坑二:状态覆盖与循环死循环
Graph 时代我一直用外部 map 传状态,迁移到 Workflow 之后,有一段时间我还是保留了这个习惯,结果就遇到一个诡异的问题:三个模型的结果写回后,最后读出来只有一个模型的数据,其他两个被覆盖了。
排查了很久才发现,是多个并行节点同时修改了外部共享变量的同一个字段。Workflow 提供的 State 机制可以规避这个问题,但前提是你得按规范声明每个节点读写哪些状态字段,让引擎帮你管理生命周期的访问。如果还是沿用外部变量的思路,等于把状态管理的包袱又背了回来。
循环死循环也是个经典坑。Workflow 支持节点指回上游形成循环后,如果退出条件写得不好,很容易出现死循环。我的习惯是:在循环入口的前置节点里,先写死循环次数的上限,再叠加业务判断条件,双重保险。实测下来,这个习惯帮我避免了好几次线上事故。
4.4 坑三:可观测性钩子没挂上,白加班
还有一次排查性能问题时,我发现自己对着 Workflow 的日志什么都看不出来,因为我没有在初始化阶段挂上 Eino 的可观测组件。结果只能靠最原始的代码日志去瞎猜,整整多花了一个下午。
所以,在 Workflow 初始化的时候,第一件事就是把可观测性和追踪能力挂上。这不是锦上添花,而是生产排障的基础设施。挂上之后,每个节点的运行状态、耗时、输入输出一目了然,问题定位效率完全不是一个量级。
5. 常见问题速查与避坑清单
最后整理一份常见问题速查表,都是我实际遇到过、或者在社区里被反复问过的问题,直接拿走能用:
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
| Workflow 节点没按预期并行 | 节点没有正确归类到同一阶段,或存在隐式依赖 | 检查节点阶段归属,用 State 声明依赖关系 |
| Graph 里无法表达循环逻辑 | Graph 是 DAG 无环设计 | 换用 Workflow,节点指回上游实现循环 |
| 并行节点结果互相覆盖 | 多个节点写全局状态同一字段 | 使用 State 机制,明确每个节点读写的状态字段 |
| 循环执行出现死循环 | 退出条件缺失或判断顺序错误 | 在循环入口增加次数上限+业务条件双重判断 |
| 排查问题找不到节点执行痕迹 | 没有挂载可观测组件 | 初始化阶段挂上 Eino 可观测 Track,开启全链路追踪 |
| 不清楚该用 Chain 还是 Workflow | 没有按生产演进视角做决定 | 面向生产迭代优先 Workflow,临时 Demo 用 Chain |
我个人现在组建新项目时,基本上默认 Workflow 起步,除非是那种真的两三步就完事的验证脚本。原因很简单:用 Workflow 写出来的编排逻辑,代码组织方式和执行模型是一致的,图和代码一样都能读懂。
最后再分享一个小习惯:每建一个 Workflow 节点,我都会给节点起一个语义明确的名字,并且在节点注释里标明它属于哪个阶段、依赖哪些上游状态、输出哪些字段。这样半年以后回头看这张图,哪怕已经忘了当时的上下文,也能一眼读懂设计意图。这个习惯,比任何框架选型都更能决定你项目的长期可维护性。