夜雨聆风学习资料网

ARTICLE · 1179190

【LangGraph源码精读】第01期|LangGraph的心脏是Google的Pregel?BSP模型到底是什么 ?

【LangGraph源码精读】第01期|LangGraph的心脏是Google的Pregel?BSP模型到底是什么 ?

【LangGraph源码精读】第01期|LangGraph的心脏是Google的Pregel?BSP模型到底是什么?

30篇源码精读系列·第01篇(执行引擎层第一篇)。你用LangGraph写了好几个Agent,每次调用app.invoke()就跑起来了。但你有没有想过:LangGraph内部到底用什么算法在调度节点?为什么多个节点能并行执行还不冲突?为什么每个节点执行完都能自动保存状态?答案是Pregel——一个来自Google 2010年论文的图计算模型。这篇从论文到源码,把Pregel BSP模型讲透。


一、开头:你每天都在用Pregel,但可能从来没听说过它

每次你调用app.invoke(),LangGraph内部都在跑一个叫Pregel的执行引擎。

你可能从来没在文档里看到过"Pregel"这个词——它被藏在CompiledStateGraph的父类里,被藏在invoke()方法的深处。但它是LangGraph的心脏,没有它,你写的StateGraph只是一张不能执行的蓝图。

三个问题,先自测一下:

  • ● LangGraph为什么叫"Graph"?它和普通的DAG工作流有什么本质区别?
  • ● 两个节点并行执行,同时修改同一个状态字段,为什么不会数据冲突?
  • ● 为什么LangGraph能支持循环(ReAct的思考-行动-观察循环),而大多数工作流框架不支持?

这三个问题的答案,都在Pregel里。

这篇是30篇源码精读的第01篇,也是执行引擎层的第一篇。 搞懂了Pregel,后面的Channel、Checkpoint、并发、重试就都通了。搞不懂Pregel,后面的源码只会越看越懵。


二、Pregel是什么?从Google论文到LangGraph

2.1 Google Pregel:2010年的图计算框架

Pregel是Google在2010年发表的论文《Pregel: A System for Large-Scale Graph Processing》中提出的大规模图计算框架。最初用于PageRank、最短路径、社区发现等图算法。

Pregel的核心思想是BSP(Bulk Synchronous Parallel,整体同步并行) 计算模型。简单说:把计算分成一轮一轮的"超级步"(Super-step),每一轮里所有顶点并行计算,计算完后统一同步,再进入下一轮。

对比项
Google Pregel
LangGraph Pregel
计算对象
图的顶点(Vertex)
图的节点(Node)
通信方式
顶点之间发消息(Message)
节点之间写Channel(状态通道)
计算单位
Super-step(超级步)
Super-step(超级步)
计算模型
BSP三阶段
BSP三阶段(+Checkpoint)
应用场景
大规模图算法(PageRank等)
Agent编排、工作流、多Agent协作
规模
分布式集群,数十亿顶点
单机/多进程,数十到数百节点

LangGraph的Pregel是Google Pregel的"Agent编排版"——把顶点换成了节点函数,把消息换成了Channel写入,把图算法换成了Agent工作流。但核心的BSP计算模型完全一致。

2.2 为什么Agent编排需要图计算模型?

你可能会问:Agent编排不就是"步骤A→步骤B→步骤C"吗?用个简单的循环不就行了,为什么要搬一个图计算模型过来?

因为Agent不是线性的,它是一张有环的图。

工作流类型
特点
适合的模型
普通DAG工作流
有向无环图,步骤只执行一次,不回头
简单的拓扑排序执行器
Agent工作流
有向有环图,思考-行动-观察循环,可能回头重试
需要支持循环的图计算模型

ReAct Agent的"思考→行动→观察→思考→行动→观察..."就是一个典型的环。普通的DAG执行器不支持环,而Pregel的Super-step天然就是一个循环——每一轮Super-step都可能触发下一轮,直到没有新任务为止。

这就是LangGraph和其他Agent框架最本质的区别:它把Agent编排建模为图计算问题,用Pregel BSP模型来解。


三、为什么选Pregel?三个天然优势

LangGraph选择Pregel不是偶然,而是因为Pregel模型天然解决了Agent编排的三个核心难题。

优势
说明
对Agent的价值
天然支持并行
同一个Super-step中,所有就绪节点可以并行执行
独立步骤(搜索+代码生成)并行,性能翻倍
天然支持Checkpoint
每个Super-step结束后,所有Channel状态一致,可以安全保存快照
断点续跑、时间旅行、人机交互
天然支持循环
Super-step本身就是循环,有新任务就继续下一轮
ReAct思考-行动-观察循环、重试循环

这三个优势是其他模型很难同时具备的。

比如Actor模型支持并行和循环,但不支持全局一致的Checkpoint(因为Actor之间是异步消息,没有全局同步点)。比如简单的状态机支持循环和Checkpoint,但不支持并行。只有BSP模型三者兼备。

代价是什么? BSP模型有同步屏障(Barrier)——快的节点必须等慢的节点执行完,才能进入Update阶段。如果一个Super-step里有一个特别慢的节点(比如调用大模型),其他节点都得等它。这是BSP模型的固有缺点,LangGraph通过并发执行和超时控制来缓解。


四、BSP三阶段:Plan → Execute → Update

每个Super-step分为三个阶段,严格按顺序执行,阶段之间有同步屏障。这是Pregel最核心的机制。

4.1 三阶段总览

阶段
做什么
核心源码函数
所在文件
Plan(计划)
检查哪些Channel在上一步被更新了,找到订阅这些Channel的节点,生成待执行任务列表
prepare_next_tasks()
pregel/_algo.py
Execute(执行)
所有待执行节点并行运行,每个节点读取Channel快照,输出写入缓冲区
PregelRunner
pregel/_runner.py
Update(更新)
所有节点执行完后,按确定顺序将缓冲区的写入合并到Channel,版本号递增,创建Checkpoint
apply_writes()
pregel/_algo.py

4.2 Plan阶段:谁该干活?

Plan阶段是每个Super-step的起点。它回答一个问题:这一轮哪些节点该执行?

判断逻辑很简单:

  1. 1. 检查所有Channel的版本号,找出上一个Super-step中被更新的Channel
  2. 2. 通过trigger_to_nodes映射表,找到订阅这些Channel的节点
  3. 3. 为每个节点构造一个PregelExecutableTask(包含节点名、输入快照、执行函数、写入缓冲区、配置)

关键理解:节点不是被"调用"的,而是被"触发"的。 你在StateGraph里定义的边(A→B),编译后变成了"B订阅A写入的Channel"这样的订阅关系。A执行完写入Channel,Channel版本号变化,下一个Super-step的Plan阶段就会触发B。

这就是为什么LangGraph支持条件边——条件边本质上是"根据当前状态决定写入哪个Channel",不同的Channel触发不同的节点。

4.3 Execute阶段:各干各的,互不干扰

Execute阶段是并行执行的核心。所有待执行节点同时运行,但有一个关键约束:每个节点拿到的是Super-step开始时的状态快照,不是实时状态。

约束
说明
为什么需要
输入冻结
节点执行期间,它读取的Channel值不会变
避免读到其他节点的中间结果
输出缓冲
节点的输出先存在writes缓冲区,不立即写入Channel
避免并行写入冲突
状态副本隔离
每个节点有独立的状态副本,修改不影响其他节点
保证并行安全

这就是并行不冲突的秘密:不是加锁,而是隔离。 每个节点在自己的副本上干活,干完把结果交出来,由Update阶段统一合并。不需要锁,不需要事务,天然线程安全。

4.4 Update阶段:统一合并,确定顺序

Update阶段是三个阶段中最关键的。所有节点执行完后,Pregel按确定的顺序把缓冲区的写入合并到Channel。

步骤
做什么
关键机制
1
遍历所有节点的writes缓冲区
按节点名排序,保证确定顺序
2
对每个写入,调用对应Channel的update()方法
Channel根据reducer合并新值
3
Channel版本号递增
每个被更新的Channel版本+1
4
检查冲突
两个节点写同一个LastValue Channel且无reducer → 抛InvalidUpdateError
5
创建Checkpoint
保存当前所有Channel的完整快照

确定顺序是结果可复现的保证。 同样的输入,不管节点执行的先后顺序如何(因为并行),Update阶段总是按节点名排序合并,所以最终状态永远一样。这就是LangGraph能做到"确定性执行"的原因。

冲突检测是安全网。 如果两个节点在同一个Super-step里都写了同一个LastValue类型的Channel(覆盖语义),而且没有reducer,Pregel会直接抛InvalidUpdateError,而不是静默覆盖其中一个。这逼着你在设计状态时就考虑好并行写入的合并策略。


五、Pregel源码文件地图:9个核心文件

Pregel执行引擎的源码在libs/langgraph/langgraph/pregel/目录下,共9个核心文件。搞清楚每个文件的职责,读源码就有了地图。

文件
职责
核心类/函数
重要程度
main.py
Pregel主类,invoke/stream入口,持有所有节点和Channel定义
Pregel、CompiledStateGraph
核心
_loop.py
BSP执行循环,Super-step的调度器
PregelLoop、SyncPregelLoop、AsyncPregelLoop
最核心
_algo.py
任务调度算法,Plan和Update阶段的实现
prepare_next_tasks()、apply_writes()
核心
_runner.py
并行执行器,Execute阶段的实现
PregelRunner
重要
_checkpoint.py
Checkpoint的创建、恢复、写入
create_checkpoint()、channels_from_checkpoint()
重要
_call.py
单个节点函数的调用逻辑
节点执行包装
一般
_write.py
写入Channel的逻辑
写入处理
一般
_read.py
读取Channel的逻辑
读取处理
一般
_retry.py
失败重试逻辑
RetryPolicy处理
一般

阅读顺序建议: 先读main.py了解Pregel类的结构,再读_loop.py理解Super-step循环,然后读_algo.py看Plan和Update的具体算法,最后读_runner.py和_checkpoint.py。_call/_write/_read/_retry是细节,可以用到再看。


六、PregelLoop:Super-step的调度器

PregelLoop是BSP循环的核心实现。它有两个子类,分别对应同步和异步执行:

子类
使用场景
调用方式
SyncPregelLoop
invoke()、stream()
同步执行,线程池并发
AsyncPregelLoop
ainvoke()、astream()
异步执行,asyncio并发

PregelLoop内部追踪的关键状态:

属性
说明
作用
step
当前Super-step编号
计数,判断是否达到recursion_limit
stop
最大允许步数(recursion_limit)
防止无限循环,默认25
channels
当前Super-step的Channel状态
执行的状态基础
checkpoint
当前Checkpoint
持久化和恢复
pending_writes
待合并的写入缓冲区
Execute→Update之间传递

recursion_limit默认25是什么意思? 它限制的是Super-step的数量,不是节点执行次数。对于一个ReAct Agent,每一轮"思考→行动"是2个Super-step,所以默认25意味着最多12轮思考-行动循环。超过就抛GraphRecursionError。这是防止Agent死循环的安全机制。


七、一次Super-step的完整执行(7步)

以一个简单的并行图为例(A和B并行执行,然后C执行),追踪一个Super-step的完整过程:

步骤
阶段
发生了什么
1
Plan
检查Channel版本,发现input Channel被更新,触发A和B(都订阅input)
2
Plan
为A和B各创建一个PregelExecutableTask,包含input的快照
3
Execute
A和B并行执行,各自读取快照,输出写入各自的writes缓冲区
4
Barrier
等待A和B都执行完(同步屏障,快的等慢的)
5
Update
按节点名排序(A在前,B在后),将writes合并到Channel
6
Update
Channel版本号递增,检查冲突,创建Checkpoint
7
下一Plan
检查更新后的Channel,发现A和B的输出触发了C,进入下一个Super-step

注意第4步的Barrier(同步屏障)。 这是BSP模型的标志性特征——所有节点必须都执行完,才能进入Update。如果A是个快速的本地计算,B是个慢速的大模型调用,A必须等B。这就是BSP模型的代价。


八、终止条件:什么时候停下来?

PregelLoop在以下4种情况下终止:

终止条件
说明
触发场景
没有新任务
Plan阶段发现没有Channel被更新,没有待执行节点
正常执行完毕,到达END
达到recursion_limit
step超过stop(默认25)
Agent死循环,安全机制
遇到interrupt
interrupt_before或interrupt_after触发
人机交互,暂停等待人工输入
节点抛出异常
节点执行失败且没有配置重试
错误处理

最常见的终止是"没有新任务"。 注意不是"到达END节点"——END是一个虚拟节点,它不订阅任何Channel,也不执行任何逻辑。到达END意味着最后一个真实节点的输出没有触发任何新节点,Plan阶段发现没有待执行任务,自然终止。


九、设计权衡:为什么BSP而不是Actor模型?

这是一个值得深入思考的问题。Actor模型(比如Akka、Orleans)也支持并行和消息传递,为什么LangGraph选择了BSP而不是Actor?

维度
BSP模型(LangGraph选择)
Actor模型
并行安全
状态副本+确定序合并,天然安全
需要Actor自己处理并发消息
全局状态
每个Super-step结束有全局一致状态
没有全局一致状态,Actor状态独立
Checkpoint
天然支持,Super-step边界就是快照点
很难做全局一致的Checkpoint
确定性
确定序合并,结果100%可复现
消息顺序不确定,结果可能不同
性能
有同步屏障,快节点等慢节点
完全异步,没有等待,性能更高
调试
Super-step边界清晰,容易调试
异步消息交错,调试困难
适用场景
需要确定性、可恢复、可调试的Agent编排
高并发、低延迟的分布式系统

LangGraph选择BSP的核心原因:Agent编排需要确定性和可恢复性。

Agent的执行过程需要可预测(同样的输入产生同样的输出)、可中断(随时暂停恢复)、可回溯(回到任意步骤重跑)。这些需求只有BSP模型能满足。Actor模型虽然性能更高,但缺乏全局一致性,做不到这些。

这是一个典型的"用性能换确定性"的设计决策。 对于Agent编排来说,确定性比极致性能更重要——Agent的瓶颈通常在大模型调用(秒级),而不是执行引擎的同步屏障(毫秒级)。


十、面试关联:Pregel相关的5道高频题

面试题
答题要点
考察深度
LangGraph的执行引擎基于什么模型?
Pregel/BSP模型,来自Google 2010年论文,三阶段Plan/Execute/Update
基础概念
并行执行为什么不会数据冲突?
Execute阶段每个节点有独立状态副本,输出缓冲;Update阶段按确定序合并;不需要锁
核心机制
Super-step和节点是什么关系?
一个Super-step可能包含多个并行节点;顺序图中一个节点对应一个Super-step
概念辨析
recursion_limit限制的是什么?
限制Super-step数量,不是节点执行次数;默认25,防止死循环
细节理解
为什么选BSP而不是Actor模型?
BSP有全局一致状态,支持确定性执行和Checkpoint;Actor性能高但缺乏全局一致性
设计权衡

这5道题能答上来,Pregel层面的面试就稳了。 后面的源码精读会把每道题的答案深入到具体代码行。


十一、本期小结

Pregel执行引擎,核心就6句话:

  1. 1. Pregel是LangGraph的心脏,基于Google 2010年论文的BSP模型。 LangGraph把Agent编排建模为图计算问题,把顶点换成节点,把消息换成Channel写入,核心计算模型完全一致。
  2. 2. 每个Super-step分三阶段:Plan→Execute→Update。 Plan确定待执行节点(Channel更新触发),Execute并行执行(状态副本隔离、输出缓冲),Update按确定序合并(无冲突、可复现)。三阶段之间有同步屏障。
  3. 3. 并行不冲突的秘密是隔离,不是加锁。 Execute阶段每个节点有独立状态副本,输出先缓冲;Update阶段所有节点执行完后统一按确定序合并。不需要锁,不需要事务,天然线程安全。
  4. 4. Pregel天然支持三个Agent核心需求:并行、Checkpoint、循环。 并行来自Execute阶段的并发执行;Checkpoint来自Super-step边界的全局一致状态;循环来自Super-step本身就是一个循环(有新任务就继续)。这是其他模型很难同时具备的。
  5. 5. 源码9个核心文件,最核心的是_loop.py(PregelLoop)和_algo.py(prepare_next_tasks/apply_writes)。 PregelLoop有Sync和Async两个子类,分别对应invoke和ainvoke。recursion_limit默认25,限制的是Super-step数量。
  6. 6. 选择BSP而不是Actor,是"用性能换确定性"的设计决策。 Agent编排需要可预测、可中断、可回溯,这些需求只有BSP的全局一致性能满足。Agent的瓶颈在大模型调用,不在执行引擎的同步屏障。

十二、下一期预告

下一期(源码02),执行引擎层第二篇,深入节点调度的源码:

【LangGraph源码精读】第02期|并行执行不冲突?状态副本+确定序合并太巧妙了

从prepare_next_tasks()到apply_writes(),逐行拆解节点调度和状态合并的源码实现,搞懂PregelLoop是怎么把三阶段跑起来的。

你在使用LangGraph时遇到过并行执行的问题吗?评论区聊聊,我优先写你最关心的场景。


本文为《LangGraph源码精读30讲》系列第01篇。内容基于LangGraph v1.2.11源码结构梳理,核心文件位于libs/langgraph/langgraph/pregel/目录。版本更新后部分细节可能变化。如有错漏欢迎指正。

相关学习资料