一句话摘要
用 Claude 从零进阶图架构师的路线
用 Claude 做图工程:从 0 到图架构师的 14 步路线图(完整课程) July 20, 2026 · 18 min read · View source ↗ Claude AI
大多数试图构建多步 Agent 的人,最终得到的都是一条直线。第一步、第二步、第三步——每一步都礼貌地等着上一步结束才开始。
9/10 的人会注意到,其中一半的步骤根本不需要等待。
它们不路由。它们不分支。它们不并行。它们只是排队——一个头、一个上下文、一次一件事,直到窗口被填满,Agent 忘了自己在干什么。
> 关注我的 Substack 获取新鲜 AI alpha:movez.substack.com
这就是那条 14 步路线图,它把那个单文件直线变成一个图:一个能跨机群展开、验证自己的发现、并收敛到一个孤军奋战的 Agent 永远无法承载的结果。
这里是没人点破的那个转变。提示词是一句话。循环是一个环。harness(运行时)是 Agent 脚下的地板。
但工作本身的形状——什么在什么之前运行,什么能同时运行,什么必须等所有其他东西——那个形状是一个图。节点做思考。边传递结果。
Claude Code 发布了直接构建这些图的工具:动态工作流(dynamic workflows)。
Claude 写一个普通的 JavaScript 编排脚本,然后启动一支协调的子 Agent 机群来执行它——而协调本身不花任何模型 token,因为它是代码,不是对话。
01. 节点是工作。边是流动的东西。
一个图只有两样东西,把它们理顺就解决了大部分困惑。节点是一个工作单元——一个 Agent、一件有边界的工作、一个输入进、一个输出出。
边是一个依赖:它说这个节点的输出喂给那个节点的输入。仅此而已。
错误在于把"然后"当成一条边。"总结这个文件,然后告诉我天气"这两者之间没有边——天气并不消费总结。
那是两个不相连的节点,被一个线性脚本无谓地串在一起。只有当数据真的跨过边时,边才存在。
学会对你的 Agent 里的每一个"然后"问:下一步读取上一步的输出吗?如果不读,就没有边,那个等待就是浪费。
把它画成方框和箭头。一个方框是一个 agent() 调用。
一个箭头是一个变量,从一个调用的返回值传进另一个调用的
prompt。如果你画不出那条箭头——如果没有变量跨越——那两个
方框就是独立的,而独立性是你这门课接下来要利用的东西。
02. 你的线性脚本是一个退化的图
当你把一个 Agent 写成"做 A,然后 B,然后 C,然后 D",你已经画了一个图——一条不分叉的单链。每个节点恰好一条边进、一条边出。
它运行正确。但它也运行得慢而脆弱,因为一条链没有冗余:如果 C 卡住,D 永远不会发生,而 A 的工作被困在上游,无处可去。
图工程的第一个真功夫是重画这条链。拿出你的线性 Agent,对每一支箭头问第 1 步的问题。
大多数链有两三支不传递数据的箭头——它们只是你碰巧打字的顺序。
剪掉那些箭头,链就坍缩成更宽的东西:几个可以同时运行的独立节点,喂给一个需要它们全部的单节点。
03. 给每个节点一份契约
一个你无法推理的节点,是一个你无法并行的节点。解法是契约:有边界的输入、有边界的输出、恰好一件工作。
输入是节点读取的任何东西——显式传入,从不假设来自一个共享窗口。输出是一个定义好的形状,最好经过验证,这样下一个节点无需猜测就能消费它。
在工作流里,这份契约用一个 schema 来强制。当你把一个带 JSON schema 的 agent() 调用交给 Claude 时,Claude 派生的子 Agent 被强制返回经过验证的结构化数据——验证发生在工具调用层,所以 Claude 在不匹配时会重试,而不是丢给你一堆需要你去解析、祈祷的自由文本。
这就是"Claude 能接进图里的节点"与"只有人类读了输出才能工作的节点"之间的区别。
// A node with a real contract: bounded in, validated out, one job.
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, // forces validated structured output
agentType: 'general-purpose',
});
// result is now a shape the next node can trust — not free text.
04. 把边当作一份数据契约
边不只是"B 在 A 之后"。它是一个关于跨越之物的承诺:A 产出这个形状,B 被构建来消费这个形状。当你用它的数据——而不是它的顺序——来命名边时,两件事变容易了。
你能立刻看出这条边是否真实(数据真的动了吗?),而且只要形状不变,你就能换掉任何一端的节点而不破坏图。
在实践中,边活在普通 JavaScript 里。展开与综合之间的 reduce 步骤——展平、去重、过滤——只是操作你节点返回的形状的代码。
不需要 Agent。图思维的安静胜利之一:人们烧掉模型 token 的很大一部分,其实是一条边,而边是免费的。
诱惑是派生一个 Agent 去"合并结果"。忍住。如果合并意味着展平加去重,那就是 results.flatMap(...) 和一个 Set——确定性、瞬时、零 token。把 Agent 留给判断,而不是管道。一个每条边都是 Agent 的图,是一个在自己布线上付房租的图。
05. 用 parallel() 展开
这是为一切买单的那一步。当你有 N 个独立节点——要检查的 N 个来源、要审的 N 个文件、要审的 N 条路由——你不要把它们串起来。
你告诉 Claude 把它们展开、同时跑。在工作流里那就是 parallel():Claude 接收一个 thunk 数组,为每个 thunk 派生一个子 Agent,全部并发执行,然后把结果数组交还给你。
两个细节让它稳健。第一,parallel() 是一个屏障——它等每一个 thunk 之后才返回,所以下一阶段看到的是完整集合。第二,一个抛异常的 thunk 解析为 null 而不是拒绝整批,所以一个不稳定的 Agent 不会让整个运行沉掉。
总是对结果做 .filter(Boolean)。并发被限制在你的核心数附近,超出的部分排队,所以你可以传一百个 thunk,它们都会完成——只是每次一小撮。
phase('Research');
// Nine sources, nine agents, all at once.
const raw = await parallel(
SOURCES.map((s) => () =>
agent(s.prompt, {
label: `research:${s.key}`,
phase: 'Research',
schema: ITEM_SCHEMA, // each node returns validated JSON
agentType: 'general-purpose',
}),
),
);
const collected = raw.filter(Boolean); // drop the nulls from failed agents
展开活在 Claude 写的代码里,而不是一段模型对话里。Claude 自己的上下文从不一次装九个来源——每个子 Agent 携带自己的,只有最终答案回来。
这就是让 Claude 能把一个工作流扩展到几十上百个子 Agent 而不淹没会话的原因。编排层不花 token,因为它不是 Claude 思考的又一轮。
06. 在一个屏障处收拢
一个展开只有在有东西收集它时才有用。收拢(fan-in)是边汇聚的节点——一个 Agent(或一段代码)同时看到所有上游结果,并做一件需要整个集合的事:跨来源去重、按影响排序、在总数返回为空时提前退出。这是唯一一个屏障值得付出墙钟成本的地方。
让图保持快的规则:只在一个阶段真的需要把之前所有结果放在一起时才用屏障。跨所有来源去重?屏障——正确。
// The edge: plain JS, no agent, zero tokens.
const flat = collected.flatMap((c) => c.items);
log(`Collected ${flat.length} items`);
phase('Curate');
// The barrier node: needs the WHOLE set to dedupe + rank.
const curated = await agent(
`Dedupe and rank these by impact:\n${JSON.stringify(flat)}`,
{ phase: 'Curate', schema: CURATED_SCHEMA },
);
只是展平一个列表?那是条边,内联做掉。这个味道测试残酷而简单:如果你写了 parallel → transform → parallel,而中间那个 transform 没有跨项的依赖,你本应使用一个 pipeline,彻底跳过那个屏障。
07. 菱形:拆分 → 工作 → 合并
把展开和收拢放在一起,你就得到了每一个严肃 Agent 图的主力拓扑:菱形。
一个节点拆分工作,多个节点并行干活,一个节点合并。这是市场扫描、依赖审计、代码审查、研究报告背后的形状——换掉来源和提示词,同一个骨架就适配了。
这个经典形式有个值得记住的名字:展开 → 归约 → 综合。展开去获取广度,用普通代码归约去压缩,用最后一个 Agent 综合去写答案。
一旦你看见菱形,你就停止问"我怎么让我的 Agent 做更多步骤",开始问"拆分在哪、合并在哪"——这才是真正能扩展的那个问题。
08. 用条件在运行时路由边
不是每个图都是固定的。有时要走的边取决于一个节点发现了什么。一个路由节点检查一个结果,决定哪条下游路径开火——给工单分类,然后分支到正确的处理器;检查 diff 大小,然后要么做一次快速审查,要么启动一次完整审计。
在工作流里,这只是对一个节点已验证输出的一个 JavaScript if 或 switch,因为控制流活在代码里。
这里确定性成了一个特性,而不是限制。路由器的决定可以是 Claude 驱动的(一个子 Agent 分类),但路由是 Claude 写的代码——所以对同样的分类,它每次都以同样的方式运行。
你在节点处得到 Claude 的判断,在边处得到脚本的可靠性。没有那种"Claude 决定跳过审计"的涌现意外——因为那个跳过必须写进图里,而它没有。
// Router node: an agent classifies, code picks the edge.
const { severity } = await agent(
`Classify this diff's risk:\n${diff}`,
{ schema: { type: 'object',
properties: { severity: { enum: ['low', 'high'] } },
required: ['severity'] } },
);
let review;
if (severity === 'high') {
// heavy path: full parallel audit
review = await parallel(FILES.map((f) => () => agent(`Audit ${f}`)));
} else {
// light path: one quick pass
review = await agent(`Quick review of ${diff}`);
}
09. 在边上放一个验证器
图真正的杠杆不是更多 Agent——而是你能围绕它们包起来以产出信心的那个结构。
一个验证器节点坐在边上,在一个结果被允许流向下游之前,它唯一的工作就是试图杀掉这个发现。如果它活下来,就通过。如果没有,它就永远到不了答案。
有三种模式值得握在手里。
对抗性验证:对每个发现,派生 N 个被提示去反驳它的独立怀疑者;只有当多数活下来时才保留它。
视角多样化验证:给每个验证器一个不同的透镜——正确性、安全性、能否复现——因为多样性捕获的失败模式,是 N 个一模一样的检查永远捕获不到的。
评审团:从不同角度生成 N 次尝试,用并行评审打分,从胜者综合,同时嫁接亚军的精华。
这正是让一个真实团队带着内置于循环里的对抗性代码审查来移植 Bun 运行时的那个模式。
10. 隔离节点,让一次失败无法毒化整张图
在链里,失败会级联——C 死了,D 永远不跑,整件事停摆。在图里,失败应该被限制在它的节点里。
这已经部分为真:在 parallel() 内抛异常的 thunk 解析为 null,所以八个好 Agent 仍然返回,而一个坏 Agent 掉线。你的 .filter(Boolean) 就是那个限制。
把每一个收拢设计成容忍缺失输入,而不是假设完整集合。
更微妙的失败是节点互相踩踏。当 Agent 并行写文件时,它们可能冲突。
解法是隔离:"worktree"——每个 Agent 在自己的 git worktree 里运行,在沙箱里干活,并干净地合并。
只有当节点真的并行写时才用它。它是那一个需要它的拓扑的安全带,而不是每一次运行的默认税。
11. 加一个环——但让它收敛
有时你进去之前不知道工作有多大:未知规模的发现、一个扫出三个更多 bug 的 bug 扫荡。那需要一个环——一条回到更早节点的受控边。
危险很明显:一个不收敛的环,是一个无限循环,一直派生 Agent 直到你的预算耗尽。
收敛的模式是"直到枯竭"(loop-until-dry):不断派生发现者,直到连续 K 轮没有浮出任何新东西,然后停。决定成败的一个细节——也是几乎每个人第一次都会犯的错——是你对着什么去重。
对着"所有见过的"去重,而不只是已确认的结果。否则被否决的发现每轮都重新出现,循环永远不枯竭,你就造了一台永远花钱重新发现同样死胡同的机器。
const seen = new Set(); const confirmed = []; let dry = 0;
while (dry < 2) { // stop after 2 empty rounds
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; } // nothing new → toward dry
dry = 0;
fresh.forEach((b) => seen.add(key(b))); // dedupe vs SEEN, not confirmed
// diverse-lens verify each fresh finding before it counts
const judged = await parallel(fresh.map((b) => () =>
parallel(['correctness', 'security', 'repro'].map((lens) => () =>
agent(`Judge "${b.desc}" via ${lens} — real?`, { 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() 调用上的 model 选项告诉 Claude 把那个节点路由到别处。
在大运行之前检查 /model,然后让 Claude 把展开的重复节点路由到更便宜的模型,把合并节点留在高位。这是把一张吃 token 的图从昂贵变经济的那个杠杆,而不动它的形状。
13. 拓扑就是你的成本和延迟
图的形状不是装饰——它是墙钟时间上最大的单一杠杆。那个把所有人都绊倒的选择:parallel() 对 pipeline()。一个 parallel() 屏障让所有东西都等最慢的节点之后,下一阶段才开始。
一个 pipeline() 让每个项独立流过所有阶段,没有屏障——项 A 可以在阶段 3 时,项 B 还在阶段 1。快的项提前完成,而不是在慢项后面空转。
默认用 pipeline()。只在一个阶段真的需要把之前所有结果放在一起时才用屏障——跨集合去重、对总数的提前退出、一个与"其他发现"对比的提示。"代码更干净"和"阶段感觉是分开的"不是理由;屏障延迟是真实、可测量、被浪费的时间。分开不等同于同步。
14. 让 Claude 画图——自路由
最后一步,是停止为那些你无法提前规划的工作手工画图。
有了动态工作流,你描述目标,Claude 自己写编排脚本——分解任务、选择展开、启动一支协调的子 Agent 机群、综合结果。你得到的是一张为这次运行量身定制的图,而不是一张你希望它凑合着用的固定图。
有三种进入方式。在你的提示词里说出"workflow"这个词,Claude 会为这个任务写一个。运行一个保存的或打包的——/deep-research 是一个生产环境中真实的图:范围 → 并行搜索 → 抓取 → 对抗验证 → 综合,正是本课的那副骨架。
或者打开 ultracode,Claude 会为会话里每一个实质性任务规划一个工作流。当一次运行不错时,按 s 把它的脚本保存到 .claude/workflows/——版本受控、可按名字重跑、任何克隆该仓库的人都能启动的图。
› Run a workflow to audit every route under src/routes/ for missing
auth. Spawn one agent per route file, then verify each finding before
reporting. ● Claude wrote an orchestration script · launching in
background… /workflows — auth-audit · running ✓ Scope 1/1 2.1k tok ·
4s ✓ Fan-out 18/18 one agent per route file ◯ Verify 11/18 3-vote
skeptics per finding… ○ Synthesize 0/1 waiting on verify session stays
responsive — keep working while the fleet runs
这周用 Claude 构建的六张图
覆盖每条路由的安全扫荡。Claude 为每个路由文件派生一个子 Agent,每个都在猎取缺失的认证检查,然后一次验证器遍历在发现到达报告之前确认每个发现。任何单一上下文都承载不了的广度。
用 /deep-research 的带引用报告。一张已经随 Claude Code 发布的图。Claude 把你的问题分解成不同角度,跑并行搜索,去重来源,然后在写作前用三票怀疑者对抗性验证每一个声明。
逐文件移植一个模块。Bun 的天花板,扩展到你的仓库。Claude 跨文件展开翻译,把测试套件作为每道门运行,并把失败循环回去——对抗性审查捕获单遍就会带着损坏发布的东西。
对 diff 的对抗性审查。Claude 按 diff 大小路由:一个小改动得到一次快速遍历,一个大改动触发一次完整并行审计,审查者在不同透镜上——正确性、安全性、性能——然后一个评审团综合。
按计划做生态扫描。保存一次,永远重跑。Claude 并行检查许多来源——发布、博客、讨论——在屏障处按影响排序,并写摘要。版本受控于 .claude/workflows/,可按名字启动。
未知规模的发现。你不知道那里有多少 bug。Claude 并行跑发现者,把每个新发现对着所有见过的去重,验证幸存者,并持续循环直到两轮没浮出任何新东西——然后停。
结论:
提示者问一个问题。架构师画一张图。
线性 Agent 从来不是天花板——它只是第一个形状,每个人都去抓它,因为它匹配我们打字的方式。一条线、一个头、一次一件事。
一旦你能看见节点和边,你就停止要求 Agent 做更多,开始要求图做得更宽:在工作独立的地方展开,在信心要紧的地方给边设门,在判断不要紧的地方分层使用模型。
大多数人会继续把步骤排成一条线。那些学会画图的人会运行一支机群——并且永远不会注意到其余人被困住的那个天花板。 Tags: # X # Claude # AI # Guide 相关文章 10 个 SEO 外链 Claude 自动化,3 个月带来 61k AI 提及 外链建设就是试错。 SEO AI Claude Automation
原文参考:https://maxed.wiki/posts/graph-engineering-with-claude-14-step-roadmap-from-0-to-graph-architect-full-course/ (Maxed.wiki,本页为站内中文整理)