第四周:LangChain 与 LangGraph 的图、状态与检查点
图擅长一条带分支的流程加每步存档,不擅长上百个任务按优先级排队。把这条边界搞清楚,比学完它的 API 有用得多。
前三周的编排都藏在框架里。这一周把它画出来:节点、边、状态。
图这个形状解决什么
第三篇的层级流程是「让一个 manager agent 决定派谁」。图是另一个答案:路径写死在结构里,但允许有条件分支。
按三层的分工,graph engineering 关心的不是「agent 在做什么」,而是「什么被允许继续往下走」 —— 节点、条件路由、并行分支都是这一层的表达方式。
图真正的价值有三样,而且都不在「画得好看」上:
一、每一步之后自动存档。 checkpointer 把状态写下来,进程死了下次从最后一个存档接着走。
二、状态是显式的。 什么信息在节点之间流动,写在类型里,不是靠对话历史隐式传递。
三、能停下来等人。 interrupt 让图停在某个节点,把 payload 交出去,等外面把答复送回来再继续 —— 而且停下来的位置被存档存住,进程死了也还在那儿等着。
第三条是自己用状态字段轮询做不到的。
两个我实测过的坑
这两个都不在文档显眼处,但会真实地咬人。
节点名不能和状态字段撞名。 我把人工复核那个节点叫 review,而 review 已经是状态里的一个字段:
review is already being used as a state attribute (a.k.a. a channel), cannot also be used as a node name.编译期一个字都不说,跑起来才报。 麻烦不在于难改,在于一个只在半小时长跑里才走到的节点,撞名了要等半小时才知道。
被打断的节点会整个重跑。 我写了个探针验这件事:一个节点里在 interrupt 之前放一个副作用,恢复之后那个副作用执行了两次。
这意味着 interrupt 之前不能有不可重复的动作 —— 不能先下单再等人确认,不能先写文件再等人确认。要么把副作用挪到 interrupt 之后,要么让它幂等。
薄图厚模块
我自己那套东西用图的方式是:一条流水线走图,跨任务的调度留在图外面。
理由是分工:
- 图擅长「一条带分支的流程,每一步之后存档」
- 图不擅长「一百多个任务按优先级和预算排队」
后者是调度问题不是流程问题 —— 动态的任务列表、预算耗尽时整批让出、单个任务失败不许传染,这些用图表达要绕好几个弯,用一个数组加一个 pick 函数反而直白。
所以判据是:这段逻辑是「流程」还是「排队」。 流程进图,排队留外面。
让 agent 造 agent
这一周还会碰到「让 agent 生成并启动别的 agent」。这个方向学术上叫 ADAS(自动化 agent 系统设计),而它有一篇很硬的反方值得先知道:
有实验量过 meta agent 设计 agent 的经济性,结论是回本点在大约一万五千个样例,而且很多数据集上无论规模多大都回不了本 —— 因为设计出来的 agent 每次推理还更贵。
所以这一格适合当作理解练习,不适合当作第一个生产方案。
边界与代价
图的表达力有上限,超过就会很难看。 节点超过一定数量、边超过一定密度,那张图就不再是「一眼能看懂流程」,而是另一种形式的意大利面。九个节点是个不错的自检线 —— 超了多半该拆成两张。
checkpointer 存 SQLite,多进程并发写会锁。 单进程没问题;真要多进程分道,这一层要换。
两套存档并存是债。 如果你自己也在写状态快照,而图又在存 checkpoint,必须定下谁说了算 —— 否则续跑会读到两个互相矛盾的进度。这一条我自己拖了很久没解,写在这儿提醒。