
译者前言:Microsoft Agent Framework 几天前刚发布 1.0,连同 Google ADK、LangGraph、CrewAI,几乎所有主流 Agent 框架都在做同一件事:把「工作流引擎」和「Agent 运行时」打包在一起卖。但有意思的是,Anthropic 和 OpenAI 这两家模型厂,却偏偏不这么干。这篇文章就是想聊聊这背后的逻辑,以及为什么我认为某些做法其实是在重蹈覆辙。
打开 Microsoft Agent Framework 的 Workflows 包源码,或者 LangGraph 的 Pregel 模块,你会看到同一个东西:一个运行在 BSP(Bulk Synchronous Parallel)模型上的图引擎。BSP 最初是 Google 在 Pregel 论文里提出的,最初是为了大规模图处理用的。
每个 superstep 里,所有有待处理消息的执行器并发运行;全部完成后,消息沿边传递,下一个 superstep 启动。如此反复,直到没有待处理消息为止。
如果你用过 Apache Beam、Akka Streams 或 Flink,应该会觉得眼熟——流处理系统的设计思路是一样的。Pregel 这类系统之所以存在,是因为当有很多小计算需要通过带类型的消息协调、每轮之间还要 checkpoint 时,BSP 提供了一个干净的执行模型,内置 fan-out、fan-in 和同步屏障。
它的适用场景真实存在,但比较窄:
对于典型的 Agent 应用,以上三点你大部分时候都不需要。
图数据流假设的拓扑是这样的,有 fan-out、join 和 superstep:
大多数 Agent 应用的真实结构其实是这样的:一个 Agent 循环加工具调用,偶尔有一次 handoff:
所谓 handoff,无非是一个 Agent 调用了一个把控制权移交给另一个 Agent 的工具。这就是 while 循环里的一个函数调用,根本不需要 superstep。
不出意料,Anthropic 和 OpenAI——这两家所有框架都在对接的模型厂——非常默契地没有自带工作流引擎。
| Anthropic / OpenAI | Microsoft / Google / LangChain | |
|---|---|---|
| Workflow engine | Don't ship one | Ship one bundled with the agent runtime |
| Composition | Bring your own (Temporal, FSM, bus, code) | Use ours (graph DSL, declarative YAML) |
| Examples | Claude Agent SDK, OpenAI Agents SDK | MAF Workflows, Google ADK, LangGraph, CrewAI |
| Bet | Models need less scaffolding over time | Enterprises need built-in compliance + audit |
Anthropic 2024 年底发布的《Building Effective Agents》立场非常明确。他们在 workflows(预定义的代码路径)和 agents(自我导向行为的系统)之间画了一条线,然后明确警告不要过度依赖框架。核心论点是:框架增加的抽象层会遮蔽底层的 prompt 和响应,让人在本可以用更简单方案时忍不住引入更多复杂度。文章直接点名了 LangGraph、Bedrock Agents、Rivet 和 Vellum。
Claude Agent SDK 体现了这个思路。核心是这些:Agent 循环、工具调用、内存钩子、MCP 集成。没有工作流图,没有节点-边 DSL。想做多步逻辑?写代码。
OpenAI 的 Agents SDK 形态一致。四个核心概念:agents、handoffs、guardrails、sessions。Handoff 不是图拓扑,就是 LLM 调用的一个普通函数工具(transfer_to_refund_agent)。多 Agent 编排就是一个 Agent 调用工具把控制权移交给另一个。当用户需要持久化时,推荐做法是把 SDK 包在 Temporal 里:每个 Agent 调用变成一个 Temporal activity,工作流引擎负责持久化和故障恢复。
所以模型厂自己的意思是:做出色的基础组件,让人们用已有的编排工具自由组合。
「工作流」这一个词被用来指代至少四个完全不同的问题。它们不能混用。
| Category | What it solves | Example libraries | Production-grade since |
|---|---|---|---|
| In-process FSM | Valid state transitions, single process | Stateless, Automatonymous | ~2010 |
| Distributed saga | Cross-service coordination + compensations | MassTransit, NServiceBus, Brighter | ~2009 |
| Durable execution | Replay-based recovery through crashes | Temporal, DurableTask, DBOS, Restate | ~2017 |
| Graph dataflow | Typed parallel message-passing + streaming | LangGraph, MAF Workflows, Beam | ~2024 (for agents) |
这个分类里,在 Agent 框架讨论中最被忽视、但其实最重要的是持久化执行。它提供了 FSM 和消息总线都给不了的东西:确定性回放。
Run 1 (crashes after step 3): Run 2 (replay):
----------------------------- ---------------
step 1 --> [LLM call: \$0.50] logged step 1 --> [logged] skip
step 2 --> [tool call] logged step 2 --> [logged] skip
step 3 --> [LLM call: \$5.00] logged step 3 --> [logged] skip
step 4 --> [tool call] CRASH step 4 --> [tool call] runs
step 5 --> continues normally
工作原理:你的代码运行,引擎把每个外部调用作为事件记录下来。如果进程中途挂掉,它不会从一个保存的 saga 状态重启,而是从顶部重放你的代码,跳过已有结果的调用,直到追赶到实时状态。从你的角度看,你写的是直线型异步代码;从系统角度看,每一步都是可恢复的。
对于需要长时间运行的 Agent 工作(一轮 LLM 调用 30 秒,接着一个工具调用 2 分钟,再来一轮 LLM 调用),这正是合适的原语。你肯定不想因为容器重启就重做一次 5 美元的 LLM 调用。
有意思的是,Microsoft 其实已经内置了 DurableTask,从 2017 年就有了。Agent Framework 的 Workflows 包甚至有 DurableTask 集成。这引出了一个合理的问题:如果我需要持久化,直接用 DurableTask 就好了;如果需要消息总线,直接用 MassTransit 就好了;如果需要状态验证,直接用 Stateless 就好了。那这个图运行时到底额外带来了什么?
诚实的答案是:流式输出,和图拓扑的声明式编排。对于大多数 Agent 应用,这两者都不重要。
学习一套新的编排模型有真实的成本,框架的市场宣传往往会低估它。
| Adopting a graph dataflow runtime | Composing existing primitives |
|---|---|
| Executor lifecycle | async Task (already known) |
| Edge types: direct, fan-in, fan-out, switch | Stateless config (afternoon to learn) |
| Superstep concurrency rules | MassTransit sagas (well-documented) |
| Port system + checkpointing | DurableTask (years of Azure Functions docs) |
| All framework-specific | Composes; transfers everywhere |
框架的卖点是「编排的事我们帮你搞定」。现实往往是:你在你写的代码和真正运行的东西之间多加了一层间接寻址。调试一个行为异常的 Agent 流程,现在意味着同时理解模型的推理和运行时的 superstep 调度。当某个环节卡住了:是 LLM 的问题,还是边路由器的问题?
这正是 Anthropic 对框架批评最有力的一点。他们不是说框架是错的,而是说框架遮蔽了 prompt 和响应。对于 Agent 工作,这种遮蔽代价高昂。调试时最有用的一步就是看清楚模型实际被问了什么、实际答了什么。任何在这层循环里加间接层的做法,在出问题时会让你付出更多。
当然也有合理的反驳。声明式图工作流让合规、审计和可视化更容易。如果你向受监管行业的企业卖产品,「这份 YAML 明确定义了 Agent 的行为」确实有价值——这显然是 Microsoft 和 Google 在押的注。我不是说这个赌注错了。只是它和模型厂自己的做法(做出色的原语,相信开发者会组合)走的是不同的路。
我对重型工作流引擎持怀疑态度的另一个原因是:模型本身在处理长周期工作时越来越不需要外部编排的脚手架了。
Anthropic 最近关于 context engineering 的文章描述了 Claude Code 的模式:一个长时间运行的 Agent,用 glob 和 grep 这类原语即时探索环境,CLAUDE.md 文件提供高层指令,文件系统本身充当临时内存。它不是图,不是状态机,就是一个有趁手工具和 workspace 的模型。
正在浮现的模式——而且我认为这是真正有趣的趋势——是用文件系统作为长时间任务的临时内存。Agent 工作时读写和更新文件;如果它崩溃了,把同一个 workspace 交给它继续就行。工作流的「状态」就是文件的状态,而这是每个开发者都理解的抽象。
这基本就是 Claude Code、Cursor 和同类编程 Agent 今天的工作方式。编排很轻,因为模型自己在做编排。框架的职责是给模型好的工具、一个运行它们的沙盒,以及故障时的恢复方式。框架的职责不是把 Agent 的推理建模成一张图。
如果模型在长周期一致性上持续进步(过去 18 个月的轨迹表明会的),那为 2024 年 Agent 能力设计重型编排框架迟早会显得杀鸡用牛刀,就像在大多数现代 Web 应用里 XML BPM 引擎显得多余一样。
先退一步,忘掉框架之争,看看大家实际上在做什么。
实践中最常见的 Multi-Agent 架构(Claude Agent SDK 鼓励的、Anthropic 研究 Agent 用的、生产部署最终收敛到的)是 orchestrator + subagents:
orchestrator 决定需要做什么,生成 subagents 来执行,可能再生成更多,最后聚合结果。这对单一连贯任务没问题。问题出在团队把这种模式扩展到多个业务域时,最终得到一个 orchestrator,它的 subagents 可以触及整个系统:订单、库存、支付、物流、客服,全部。
这就是一个单体式的 Agent 系统。我们在搭建 2010 年代的企业单体架构,只是把盒子叫成「Agent」。
正确的架构:每个有界上下文有其专属 Agent,通过消息通信:
领域驱动设计十五年前就解决了这个问题。有界上下文拥有自己的模型、语言、规则、数据。如果一个上下文内部需要触发另一个上下文的副作用,不要跨边界伸手,而是发一条消息。接收方决定如何解释。这就是每一个真正能扩展的微服务架构的基本形态。
Agent 也应该遵循同样的规则。订单 Agent 操作订单,不直接修改库存、不直接扣款、不直接触发物流。当一个订单需要预留库存时,它发布 OrderPlaced(或发送 ReserveInventory),由库存上下文的 Agent 在自己的边界内处理。
这不只是架构卫生。有界上下文给了你一个正经 Agent 系统想要的一切:
关键的是,这还能让你回到成熟的编排工具上。跨 BC 的消息正是消息总线的用武之地。MassTransit、NServiceBus、Kafka、RabbitMQ 都是为这个设计的。如果你的 Agent 架构是有界上下文通过事件通信,你不需要图工作流引擎来编排它们。你只需要一条总线。
自带的「工作流引擎」做法实际上在反着来。它鼓励你把全部 Agent 放进一个工作流图里,在一个进程里,共享一个执行上下文。这就是单体的拓扑。把盒子叫成「Agent」并不改变这一点。
如果要做任何超出玩具 Demo 的东西,问题不是「我应该用什么图运行时来编排 Agent」,而是「我的有界上下文是什么,每个上下文里放哪些 Agent」。
| If you need... | Reach for |
|---|---|
| In-process state validation | Stateless (or equivalent FSM) |
| Cross-service coordination with compensations | MassTransit or NServiceBus |
| Replay through crashes, long-running tasks | Temporal or DurableTask |
| Streaming fan-out + typed joins | LangGraph or MAF Workflows |
| Multi-agent orchestration per bounded context | Message bus + your language's async primitives |
如果你需要的是数据流并行(token 级流式输出、跨节点 fan-out、streaming join)——这是图数据流唯一真正有价值的地方——那么 LangGraph 和 MAF Workflows 的 BSP 模型是合理的。这不是一条坏路,只是条窄路。
但对于大多数 Agent 应用,我要说的核心观点是:不要从框架开始,从问题开始。先搞清楚你要解决的是哪类编排问题,再看现有工具里哪个最适合。不要因为某个框架把「Agent」和「Workflow」打包在一起就默认你需要两者——模型厂自己都在告诉你:给他们好的原语,你用自己已有的工具组合。
参考文献和链接如有需要可参考原文。