/ #agent #工作流 #graph engineering

Graph engineering 比 workflow 多了什么?不是自由画图,而是工程化执行空间

从 Claude Code Dynamic Workflows 与 LangGraph 的运行机制出发,解释 Graph engineering 和 workflow 的真实边界,以及怎样系统地设计节点、状态、路由、异常与约束。

如果 workflow 已经可以包含分支、循环、并行和多个 Agent,为什么还要再造一个叫 Graph engineering 的词?

顺着这个问题继续想,很容易得到一个看似合理的答案:普通 workflow 由工程师写好,Graph engineering 则让 Agent 自己决定图,工程师只负责补上严苛约束,防止它失控。

这个理解已经摸到了“动态路径”和“约束”的重点,但还差两步。第一,图不一定由 Agent 决定;第二,工程师的工作也不只是列禁止事项。真正需要工程化的是一整个执行空间:有哪些节点,状态怎样流动,谁有权选择哪条边,失败后回到哪里,以及什么条件下必须停止。

数据快照

#先说结论:人设计变化空间,Agent 只决定这一次怎样走

Graph engineering 不是 workflow 的反义词,也不是“让 Agent 自己画图”的简称。

更准确的理解是:

Workflow 描述任务怎样执行;Graph engineering 则把节点、边、状态、约束、失败恢复和评测一起当成可设计、可执行、可观察的工程对象。

一个 Graph 可以完全固定,也可以只有路由由 Agent 决定,还可以让 Agent 在运行时生成部分子任务。无论动态到什么程度,外层通常仍有一张由工程师定义的“元图”:

允许创建哪些节点
→ 允许连接到哪里
→ 可以读取和修改哪些状态
→ 最多运行多少次
→ 什么时候必须验证、停止或交给人

所以,把职责压缩成一句话就是:

工程师设计“允许怎样变化”,Agent 决定“这一次具体怎样走”,Runtime 保证“再怎么走也不能越界”。

这里的 Runtime 不只是一段循环代码。它还可能包括 schema 校验、计数器、权限系统、沙箱、checkpoint、重试策略和人工审批。

#Graph engineering 是新概念,还是 workflow 换了名字

“Graph engineering”作为 Agent 圈的新说法确实很新。LangChain 在 2026 年 7 月 22 日的文章中直言,这个词来自社交媒体的又一轮命名热潮,带有 buzzword 成分;但它描述的问题真实存在:开发者需要在确定性路径和模型自主决策之间找到平衡。LangChain 对它的定义是把 Agent 系统表示为图,让构建者能够在需要时把行为限制在更明确的路径中。

图本身当然不新。DAG、状态机、工作流引擎、重试队列和人工审批早就存在。相对新的地方,是图里的一个节点现在可以不只是函数或单次 LLM 调用,而是一个带工具、记忆和内部循环的完整 Agent。LangChain 也把这点视为本轮讨论真正发生的变化。

用最小模型表示,一张 Agent 执行图包含四部分:

Graph = Nodes + Edges + State + Runtime semantics
  • Node 执行工作,可以是代码、工具、一次模型调用、完整 Agent 或人工节点。
  • Edge 决定下一步,可以固定,也可以依据状态、模型判断或外部信号动态选择。
  • State 保存跨节点共享的信息,并规定不同更新怎样合并。
  • Runtime semantics 规定并发、重试、超时、checkpoint、恢复、权限和停止条件。

LangGraph 的 Graph APIState、Node 和 Edge 暴露为一等公民,并通过 reducer 定义并行或连续更新怎样写回状态。但这只是显式图的一种实现。OpenAI 的 Agent 指南也区分了声明式 Graph 和 code-first 编排:前者预先定义节点和边,后者使用普通编程语言的分支、循环与函数表达同一套控制流。显式 Graph API 和隐式代码图都成立,差别在表达与治理方式,不在有没有一张可视化画布。

#Workflow 和 Graph engineering 的差别不在“能不能分支”

Anthropic 在《Building Effective AI Agents》中曾给出一个很窄但好用的区分:

  • workflow 通过预定义代码路径编排 LLM 和工具;
  • agent 由 LLM 动态决定自己的过程和工具使用。

但同一篇文章也把 routing、parallelization、orchestrator-workers 和 evaluator-optimizer 都列为 workflow。特别是 orchestrator-workers,子任务本来就是运行时由中央 LLM 动态拆出来的。可见 workflow 并不天然等于“线性固定步骤”。

Graph engineering 和 workflow 真正的差别,不是前者有分支、后者没有,而是观察角度不同。

表 1:Workflow 与 Graph engineering 的概念对比;这是本文的分析性拆解,不是某个框架的官方术语表

维度WorkflowGraph engineering
主要问题任务按什么顺序执行执行空间怎样设计和治理
基本对象步骤、条件、循环、并行任务节点、边、状态、Runtime 语义
动态性可以固定,也可以运行时变化可以固定,也可以运行时变化
Agent 的位置可以没有 Agent,也可以由 Agent 编排Agent 可以是节点、路由器或子图生成器
状态可能只是步骤间变量通常显式考虑 schema、写权限、合并和持久化
异常常按任务或步骤处理还要定义失败类型、恢复边和停止边
质量关注最终任务是否完成同时关注节点、路由、状态转移和最终结果
产物一段流程或可执行程序图结构及其约束、状态契约、运行策略和评测

一条线性 workflow 本来就是路径图;带条件的 workflow 是 DAG 或状态机;带重试的 workflow 是有环图;一个 Agent 不断调用工具,则是一个循环子图。

因此,从 workflow 走向 Graph engineering,通常不是把现有流程推倒重来,而是把藏在代码和 Prompt 里的结构显式拿出来问:

  • 这条边为什么存在?
  • 谁能决定走这条边?
  • 进入下一个节点之前,哪些条件必须成立?
  • 两个并行节点同时修改状态时,结果怎样合并?
  • 失败后重试当前节点、回到规划节点,还是直接结束?
  • 这条路径怎样被测试和观察?

#Claude Code Dynamic Workflows 已经是一张隐式图

Claude Code 的 Dynamic Workflow 很适合解释“workflow 和 graph 并不对立”。

按照当前的官方文档,Claude 会先针对任务生成一段 JavaScript,之后由独立 Runtime 在后台执行。脚本中的 agent() 启动一个 subagent,pipeline() 按输入集合批量分发任务,中间结果保存在脚本变量中,而不是全部回到主对话上下文。

官方对这套设计的概括很直接:把 plan 移进 code。下一步运行什么,不再由主 Agent 每一轮临场判断,而由脚本控制。

下面是一段与官方示例同构的示意代码:

const files = await agent('找出所有路由文件');
const findings = await pipeline(files, (file) => agent(`审查 ${file} 的鉴权问题`));
return findings.filter(Boolean);

图 1:Dynamic Workflow 中的 agent()pipeline() 与变量形成 fan-out / fan-in 隐式图

flowchart LR
  A["发现文件"] --> B1["审查 Agent 1"]
  A --> B2["审查 Agent 2"]
  A --> BN["审查 Agent N"]
  B1 --> C["汇总结果"]
  B2 --> C
  BN --> C

图中没有 add_node()add_edge(),但结构并没有消失:agent() 是节点,pipeline() 是动态 fan-out,等待所有结果是 fan-in,JavaScript 变量承担状态传递,ifwhile 则分别对应条件边与循环边。

表 2:Claude Code Dynamic Workflow 元素到 Graph 元素的映射;依据 2026-07-30 核验的官方文档

Dynamic Workflow 元素Graph 视角
agent(prompt)Agent 节点
pipeline(items, fn)运行时 fan-out
等待批量结果fan-in / join
if条件边
forwhile循环边
JavaScript 变量运行中的状态
汇总 Agentreducer 或 aggregation 节点
workflow scriptcode-first 的隐式图定义
workflow runtime图的执行器

这说明 Dynamic Workflow 可以承载 Graph engineering,但不等于一套通用、持久的 Graph 平台。当前文档列出的边界包括:

  • workflow 脚本不能直接访问文件系统或 Shell,实际操作由 subagent 完成;
  • 单次运行最多并发 16 个 Agent,累计最多 1000 个;
  • 除权限提示外,不支持普通用户在运行中输入;
  • 暂停后的恢复只在同一个 Claude Code session 内有效,退出后需要重新开始。

这里可以得到一个分析性结论:

Claude Dynamic Workflow 是 code-first、agent-oriented 的隐式 Graph Runtime;它把编排从主上下文迁到了代码,但没有把状态 schema、跨 session 持久化和人工中断都提升为通用图原语。

这不是说它“不完整”,而是适用目标不同。它优先解决的是本地大型任务的即时编排、上下文隔离和可复用脚本,不是运行几天甚至几个月的业务流程。

#图不一定由 Agent 决定,它有三种常见动态程度

把“Graph engineering 等于 Agent 自己生成图”当成定义,会漏掉大量最常用的系统。

#固定图:Agent 只在节点内部工作

输入 → 搜索 → 分析 → 验证 → 输出

节点和边全部由代码确定。Agent 可以负责“分析”节点,但没有权力跳过验证,也不能临时调用图外节点。这种图和传统 workflow 很接近,价值主要来自显式状态、恢复和观察。

#动态路由图:Agent 在合法边中选一条

┌→ 普通分析 → 输出
输入 → 分类 Agent ┤
└→ 高风险分析 → 人工审批

Agent 负责语义分类,但可以选择的路径仍由工程师预先定义。分类置信度过低时去哪里,也应当是一条明确的 fallback 边,而不是让 Agent 假装确定。

#动态子图:Agent 决定本次创建多少工作

Planner → 动态创建 N 个调查任务 → 汇总 → 验证 → 输出

这里 N 和每个任务的内容由 Planner 根据输入决定,但外层仍有稳定骨架。LangGraph 使用 Send 支持这种运行时动态路由;Anthropic 的 orchestrator-workers workflow 也采用相同思想:子任务无法预先枚举,但“拆分—执行—汇总”的结构仍然已知。

图 2:动态子图仍被固定元图包围,Planner 只能实例化允许出现的工作

flowchart LR
  I["输入"] --> P["Planner"]
  P --> W1["Worker 1"]
  P --> W2["Worker 2"]
  P --> WN["Worker N"]
  W1 --> M["证据汇总"]
  W2 --> M
  WN --> M
  M --> V{"Validator"}
  V -- "通过" --> O["输出"]
  V -- "证据不足" --> P
  V -- "高风险动作" --> H["人工审批"]

这张图真正重要的不是 N 个 Worker,而是 Planner 的外边界:最大 Worker 数量、允许调用的工具、状态写入范围、回到 Planner 的次数,以及高风险动作不能绕过人工审批。

#工程师不只提供约束,还要分配决策权

如果把工程师的工作概括成“提供约束”,容易只想到 max_iterations: 3 和“禁止删除生产数据”。这些当然重要,但 Graph engineering 至少还包含五类设计工作。

第一类是任务分解。哪些工作应该成为独立节点,哪些应该留在一个 Agent 的内部循环?节点过粗,状态和失败难以观察;节点过细,编排成本又会吞掉收益。

第二类是状态契约。节点读什么、写什么,原始证据是否只可追加,并行更新怎样去重,冲突结论能不能互相覆盖。LangGraph 用 State schema 和 reducer 处理这类问题;即使不用 LangGraph,也需要在自己的数据结构中回答同样的问题。

第三类是决策权分配。格式是否正确适合交给 schema;数值是否超过阈值适合交给代码;证据是否相关可以交给 Judge;是否允许生产写入必须由权限系统或人决定。不是每一条边都应该让模型投票。

第四类是失败语义。API 超时、Agent 返回非法 JSON、证据冲突和权限不足,虽然都叫“失败”,但恢复路径完全不同。Graph engineering 要把这些路径设计出来,而不是只写一个笼统的 retry。

第五类是观察与评测。最终报告通过不代表路由合理;某个节点正确也不代表整条路径成本可接受。需要分别观察节点输出、实际路径、状态变化、重试次数和最终任务成功率。

所以,约束只是其中一部分。更完整的说法是:

工程师负责把业务目标变成一组可执行的职责、状态、决策权、失败语义和验收标准。

#约束不是靠灵感罗列,而是从图的五个切面推出来

面对一张新图,试图一次性想全所有异常几乎不可能。更实用的办法是先画最小图,再逐项检查 Node、Edge、State、Runtime 和 Completion。

约束又可以分为两种目的:

  • 防失控约束:避免越权、死循环、成本爆炸和不可逆破坏。
  • 保质量约束:规定什么情况下才允许继续或完成。

前者回答“不能出什么事”,后者回答“什么才算做对”。只做前者,系统可能安全地输出废话;只做后者,系统可能为了得到好结果而无限重试。

表 3:从 Graph 的五个切面盘点约束;问题栏可以直接用作设计评审清单

切面需要回答的问题约束示例
Node职责是什么?输入前提是什么?能读写什么?怎样算完成?日志节点只写 log_evidence;缺少服务名则拒绝运行
Edge谁决定路由?进入条件是什么?失败后去哪?最多回来几次?覆盖率低于 0.8 返回补查;最多回到 Planner 两次
State数据从哪里来?谁能覆盖?并行结果怎样合并?是否持久化?原始证据只追加;冲突结论同时保留
Runtime / Tool超时、重试、并发、预算和权限怎样限制?最多 5 个并行 Agent;生产工具默认只读
Completion什么条件下才能结束?何时必须承认不确定?严重发现必须附证据;关键来源不可用时不得给确定结论

#用失败模式反推遗漏的约束

完成五个切面的第一轮盘点后,再问一句:每个节点最可能怎样失败?

可以使用一个简化版 FMEA(Failure Mode and Effects Analysis,失效模式与影响分析)。不必追求复杂评分,先把失败、后果、发现方式和恢复路径连起来。

表 4:代码审查 Agent Graph 的简化失败模式表;场景为多文件鉴权审计

对象失败模式后果如何发现预防约束失败后处理
Planner拆出过多文件任务Token 与耗时失控监控任务数和预计成本最多 20 个任务,超出时分批合并低风险目录
审查 Agent没读项目规则就下结论大量误报检查输出中的规则来源输入必须包含生效的项目规则退回重审
汇总节点用后一条结论覆盖前一条冲突证据丢失检查 evidence ID原始证据 append-only标记冲突并进入复核
Validator同模型投票形成一致性幻觉错误结论被多数通过对照测试或源码证据高风险项必须有确定性证据降级为“未验证”
整张图验证失败后无限循环资源耗尽iteration counter最多重规划两次输出部分结果与不确定项

这张表会自然生成约束。比如发现“状态覆盖导致证据丢失”,约束就不该写成“汇总 Agent 应谨慎”,而应该把原始证据字段设计为只追加,并由 reducer 或存储层拒绝覆盖。

#一条约束要写成可执行的五元组

“Agent 应谨慎使用生产工具”不算可执行约束。它没有说明什么时候算生产操作,也没有说明违反后由谁阻断。

一条完整约束至少包含:

适用范围 + 触发条件 + 必须行为 + 违反后处理 + 强制执行者

例如:

id: TOOL-007
scope:
node: remediation-agent
tool: rollback
condition:
environment: production
required_behavior:
- generate_dry_run_plan
- obtain_human_approval
on_violation:
action: deny_tool_call
enforcement:
mechanism: runtime_permission_gate

这条规则可以被测试:构造一个没有 approval ID 的生产回滚请求,确认 Runtime 会拒绝,而不是观察 Agent 有没有“记得谨慎”。

#约束必须放到真正能执行它的层

不是所有规则都应该写进 Prompt。Prompt 可以影响模型行为,却不是可靠的权限边界。

表 5:约束类型与适合的执行层;越接近不可逆副作用,越不能只依赖模型遵守

约束类型适合的执行层示例
操作方法与偏好Prompt、Skill先读取项目规则,再审查代码
输出结构JSON Schema、类型系统每条发现必须包含文件和证据
数量、阈值、预算Runtime 计数器与代码最多重规划两次
状态合并reducer、数据库约束原始证据只追加
工具与资源权限IAM、沙箱、allowlist禁止 Agent 写生产数据库
语义质量Judge、Verifier证据是否真正支持结论
高风险决策Human gate发布、回滚、删除

这里有一条很朴素的原则:

能用代码判断,就不要交给模型;能用权限系统阻断,就不要只在 Prompt 中提醒。

Agent 可以为当前任务生成局部软约束,例如“至少检查鉴权、输入校验和权限继承三条证据链”,也可以在证据不足时建议扩大搜索。但它不能自行提高预算、放宽工具权限,或取消原本要求的人工审批。执行者不能同时成为规则制定者和最终裁判。

#从最小约束集开始,再让失败轨迹补全它

约束不可能在第一次设计时全部列完。它通常来自四个地方:

  1. 业务规则和不可违反的红线;
  2. 系统架构与工具本身的限制;
  3. 已知事故、失败案例和人工复盘;
  4. Eval、红队测试和真实运行轨迹中暴露的新问题。

因此,Graph engineering 不是写完一份 YAML 就结束,而是一个持续循环:

设计最小图与最小约束
→ 运行 Eval
→ 观察失败路径
→ 归类失败模式
→ 增加约束或恢复边
→ 增加回归用例

每新增一条约束,最好同时新增一个能触发它的测试。否则它很容易从“系统保证”退化成“文档里写过”。

实际落地时,可以先给每个节点填一张八问卡:

1. 这个节点只负责什么?
2. 运行前必须具备什么?
3. 可以读取什么?
4. 可以修改什么?
5. 输出 schema 是什么?
6. 最可能怎样失败?
7. 失败后走哪条边?
8. 谁来验证它成功?

然后再为整张图补七个问题:

1. 最多运行多久?
2. 最多循环和重规划几次?
3. Agent 可以创建或调用哪些节点?
4. 哪些动作必须人工批准?
5. 什么条件下算完成?
6. 什么条件下必须承认“不确定”?
7. 如何 checkpoint、恢复和审计?

这 15 个问题不会覆盖所有生产异常,但足以把“凭感觉编排”推进到可测试的第一版。

#什么时候不要做 Graph engineering

Graph engineering 最大的风险不是图不够复杂,而是图太复杂。

如果任务只有两三个固定步骤,一个 Agent 加清晰工具已经能稳定完成,或者失败后整体重跑的成本很低,那么显式设计大量节点、状态字段和恢复边只会增加调试负担。

LangChain 在介绍 Graph engineering 时也明确提醒,有些任务天生更开放,把它们强行塞进确定性路径反而不合适。Anthropic 的建议更直接:从能工作的最简单方案开始,只有当复杂性可以证明改善结果时,才增加多步 Agent 系统。

可以用下面几个信号判断是否值得升级:

  • Prompt 已经塞满跨阶段的 if/else 和重复状态说明;
  • 不同失败类型需要不同恢复路径;
  • 有明显的并行收集、汇聚和交叉验证;
  • 任务需要暂停、恢复或人工审批;
  • 必须限制 Agent 的工具、预算和状态写入;
  • 只看最终答案已经无法定位质量问题;
  • 同一套编排需要长期复用和回归测试。

如果这些问题都不存在,普通 workflow 甚至单 Agent loop 通常已经够用。

#总结

Workflow 和 Graph engineering 不是新旧替代关系。很多 workflow 本来就是图,只是图结构藏在 JavaScript、Python、YAML 或主 Agent 的临场决策里。

Graph engineering 真正增加的,是把这些隐含问题变成显式工程对象:节点职责、合法边、状态契约、决策权、失败恢复、资源边界和验收标准。Agent 可以选择路径,也可以动态生成部分工作,但它的自主性应该发生在工程师定义的执行空间内。

所以,工程师并不只是“提供约束”。工程师要先决定哪些地方值得让模型判断,哪些地方必须由代码判断,哪些副作用必须由 Runtime 或人阻断。约束只是这套决策分工的可执行表达。

下一次设计 Agent workflow 时,不妨先别急着画一张更大的图。先给每个节点填完八问卡,再给整张图补上预算、停止、不确定性和人工审批。能回答这些问题,才算真正从“流程能跑”走到了“系统可控”。

#参考资料

Share this post