3 minutes
Graph 工作流:多节点编排
单一 ReAct 循环是 Agent 的"单核"——但很多真实任务天然就是图(DAG),而不是直线。本章把 Agent 从"循环"扩展到"有向图",让你能把分支、并行、汇合、回退写到系统里,而不是让模型现编。
11.1 为什么单 Loop 不够
让 AI 做一个"自检 + 修复一束文件"的任务,在单 loop 里跑大概是这样:
thinking: 看 5 个文件 (生成 list)
↓
tool: read 文件 1
↓
thinking: 文件 1 没问题
↓
tool: read 文件 2
↓
thinking: 文件 2 有 lint 错
↓
tool: edit 文件 2
↓
...重复 5 轮,每次都让"thinking"来做路由决策
问题:你反复让模型决定"下一步该看哪一支",但其实流程是确定的:每个文件都看一遍、有问题就修。这把"决策权"浪费在了不必要的地方,还让模型容易漏一两个文件。
这是 Graph 工作流要解决的事:把"必然要做的形状"画成 DAG,模型只做每个节点的"实质推理",流程通过图去接管。
11.2 Graph Agent 的三件套
┌─────────────────────────────────────────────────────┐
│ Node : 一个 f(input) -> output 的小 Agent │
│ Edge : 节点之间的连接,可以是固定的 │
│ 也可以是 condition 提供的分支选择 ──┘ │
│ State : 在所有节点之间流动的共享字典 │
└─────────────────────────────────────────────────────┘
11.3 一段 30 行的极简 Graph Loop
不依赖任何框架你能写一个:
from typing import TypedDict, Callable
class State(TypedDict):
files: list[str]
failed: list[str]
fixed: list[str]
def node_check(state: State) -> State:
# 让 AI 检查这批文件,输出 failed 列表
state["failed"] = ai_check_files(state["files"])
return state
def node_fix(state: State) -> State:
state["fixed"] = []
for f in state["failed"]:
state["fixed"].append(ai_fix_file(f))
return state
# 边:根据 state 决定下一步去哪
def route(state: State) -> str:
return "fix" if state["failed"] else "done"
graph = {
"check": (node_check, route),
"fix": (node_fix, "check_after_fix"), # 固定边
"check_after_fix": (node_check, route),
"done": (lambda s: s, None),
}
def run_graph(initial: State) -> State:
state = initial
cur, runner, router = "check", *graph["check"]
while runner:
state = runner(state)
nxt = router(state) if callable(router) else router
runner_pair = graph.get(nxt)
if not runner_pair:
break
runner, router = runner_pair
return state
经过几百行框架代劳后的同济图结构,你看 LangGraph / OpenAI Swarm / Anthropic Computer Use 之类项目里都能识别出来这个内核。
11.4 何时把任务画成 Graph
简单决策树:
是单一 ReAct Loop 能 30 步解决的吗?
├─ 是 → 单 loop(用 Claude Code / OpenCode 原生就行)
└─ 否 → 任务形状有"多个固定路径 / 并行分支 / 需要 fan-out fan-in"吗?
├─ 是 → Graph
└─ 否 → 是否有所谓的 "需要在 N 个尝试中选一个 best"?有 → Graph+ 路由
11.4.1 Graph 适用场景
- ** mandated Pipeline**:你的 CI/CD 像流水:lint → test → build → deploy → notification
- ** fan-out / fan-in**:对 N 个文件做同样分析,再 merge
- 分支决策:单测失败 vs 成功走不同修复路径
- 多轮验证:fix → retest → fix → retest 这些是有 schema 的
11.4.2 不适用 Graph 的场景
- 开放式探索:让 Agent 自己决定下一动作——回到 ReAct 即可,画成图反而死板
- 测试级 prompt 探索:会浪费一半 model turn
11.5 三种典型 Graph 形态
11.5.1 Linear(手拉手排队)
A → B → C → D
适合简单 pipeline:
graph = {
"extract": (node_extract, "transform"),
"transform": (node_transform, "load"),
"load": (node_load, None),
}
11.5.2 Fan-out / Fan-in(并行)
┌── A_1 ──┐
data ──┤── A_2 ──┤── merge
└── A_3 ──┘
并行跑 3 路节点,merge 节点收集结果。节省墙钟时间的经典套路:
import asyncio
async def node_fanout(state: State) -> State:
results = await asyncio.gather(*[ai_analyze(f) for f in state["files"]])
state["analyses"] = results
return state
11.5.3 Loop with Branch(有条件回环)
check ──┬─ failed → fix ──→ check ──┐
└─ passed → done ↓
(订阅一直循环到通过)
工程上的两个底层:
def route(state: State) -> str:
if state["failed"]:
return "fix"
if state["loop_count"] > 3: # 熔断
return "escalate"
return "done"
不要让 LLM 在"路由判断上"消耗 token —— 这个判断的逻辑正好用普通编程做掉,到下一节点才让 AI 认真思考。
11.6 写 Graph:组手令 vs 编程嵌入
6.1 用框架(LangGraph 风格)
from langgraph.graph import StateGraph, END
g = StateGraph(State)
g.add_node("check", node_check)
g.add_node("fix", node_fix)
g.add_conditional_edges("check", route, {"fix": "fix", "done": END})
g.add_edge("fix", "check")
app = g.compile()
框架帮你做了"路由 + state 传播 + 错误重试"。代价是抽象的概念多(“node”、“checkpointer”、“interrupt”),学习曲线不低。
6.2 不用框架,纯 Python 协程
async def run_etl_pipeline(initial: State) -> State:
state = initial
state = await node_extract(state)
state = await node_transform(state)
while state["errors"]:
state = await node_repair(state)
state = await node_transform(state)
if state["attempt"] > 5:
raise RuntimeError("etl stalled")
return await node_load(state)
简洁、易测。如果节点 ≤5、分支少、不需要 checkpoint 维护,推举纯协程。Graph 是视觉化辅助,不要为画图而画图。
11.7 跟 Agent harness 的关系
Claude Code / OpenCode / Codex 这些 harness 是 multi-loop + 部分 graph 路由:内部 loop 是 9/10 章的 ReAct,外部在 subagent dispatch 时是 graph(11.x) + Loop。你不用学所有框架,理解这两层就够。
下面 12 章 Harness 框架 会专门讲这些"外部架构"。
11.8 小结
- Graph 适合:DAG pipeline / fan-out / 分支循环
- 不适合:开放探索、单任务 30 步内能完成
- 30 行极简 Graph 即可上手;不需要 LangGraph 也能做
- 决策点优先交给 if/else,复杂推理才交 LLM
下一篇:《12 Harness 框架:OpenCode 与 superpowers》。
Summary: Graph 把"固定形态"从模型里拿出来当 DAG 画,模型只做干货节点的推理。