使用Claude进行Graph-Engineering-14步路线图
使用 Claude 进行 Graph Engineering:从 0 到成为图架构师所需的 14 步路线图
大多数人构建多步骤 Agent,最后都得到一个直线流程:第一步、第二步、第三步——每一步都礼貌地等前一步跑完才开始。然后十分之九的人会意识到,其中一半的步骤根本不需要等待。
它们不会路由、不会分支、不会并行,只是排队:一个头部、一个上下文、一次只做一件事,直到窗口被填满、Agent 忘记自己在干什么。
真正的转变很少有人说破:提示词是一句话,循环是一个周期,调用框架是 Agent 站的地板。但工作本身的形状——什么可以先跑、什么能并行、什么必须等所有东西就绪——这个形状是一个图。
节点负责思考,边承载结果。而 Claude Code 的动态工作流(dynamic workflow)就是直接画这张图的工具:Claude 写一段纯 JavaScript 编排脚本,生成一个协调的子 Agent 舰队去执行它——协调本身花 0 个模型 Token,因为它是代码,不是对话。
一、先建立世界观:节点和边
一个图只有两种东西,想清楚它们能解决大部分困惑:
- 节点(Node) 是一个工作单元——一个 Agent、一个有边界的任务、一个输入、一个输出。
- 边(Edge) 是一个依赖关系:它声明「这个节点的输出,提供给那个节点的输入」。仅此而已。
最常见的错误,是把「然后」当成一条边。「总结文件,然后告诉我天气」——这两者之间没有边,因为天气不需要消费总结。这是两个断开的节点,被线性脚本强行串在了一起。只有数据真正通过边流动时,边才存在。
给自己立一条规矩:每遇到一个「然后」,问一句——下一步真的读取了上一步的输出吗?如果不是,就没有边,等待就是浪费。
画图就画方框和箭头:一个方框是一次 agent() 调用,一个箭头是一个变量——从一次调用的返回值,传到下一次调用的提示词里。画不出箭头(没有变量传递),两个方框就是独立的;而独立性,正是后面要利用的东西。
这是一条包含 14 个步骤的路线图,它可以将那单行线转化为一个图形结构。这个图形可以扩展到整个舰队范围内,验证自身的发现结果,并最终得出一个单个体永远无法达到的完美结果。
二、14 步路线图
01 · 节点是任务,边是流动的东西
如前所述。节点有边界、有输入有输出;边是数据依赖。把「然后」和「边」区分开,是图工程的第一课。
02 · 你的线性脚本是一个退化的图
当你把 Agent 写成「做 A,然后 B,然后 C,然后 D」,你确实画了一个图——一条无分支的单链,每个节点恰好一条入边一条出边。它能跑,但慢且脆弱:链条没有冗余,C 卡住 D 永远不会发生,A 的工作困在上游无处可去。
图工程的第一项真功夫是重画链条:拿你的线性 Agent,对每条箭头问步骤 01 的问题。大多数链条有两三条箭头不携带数据——它们只是你碰巧键入的顺序。砍掉这些箭头,链条就塌成更宽的东西:几个独立节点可同时跑,再汇聚到一个需要它们全部结果的节点上。
03 · 给每个节点一个契约
一个你无法推理的节点,就是一个你无法并行化的节点。解法是契约:有边界的输入、有边界的输出、恰好一个任务。输入是节点读取的一切——显式传入,绝不从共享窗口里假设。输出是一个定义的形状,最好经过验证,让下一个节点无需猜测就能消费。
输入是节点所读取到的内容——需要明确传递进来,而不是从共享窗口中自动获取。输出则是一个定义好的格式,最好经过验证,这样下一个节点就可以直接接收它,而无需进行猜测操作。
在工作流里,契约靠 schema 强制。给 Claude 一个带 JSON schema 的 agent() 调用,生成的子 Agent 被强制返回经过验证的结构化数据——验证发生在工具调用层,所以不匹配时 Claude 会自动重试,而不是丢给你一段需要解析和祈祷的自由文本。
// 一个有真实契约的节点:有界输入,验证输出,一个任务。
const ITEM = {
type: 'object', additionalProperties: false,
properties: {
title: { type: 'string' },
url: { type: 'string' },
impact: { type: 'string', enum: ['high', 'medium', 'low'] },
},
required: ['title', 'url', 'impact'],
};
const result = await agent(source.prompt, {
label: `research:${source.key}`,
schema: ITEM, // 强制验证结构化输出
agentType: 'general-purpose',
});
// result 现在是一个下一个节点可以信任的形状——而不是自由文本。
这就是「能被 Claude 接入图的节点」与「只有人类读得懂其输出的节点」的本质区别。
04 · 把边当成数据契约
一条边不只是「B 在 A 之后」,它是关于「什么会跨过边」的承诺:A 产出这个形状,B 被构建为消费这个形状。用其数据而不是其顺序来命名边,两件事变容易:你能立刻看出边是否真实(数据真的移动了吗?),并且只要形状不变,你可以替换两端节点而不破坏整张图。
实践中,边就是纯 JavaScript。扇出与合成之间的 reduce(扁平化、去重、过滤)只是对节点返回形状做操作的代码——不需要 Agent。
诱惑是生成一个 Agent 来「合并结果」。抵制它。
如果合并意味着扁平化和去重,那就是 results.flatMap(...)
加一个 Set —— 确定性、即时、零 Token。把 Agent 留给
判断,而不是管道。每条边都是一个 Agent 的图,是在为
自己的布线支付租金。
05 · 用 parallel() 做扇出
这是让一切值回票价的关键操作。当你有 N 个独立节点——N 个要查的来源、N 个要审的文件、N 个要审计的路由——别串联,让 Claude 把它们扇出同时跑。在工作流里就是 parallel():Claude 接受一个 thunk 数组,为每个生成一个子 Agent,全部并发执行,再把结果数组返回给你。
两个细节让它健壮:① parallel() 是屏障,等每个 thunk 完成才返回,下一阶段看到完整集合;② 抛错的 thunk 解析为 null 而不是拖垮整批,所以一个不稳定的 Agent 搞不垮整次运行。永远对结果做 .filter(Boolean)。 并发数受核心数限制,多余任务排队,传一百个 thunk 它们都会完成,只是一次处理少数几个。
phase('Research');
// 九个来源,九个 Agent,全部同时进行。
const raw = await parallel(
SOURCES.map((s) => () =>
agent(s.prompt, {
label: `research:${s.key}`,
phase: 'Research',
schema: ITEM_SCHEMA, // 每个节点返回经过验证的 JSON
agentType: 'general-purpose',
}),
),
);
const collected = raw.filter(Boolean); // 丢弃失败 Agent 的 null
扇出存在于 Claude 写的代码里,不在模型对话里。Claude 自己的上下文从不会同时持有九个来源——每个子 Agent 带自己的来源,只返回最终答案。这就是为什么 Claude 能把工作流扩展到数十、数百个子 Agent 而不淹没会话:编排层花 0 Token,因为它不是 Claude 思考的又一轮。
06 · 在屏障处扇入
扇出只有有人收集才有用。扇入是边汇聚的节点——一个 Agent(或一段代码)一次性看到所有上游结果,做需要整个集合的事:跨来源去重、按影响排名、总结果为空则提前退出。这是屏障值得消耗墙上时钟时间的唯一地方。
// 边:纯 JS,没有 Agent,零 Token。
const flat = collected.flatMap((c) => c.items);
log(`收集了 ${flat.length} 个项目`);
phase('Curate');
// 屏障节点:需要整个集合来去重 + 排名。
const curated = await agent(
`按影响程度去重并排名:\n${JSON.stringify(flat)}`,
{ phase: 'Curate', schema: CURATED_SCHEMA },
);
保持图快的原则:仅当一个阶段真正需要所有先前结果同时在一起时才用屏障。 跨所有来源去重?用屏障,正确。但仅仅扁平化一个列表?那是边,内联做就行。残酷的气味测试:如果你写了 parallel → transform → parallel,而中间那个 transform 没有跨项目依赖,你应该用管道(pipeline)并完全跳过屏障。
07 · 菱形:拆分 → 工作 → 合并
把扇出和扇入放一起,就得到每个严肃 Agent 图的主力拓扑:菱形。一个节点拆任务,许多节点并行干活,一个节点合并。它是市场扫描、依赖审计、代码审查、研究报告背后的形状——换来源和提示词,同一副骨架就能适配。
规范形式有个值得记的名字:扇出 → 规约 → 合成(fan-out → reduce → synthesize)。扇出去拿广度,用纯代码规约来压缩,用最终的 Agent 合成来写答案。
一旦你看见菱形,你就不再问「怎么让我的 Agent 做更多步骤」,而开始问「拆分点在哪、合并点在哪」——这才是真能扩展的问题。
08 · 用条件语句在运行时路由边
不是每张图都固定。有时走哪条边取决于某个节点发现了什么。路由器节点检查一个结果,决定哪个下游路径触发:分类工单后分支到正确处理器;检查 diff 大小后,要么快速审查,要么启动全面审计。
在工作流里,这只是对节点验证输出的 JavaScript if / switch,因为控制流在代码里。
// 路由器节点:一个 Agent 做分类,代码选边。
const { severity } = await agent(
`分类这个 diff 的风险:\n${diff}`,
{ schema: { type: 'object',
properties: { severity: { enum: ['low', 'high'] } },
required: ['severity'] } },
);
let review;
if (severity === 'high') {
// 重路径:完整的并行审计
review = await parallel(FILES.map((f) => () => agent(`审计 ${f}`)));
} else {
// 轻路径:一次快速通过
review = await agent(`快速审查 ${diff}`);
}
这正是确定性成为特性而非限制的地方。路由器的决策可由 Claude 驱动(一个子 Agent 做分类),但路由本身是 Claude 写的代码——所以相同分类每次运行方式一致。你在节点拿到 Claude 的判断,在边拿到脚本的可靠。没有突发的「Claude 决定跳过审计」——因为跳过必须写进图里,而它没有。
09 · 在边上放验证器
图的真正杠杆不是更多 Agent,而是你能围绕它们构建、用来产生信心的结构。验证器节点位于结果被允许向下游传递之前的边上,唯一任务是尝试否定这个发现。幸存则通过,否则它永远到不了答案。
三个值得掌握的模式:
- 对抗性验证:对每个发现生成 N 个独立怀疑者,提示它们反驳;只有多数幸存才保留。
- 视角多样化验证:给每个验证器一个独特视角——正确性、安全性、能否复现——多样性能捕获 N 个相同检查永远发现不了的失败模式。
- 评审团:从不同角度生成 N 个尝试,用并行评审员打分,从胜出者合成,同时嫁接最佳亚军的成果。
这正是某真实团队把 Bun 运行时移植到新平台、并把对抗性代码审查内嵌进循环的做法。
10 · 隔离节点,让一个失败毒不了一整张图
链条里失败会级联——C 失败,D 永不运行,整件事停摆。图里失败应被限制在节点内。这已经部分成立:parallel() 内抛错的 thunk 解析为 null,八个好 Agent 仍返回,坏的退出。你的 .filter(Boolean) 就是隔离。设计每个扇入节点去容忍缺失输入,而不是假设拿到完整集合。
更微妙的失败是节点相互冲突——Agent 并行写文件时会撞车。解法是隔离「工作树(worktree)」:每个 Agent 在自己的 git 工作树里跑,沙箱中干活,再干净合并。只在节点真在并行写文件时才用它,它是需要它的拓扑的安全带,不是每次运行的默认开销。
11 · 加一个循环——但必须让它收敛
有时你进入任务才知道任务多大:未知规模的发现、一次 bug 扫描、找到一个 bug 会牵出另外三个。这需要循环——一条回到早期节点的受控边。危险显而易见:不收敛的循环就是无限循环,会生成 Agent 直到预算耗尽。
收敛的模式是直到干涸的循环(loop-until-dry):持续生成查找器,直到连续 K 轮没发现新东西就停。几乎所有人第一次都会犯的错——你针对什么去重。针对所有已见内容去重,而不只是确认的结果。 否则被拒绝的发现每轮都重新出现,循环永不干涸,你造了台永远重发现同一死胡同的机器。
const seen = new Set(); const confirmed = []; let dry = 0;
while (dry < 2) { // 2 轮空结果后停止
const found = (await parallel(
FINDERS.map((f) => () => agent(f.prompt, { schema: BUGS }))
)).filter(Boolean).flatMap((r) => r.bugs);
const fresh = found.filter((b) => !seen.has(key(b)));
if (!fresh.length) { dry++; continue; } // 没新东西 → 趋向干涸
dry = 0;
fresh.forEach((b) => seen.add(key(b))); // 针对 SEEN(已见)去重,而非 confirmed(已确认)
// 计数前对每个新发现做多样化视角验证
const judged = await parallel(fresh.map((b) => () =>
parallel(['correctness', 'security', 'repro'].map((lens) => () =>
agent(`通过 ${lens} 判断 "${b.desc}" — 真实吗?`, { schema: VERDICT })))
.then((v) => ({ b, real: v.filter(Boolean).filter((x) => x.real).length >= 2 }))));
confirmed.push(...judged.filter((v) => v.real).map((v) => v.b));
}
12 · 跨节点分层用模型
不是每个节点都需要你最好的模型。图把这变得显而易见,而单个 Agent 永远做不到:有些节点有边界且重复(提取字段、分类工单),有些承载真正判断(合成报告、裁决发现)。在便宜模型上跑无聊的节点,把昂贵的 Token 花在判断真正发生的地方。
在工作流里,Claude 生成的每个子 Agent 默认继承你的会话模型,除非脚本覆盖它——所以大型运行默认完全按你的会话层级计费。单个 agent() 调用上的模型选项告诉 Claude 把那个节点路由到别处。大型运行前查一下 /model,让 Claude 把扇出的重复节点路由到更便宜模型、把合并节点留在高层级。这是把 Token 饥饿的图从昂贵变经济的杠杆,且不碰它的形状。
13 · 拓扑结构决定成本和延迟
图的形状不是装饰——它是墙上时钟时间最大的单一杠杆。一个坑翻所有人的选择:parallel() vs pipeline()。parallel() 的屏障让所有内容在下一阶段开始前等最慢的节点。pipeline() 独立地把每个项目流经所有阶段、没有屏障——项目 A 在阶段 3 时,项目 B 还在阶段 1。快的项目提前完成,而不是在慢项目后面闲置。
默认用 pipeline()。 仅当一个阶段真正需要所有先前结果同时在一起时才用屏障——跨集合去重、总结果提前退出、比较「其他发现」的提示词。「代码更干净」「阶段感觉独立」不是理由;屏障延迟是真实、可测量、被浪费的时间。独立不等于同步。
14 · 让 Claude 画图——自动路由
最后一步:对无法提前规划的任务,停止手动画图。
用动态工作流,你描述目标,Claude 自己写编排脚本——拆任务、选扇出、生成协调的子 Agent 舰队、合成结果。你拿到一张为本次运行量身定制的图,而不是一张你希望合适的固定图。
三种进入方式:
- 提示词里说「workflow」这个词,Claude 就为任务写一张。
- 运行一个保存的或捆绑的——
/deep-research就是生产环境里真实存在的图:范围界定 → 并行搜索 → 获取 → 对抗性验证 → 合成,正是本课程的骨架。 - 打开 ultracode,Claude 会为会话里每个重要任务规划一张工作流。跑得好时按
s把脚本存到.claude/workflows/——版本控制、可按名重跑、任何克隆仓库的人都能启动的图。
› 运行一个工作流来审计 src/routes/ 下每个路由是否缺少认证。
为每个路由文件生成一个 Agent,然后在报告前验证每个发现。
● Claude 编写了一个编排脚本 · 在后台启动…
/workflows — auth-audit · running ✓
范围界定 1/1 2.1k token · 4s
✓ 扇出 18/18 每个路由文件一个 Agent
◯ 验证 11/18 每个发现 3 票怀疑者…
○ 合成 0/1 等待验证
会话保持响应 — 在舰队运行时继续工作
三、本周用 Claude 构建的六个真实图
原文作者给了六个可直接借鉴的实例,证明这张思维模型不是玩具:
- 跨每个路由的安全扫描:Claude 为每个路由文件生成子 Agent,各自找缺失的认证检查,再由验证器逐个确认发现,才进入报告。单个上下文装不下的广度。
- 用
/deep-research出带引用的报告:Claude 把问题拆成不同角度,跑并行搜索,去重来源,写之前用三票怀疑者对抗性验证每条声明。 - 逐文件移植一个模块:Claude 跨文件扇出翻译,把测试套件当作每个文件的关卡(gate),失败循环回去——对抗性审查捕获单次通过会放过的坑。
- 对 diff 做对抗性审查:按 diff 大小路由——小的走快速通过,大的触发完整并行审计,评审员用不同视角(正确性 / 安全性 / 性能)再合成。
- 按计划做生态扫描:保存一次,永久重复跑。并行查许多来源(发布、博客、讨论),在屏障处按影响排名,写摘要。版本控制在
.claude/workflows/,可按名启动。 - 未知规模的发现(bug hunt):你不知道有多少 bug。Claude 并行跑查找器,针对所有已见内容去重每个新发现,验证幸存者,持续循环直到两轮无新东西才停。

四、结语:提示者提问,架构师画图
线性 Agent 从来不是天花板——它只是第一个形状,因为符合我们打字的方式,人人都用它。一行、一个头部、一次一件事。
一旦你能看见节点和边,你就不再要求 Agent「做更多」,而开始要求图「做更宽」:在工作独立处扇出,在信心重要处把关边,在判断不需要处分层模型。
大多数人会继续在线排队步骤。那些学会画图的人,会运行一支舰队——并且永远不会注意到其他人被困在下面的天花板。
更多推荐




所有评论(0)