Graph Engineering 解析:什么是图工程,何时该用,何时不该用

本文整理自 Anatoli Kopadze 文章《Graph Engineering explained: what it is, when to use it and when not to》

大多数人使用 AI,只发挥出了它 5% 到 10% 的能力。存在一种更快的方式,而且它比看起来的更重要。学会它,能优化庞大的流程,而不只是个人的琐碎任务。

这是大公司真实岗位背后的技能。是"做好一份工作"和"设计一百份工作如何被完成"之间的区别。

原作者 Anatoli Kopadze 很早就接触到了这套思路。他曾在丹麦一所顶尖大学就读,学校开设过一整门课,专门讲一件事:如何把一个流程画成图,并让它尽可能高效。

当时这感觉很抽象。而现在,这正是顶尖 AI 工程师们在你的时间线上争论的东西。

读完这篇文章,你对图工程的理解将超过你关注的几乎所有人:图到底是什么、那个能让你的 AI 瞬间变快的测试、那个能自己收回成本的单一模式、这些系统在哪里悄悄崩坏、什么时候图是错误的工具,以及如何在几分钟内亲手搭建一个真正的图。

1 --- 这一切从何而来

一个月前,整个领域都在谈论"循环"(Loop)。然后 Peter Steinberger 发了上面那句话,一个刚学完循环的互联网角落,一夜之间宣布循环已经过时了。

这个梗之所以成立,是因为它有一半是真的。如果你读过作者的 Loops 那篇文章,你已经具备了基础。循环就是一个智能体反复改进一件事:尝试、检查、调整、再来一次。那是上个月的技能。

而现在大家转向的,并不是一个更好的循环。而是循环组成的图------一个网络,其中多个循环互相监督、互相纠正,而不是一个智能体孤军奋战地追逐一个指标。

工程师们几小时内就对这波热度提出了质疑,指出这是一个几十年前的旧概念换了个新名字。他们是对的,而这恰恰是好消息。一个已经支撑关键系统运行了三十年的模式,正是你应该信任来处理自己工作的东西。


2 --- 图到底是什么

图,只是你 AI 工作的一份计划,被画出来让你能看见。它回答两个问题:哪些工作需要发生,以及哪个工作必须等哪个工作。

图只有两个部分,把这两个搞清楚,绝大部分困惑就解决了。

一个方框叫节点(Node)。它是一份工作:一个智能体做一件任务,一个输入,一个输出。研究一个竞品、写一份草稿、核实一个说法,都是节点。

一个箭头叫边(Edge)。它的意思是,一份工作需要另一份工作产出的结果,所以必须等待它。而且只有当真正有东西沿着这条箭头传递时,这条边才算数。

节点负责思考,边负责传递结果。这就是全部的词汇表。一旦掌握,你就再也不需要定义了。

让一个节点在图里真正可用的,是一份契约:一份有边界的工作,一个明确的输入,一个明确的输出。一个输出是一整段自由文本的节点,只有人能读懂。一个输出格式固定的节点,才能被下一个节点直接读取,而不用猜测------这才是重点。

复制代码
▸ 节点契约
任务:     研究某一个竞品的定价(一件事,仅此而已)
输入:     { competitor: "名称", url: "https://..." }   ← 传入的,从不假设
输出:     { price: 数字, plan: 字符串, source: 链接, date: "YYYY-MM-DD" }
格式约束: 强制执行。如果智能体返回自由文本,会被拒绝并重试
原因:     明确的输出,是让下一个节点无需人工介入就能读取这个节点的关键
          这正是让它能够被"接线"的原因。

3 --- 能找出假边的测试

看看你今天正在跑的 AI 工作流,一步一步走一遍。每一步都问一个问题:这一步真的需要用到前一步的结果吗?

如果是,这条边就是真的,保持顺序。如果不是,就没有边,这个等待是白白浪费的。这两份工作可以同时进行。

举个简单的例子:"审查文件 A 的 bug,然后审查文件 B 的 bug。"读起来像是一个顺序,但对文件 B 的检查从来没有用到文件 A 返回的任何结果。它们只是因为你打字时的先后顺序才依次运行。让它们并行运行,整个流程会在处理单个文件所需的时间内完成,而不是两者相加的时间。

在几乎任何一个你画出来的工作流里,你都能找到两三条这样的假边。每一条都是你在白白浪费的时间。


4 --- 流程其实已经是一张图

当你把一个智能体写成"先做 A,再做 B,再做 C,再做 D"时,你其实已经技术上画出了一张图。只不过这是最可悲的那种图:一条单一的直线链条,每个节点只有一条箭头进、一条箭头出。

它能正确运行。但它也运行得很慢,而且很脆弱,因为链条没有任何冗余。如果 C 卡住了,D 永远不会发生,而 A 的成果就被困在上游,无处可去。

图工程的第一项真正技能,就是重新画出那条链条。拿出你的线性工作流,对每一条箭头都问一遍假边问题。剪掉那些不传递数据的箭头,这条线就会收缩成更宽的东西:几份可以同时运行的独立工作,最终汇入一份需要它们全部结果的工作。

这个道理不是表面文章。一个有 40 个步骤的线性工作流,有 40 个顺序失败点,延迟是全部 40 步加起来的总和。同样这 40 份工作画成图之后,只有实际存在的依赖数量,通常是三到五个,完成速度取决于你最慢的那一层,而不是所有工作的总和。这就是同样的工作,从需要五分钟变成只需要十五秒的区别。

模型从来都不是瓶颈。是你画的那条线才是瓶颈。


5 --- 唯一值得回本的模式:菱形

你不需要一百种图形。观察任何一个成熟的智能体系统运作,同一幅画面会反复出现:工作被拆分,多个工作者并排挖掘,某个环节核查它们的发现,最后一切汇合成一个答案。

这幅画面叫做菱形(Diamond) ,它几乎是你今年唯一需要的模式。它的正式名称值得记住:分发(fan out)、归并(reduce)、综合(synthesize)

分发出去以获取广度,用普通代码归并以压缩信息,用一个最终智能体综合以写出答案。

Claude 内置的 Research 功能,在生产环境中运行的正是这一套逻辑。一个"主脑"规划角度,多个"工作者"并行搜集信息,发现被核实,然后才有一份报告送达你手中。一旦你能看见这个菱形,你就不再问"如何让我的智能体多做几步",而是开始问"分发点在哪里,汇合点在哪里"。第二个问题才是能规模化的那个。

以下是菱形在底层实际的样子。当你说出"workflow"这个词时,Claude 会自己写出一段简短的编排脚本,并把协调过程当作代码来运行,这正是为什么在智能体之间传递结果不会额外消耗上下文。

复制代码
// 一个市场扫描图------菱形,由 Claude 在你说 "workflow" 时自动写出

const angles = [
  "与前三名竞品的定价对比",
  "买家在评论中的主要抱怨",
  "该品类中的功能缺口",
  "未来12个月市场的走向",
];

// 分发(FAN OUT)------ 每个角度一名研究员,全部同时进行
const raw = await parallel(
  angles.map(a => () => agent({
    task: `research: ${a}. 每个论断都需要来源链接和日期。`,
    schema: Finding,        // 经过校验的输出,而非自由文本
    model: "cheap",         // 无需判断力的节点 → 便宜模型
  }))
);

// 归并(REDUCE)------ 普通代码,不调用模型,不消耗token
const findings = dedupeBySource(raw.flat().filter(Boolean));

// 核验(VERIFY)------ 每条发现由一个全新的怀疑者核查,试图推翻它
const survivors = await parallel(
  findings.map(f => () => agent({
    task: "尝试推翻这一条。返回 保留 | 剔除 及理由。",
    input: f,
    freshContext: true,     // 绝不复用研究员的对话上下文
    model: "strong",        // 需要判断力的节点 → 强模型
  }))
).then(v => findings.filter((_, i) => v[i].verdict === "keep"));

// 综合(SYNTHESIZE)------ 一个智能体根据幸存的发现撰写答案
return agent({ task: "撰写一份报告,按置信度排序,附上来源。",
               input: survivors, model: "strong" });

把这段代码读一遍,整套手艺就都看得见了:工作彼此独立时的分发、用免费代码完成的归并、在全新上下文中进行的核验、无需判断力的节点用便宜模型、需要判断力的节点用强模型,最后是单一的一次综合。无论是市场扫描、代码审查还是研究报告,背后都是同一套骨架。换掉角度和提示词就行。


6 --- 检查者才是整套技巧的关键

现在是几乎每个人都会跳过的部分,也正是它区分了一张真正的图和一个昂贵的玩具。

每一项关于 AI 自我审查的严肃测试都得出了同样的结论:模型会漏掉自己犯的大部分错误。一个模型给自己的作品打分,会宽松得离谱。

所以,永远不要让完成这份工作的智能体去检查这份工作。

你要在这条边上放一个独立的节点。它唯一的任务就是在这份发现被继续传递之前,尝试把它"杀死"。如果它挺过去了,就放行;如果没有,就在这里终结。

这里有一个没人明说的陷阱:这个检查者需要一个干净的上下文。

如果给它和工作者相同的对话,它就不是在检查任何东西,只是换了个字体在自我附和。一群共享同一个上下文的智能体组成的图,其实只是一个套了马甲的单一循环,会以同样的方式崩坏,只是更晚、代价更高。

所以要让核验者保持全新。拥有自己的上下文。检查的是真实信号,不是"这个智能体是不是说自己做完了",而是"这个测试是不是真的通过了"。

然后把检查拆成三路。它是否正确?它是否是最新的?来源是否真实存在?三种不同的视角能捕捉到十个相同视角捕捉不到的问题。

复制代码
▸ 核验节点
输入:     来自工作者的一条发现(只有这条发现,绝不包含工作者的对话记录)
上下文:   全新且空白。它没有看过自己正在评判的那份工作
检查项:   三个怀疑者并行运行,每个问不同的问题
  1. 它是否正确?        → 这个论断本身是否站得住脚
  2. 它是否是最新的?    → 来源是否新近,而非陈旧过时
  3. 来源是否真实?      → 链接打开后是否确实对应所引用的内容
通过:     只有当多数怀疑者都认可时,这条发现才被保留
不通过:   在它抵达最终答案之前就被剔除

需要记住的规则是:工作者和它的核验者绝不能共享上下文。一旦共享,你就又回到了一个循环给自己的作业打分的状态,只是账单更大了而已。


7 --- 图会在哪里崩坏

1. 上下文坍缩

分发出一千个节点,然后试图把这一千个输出全部塞进最后一步,还没开始综合,你就已经超出了上下文窗口。

解决办法:分层做汇合。把结果分批,对每一批做摘要,再合并这些摘要,绝不直接合并原始的一大堆数据。

复制代码
// 分层汇合------绝不把1000份原始输出一次性倒进一个步骤
const batches = chunk(results, 40);              // 每40个一组
const summaries = await parallel(
  batches.map(b => () => agent({ task: "总结这一批", input: b }))
);
return agent({ task: "根据这些摘要写出答案", input: summaries });
// 最后一步读取的是约25份摘要,而不是1000份原始输出

2. 假性独立

两个节点看起来彼此独立,因为它们的提示词从未提到对方,但它们都在写同一个文件,或者调用同一个有速率限制的 API。这是一条隐藏的边。

当 Bun 团队第一次把一个大任务分发给多个智能体时,它们共享了同一个工作空间,结果互相覆盖了对方的成果。

解决办法:给每个工作者独立隔离的空间,并审查共享资源,而不只是审查共享数据。

复制代码
// 隔离工作者------不共享文件,不共享工作空间
await parallel(files.map(f => () => agent({
  task: `重构 ${f}`,
  worktree: true,        // 每个智能体在自己的 git worktree 中工作
})));
// 它们无法互相覆盖,之后结果再干净地合并
// 规则:任何两个写入同一文件的节点,需要的是一条边,而不是并行

3. 节点静默失败

在链条中,一次失败会让一切停摆------恼人,但至少显而易见。在图中,两百个节点里死掉一个,可能悄悄混入一份看起来完整的报告里。

解决办法:每个汇合步骤都要把实际收到的输入数量,与预期数量对比,发现缺口就标记出来,而不是悄悄地只用一半数据继续运行。

复制代码
// 汇合防护------捕捉悄悄死掉的节点
const results = (await parallel(jobs)).filter(Boolean);   // 死掉的节点 = null
if (results.length < jobs.length) {
  flag(`警告:${jobs.length} 个节点中有 ${jobs.length - results.length} 个没有返回任何结果`);
}
// 绝不在部分数据集上做综合,还宣称报告是完整的

8 --- 你真的需要一个图吗

按照作者一贯的传统,来诚实地判断一下,这套东西究竟对谁有用。

一张图买到的是广度,买不到更好的判断力。

它是一个用于"宽度"的工具,用于同时进行的独立工作。当工作本身并不宽的时候,那条线从来就不是问题所在。

以下情况请跳过图:

  • 任务很小或是孤立的。 加一个函数、修一个 bug。协调本身就是纯粹的开销,单个智能体更快也更便宜。
  • 你想审批每一步。 图的整个意义就在于不需要你就能大范围运行,所以紧盯着的方式和它的初衷是相悖的。
  • 你还不知道自己在找什么。 探索性的工作需要一个你可以引导的智能体,而不是一支被锁定在计划里的舰队。
  • 各步骤之间确实互相依赖。 把图硬套在真正的顺序性工作上,只会增加成本,却带不来任何加速。
  • 判断标准就是假边测试。 如果你找不到两份工作之间没有边,那就没有图可建。这就是一个循环,而循环没问题。

9 --- 没人愿意听的部分:锚点

这里有一个更深的陷阱,也是这整场变革真正的教训。

想象你搭建了一整张图。成对的检查者、审计节点、调优其他节点的元节点。每个节点都在监视另一个节点,每一个都在读同一份报告。

审计节点拿数字去核对财务数字,而这些数字最初就来自同一个系统。

一切看起来都是一致的。但什么都没有被真正核实。

这张图会以和当初那个单一循环一样的方式失败,只是更晚、代价更高,而且下坠的路上还亮着更多的绿灯。

拓扑结构本身买不到真相。这张图需要锚点:那些无法被辩驳的节点。

真正跑过的测试,而不是"应该会通过",是"确实通过了"。真正到账的收入,真正留下来的客户。

而且有些规则必须被冻结------正是那些优化器会想要削弱的规则------因为它们恰恰就是优化器为了取胜而会去弯曲的那些,所以必须划为禁区。

一张图的诚实程度,取决于它内部那些拒绝被移动的东西。

用无法反驳的数字去评判它,它就会保持接地气。让它给自己的报告打分,它就会自信满满地犯错。


10 --- 在 Claude Code 中亲手搭建一个

理论说得够多了。如果你已经决定这适合你,或者只是想试试看,那就动手搭一个。你可以在几分钟内搭建一个真正的图,因为 Claude Code 已经直接发布了实现这个功能的工具,叫做动态工作流(dynamic workflows)

一切归结为一个词:"workflow"。

把它放进你的提示词里,Claude 就不再按照一条单一的步骤线索去工作,而是会写出一段简短的编排脚本,然后生成一支协调好的子智能体舰队去执行它。

关键点在于,协调是代码,而不是对话。在智能体之间传递结果,不会像聊天式的交接那样重新消耗你的上下文,这正是一次运行能够扩展到一整支舰队、而不会淹没会话的原因。

打开一个你熟悉的真实代码仓库,粘贴这个:

复制代码
▸ 图规格说明
目标: 审查 src/routes/ 下的每一个路由文件,检查是否缺少身份验证

分发:      每个文件一个智能体,全部并行运行
核验:      对每一条发现进行独立检查,使用全新上下文
上限:      本次首轮运行限制20个文件
失败处理:  标记出任何没有返回结果的文件,绝不悄悄跳过
报告:      一份合并后的列表,列出所有缺少身份验证的路由

(提示词以"workflow"这个词开头,Claude才会构建这张图)

运行它,接下来会发生这些。

首先,Claude 会表明它正在构建一个工作流,而不是像普通对话那样直接回答,并且会在动手之前把计划展示给你看。你阅读并批准。

然后,这支舰队开始运行。每个文件一个智能体,全部同时进行,而你自己的会话在整个过程中始终保持空闲。

最终呈现的,不是二十份需要你自己去翻阅的独立对话,而是一份报告。中间的结果都留在了脚本内部,从未进入你的上下文,所以你实际看到的只有最终答案。

那就是一张图。一句话,一支舰队。当一次运行效果不错时,把它保存下来,它就会变成一个你可以按名字永久重复运行的命令。

注意那个提示词里"20个文件"的上限。它让你的第一次运行成本可控,也暗示了每个演示都会略过的东西:账单。


11 --- 可以直接粘贴使用的图模板

以下每一个,都是同一个菱形对准了不同的工作目标。在一个真实文件夹中打开 Claude Code,把方括号里的部分换成你自己的内容,然后粘贴进去。"workflow"这个词是告诉 Claude 去构建一支协调好的舰队,而不是一条单一的步骤线索。让你自己始终保持在任何东西上线之前的最后一道批准。

决策级研究台。 取代一整周的谷歌搜索,或一份昂贵的分析师报告。你的问题被拆成多个角度,研究员同时深入挖掘,一个怀疑者攻击每一条发现,只有幸存者能进入报告。

复制代码
▸ 图规格说明
目标: 针对[你的问题]进行决策级研究

分发:        拆分成5个不同角度,每个角度一名研究员,并行进行
规则:        每条发现都需要来源链接和日期
核验:        一个怀疑者攻击每条发现,试图推翻它,未通过的剔除
汇合:        幸存者合并为一份报告,按置信度排序
保存:        research-report.md,然后向我展示排名最高的发现
人工把关:    在此之后,未经我同意不得改动任何内容

(提示词以"workflow"这个词开头,Claude才会构建这张图)

SEO内容生产线。 每次运行产出一份可冲榜的草稿,且从不会未经你同意就发布。

复制代码
▸ 图规格说明
目标: 针对[主题]产出一份可冲榜的草稿

并行任务(同时进行):
  1. 当前排名靠前的页面覆盖了哪些内容
  2. 人们关于这个主题真正会问的问题
  3. 那些排名靠前的页面遗漏了什么
汇合:        三者合并成大纲,然后撰写完整草稿
核验:        一个事实核查者,标记出所有没有来源支撑的论断
保存:        drafts/ 目录,被标记的论断列在最前面
人工把关:    绝不发布任何内容

(提示词以"workflow"这个词开头,Claude才会构建这张图)

上市物料包。 一次运行产出完整的发布物料包,每一份都经过你的批准。

复制代码
▸ 图规格说明
目标: 为[产品]、面向[受众],产出完整的发布物料包

并行任务(研究,同时进行):
  1. 描绘买家画像及其真实使用的语言
  2. 定位这些买家活跃的线上渠道
  3. 收集竞品是如何向他们推销的
汇合:        整合成一份定位文档
人工把关:    暂停,向我展示定位文档后再开始撰写
并行任务(撰写,基于该文档):
  1. 落地页文案
  2. 一周的发布帖文
  3. 一组外联信息
核验:        一个检查者将每份物料与定位文档对照,标记出偏离之处
保存:        launch-kit/ 目录,此后未经我同意不改动任何内容

(提示词以"workflow"这个词开头,Claude才会构建这张图)

全仓库重构扫描。 单一上下文无法容纳的广度。

复制代码
▸ 图规格说明
目标: 找出所有超过100行的函数,并为每一个提出重构方案

分发:      每个文件一个智能体,并行运行
核验:      对每个提出的重构方案进行独立检查,使用全新上下文
去重:      与已看过的方案进行比对
上限:      本次首轮运行限制50个文件
报告:      返回结果的文件数量,确保没有任何失败被悄悄跳过

(提示词以"workflow"这个词开头,Claude才会构建这张图)

规模未知的探索式循环。 适用于那些在深入之前不知道工作量有多大的任务,比如一次 bug 排查,发现一个 bug 就可能牵出另外三个。

复制代码
▸ 图规格说明
目标: 在这个代码仓库中排查[安全问题 / 错误处理缺陷 / 死代码]

分发:      并行运行多个查找器
去重:      将每条新发现与已发现的内容对比
核验:      对幸存下来的发现进行独立检查
循环:      持续进行,直到连续两轮都没有新发现,然后停止
上限:      设置智能体总数的硬性上限,防止失控
报告:      按严重程度排序的最终列表

(提示词以"workflow"这个词开头,Claude才会构建这张图)

先限定范围小规模运行一次,观察成本,再逐步放宽。当一次运行效果不错时,把它保存下来,它就会变成一个你可以按名字直接启动的命令。


12 --- 成本与监督

一张图比一次普通对话的成本更高,而且高很多。变便宜的是协调本身,不是工作量本身。智能体们仍然在消耗 token,一支舰队消耗的是一大堆 token。

最清晰的例子是公开的。一名工程师用这套完全相同的方式重写了 Bun 运行时,把大约 53.5 万行一种语言的代码,翻译成超过 100 万行另一种语言的代码,耗时约 11 天。如果靠人工,这接近一年的工作量。

它运行了约 50 次工作流,最多同时有 64 个智能体在运行。

但这也花费了大约 16.5 万美元的使用成本,需要一个人全程设计和监督整个过程,而且这些数量庞大的 AI 生成代码是否能被安全地审查,也招来了真实的质疑。

这就是这件事诚实的样子。一张图可以分发给上千个智能体,啃下单一上下文永远无法容纳的工作量。它也可能在你把它指向错误的任务、或者跳过锚点的时候,悄悄地在后台花掉你的钱。

所以,重量级版本是给那些拥有预算、有上限设置、有监控能力去运行它的团队准备的。如果你现在还不是这样的团队,你并没有错过什么。从小处开始,观察一次运行的成本,只有在赢得了信任之后,才逐步扩大规模。


13 --- 这对你而言究竟意味着什么

这就是全貌了。你现在知道了图是什么、它在哪里有优势、它在哪里会崩坏,以及它到底适合谁。

你知道了它的长处:广度,同时进行的独立工作。也知道了它的弱点:它买到的是宽度,不是判断力,而且如果你把它指向错误的工作,它会花掉你的钱。

所以真正该做的,不是把一切都变成图,而是判断这份工作是否足够"宽",值得为它建一张图;还是说,一个简单的循环从始至终就是正确答案。

作者的建议是:可以马上就去学假边测试。画出你现在的工作流,找出那些不传递数据的边,把它们删掉。仅这一个动作,就能让你在碰任何新工具之前,比大多数人更快。

大多数人会继续把步骤排成一条队列。少数学会画图的人,将会驾驭一支舰队。

相关推荐
析数塔1 小时前
DuckDB 2.0 Cyanoptera 来了:脚本里的 OLAP,现在可以开成服务了
数据库
lv__pf1 小时前
redis缓存数据库进阶
数据库·redis·缓存
鸽芷咕1 小时前
告别手工分表:金仓时序数据库超表架构落地的一次实战复盘
数据库
草莓熊Lotso1 小时前
【Redis 初阶】Hash 类型深度解析:结构化数据存储的最优解
linux·网络·数据库·redis·tcp/ip·缓存·哈希算法
nvd111 小时前
K3s + ArgoCD 中的密码管理
数据库·oracle·argocd
Wang's Blog2 小时前
PostgreSQL笔记62: 分区表维护最佳实践——默认分区、锁策略与性能调优
数据库·笔记·postgresql
熊出没2 小时前
解密数仓中的ODS、DWD、DWS、ADS
数据库·数据仓库
梦想不只是梦与想2 小时前
MySQL 不同操作系统的安装方式
数据库·mysql·mysql安装
熊文豪2 小时前
向量数据库单独建一套,这笔账划不划算
数据库