如果 workflow 已经可以包含分支、循环、并行和多个 Agent,为什么还要再造一个叫 Graph engineering 的词?
顺着这个问题继续想,很容易得到一个看似合理的答案:普通 workflow 由工程师写好,Graph engineering 则让 Agent 自己决定图,工程师只负责补上严苛约束,防止它失控。
这个理解已经摸到了“动态路径”和“约束”的重点,但还差两步。第一,图不一定由 Agent 决定;第二,工程师的工作也不只是列禁止事项。真正需要工程化的是一整个执行空间:有哪些节点,状态怎样流动,谁有权选择哪条边,失败后回到哪里,以及什么条件下必须停止。
数据快照
- 最后核验:2026-07-30
- 术语来源:LangChain,2026-07-22
- 主要实现参考:Claude Code Dynamic Workflows,要求 v2.1.154 或更高版本、LangGraph v1 文档
- 比较边界:本文讨论的是 Agent 的执行图与编排,不是知识图谱(Knowledge Graph)
#先说结论:人设计变化空间,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 API把 State、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 的概念对比;这是本文的分析性拆解,不是某个框架的官方术语表
| 维度 | Workflow | Graph 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 变量承担状态传递,if 和 while 则分别对应条件边与循环边。
表 2:Claude Code Dynamic Workflow 元素到 Graph 元素的映射;依据 2026-07-30 核验的官方文档
| Dynamic Workflow 元素 | Graph 视角 |
|---|---|
agent(prompt) | Agent 节点 |
pipeline(items, fn) | 运行时 fan-out |
| 等待批量结果 | fan-in / join |
if | 条件边 |
for、while | 循环边 |
| JavaScript 变量 | 运行中的状态 |
| 汇总 Agent | reducer 或 aggregation 节点 |
| workflow script | code-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 可以为当前任务生成局部软约束,例如“至少检查鉴权、输入校验和权限继承三条证据链”,也可以在证据不足时建议扩大搜索。但它不能自行提高预算、放宽工具权限,或取消原本要求的人工审批。执行者不能同时成为规则制定者和最终裁判。
#从最小约束集开始,再让失败轨迹补全它
约束不可能在第一次设计时全部列完。它通常来自四个地方:
- 业务规则和不可违反的红线;
- 系统架构与工具本身的限制;
- 已知事故、失败案例和人工复盘;
- 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 时,不妨先别急着画一张更大的图。先给每个节点填完八问卡,再给整张图补上预算、停止、不确定性和人工审批。能回答这些问题,才算真正从“流程能跑”走到了“系统可控”。