LangGraph 源码学习笔记

基于对 LangGraph 源码的阅读与讨论,整理成一份以「概念 + 入口 + 结构」为主的学习笔记,方便后续翻查和扩展。


一、从 StateGraph 的泛型说起

1.1 这行代码在说什么?

python 复制代码
class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
  • Generic[X, Y, Z] :来自 typing,表示「泛型类」,方括号里是类型参数。
  • StateT, ContextT, InputT, OutputT:四个类型变量,分别表示「状态 / 上下文 / 输入 / 输出」的类型。
  • 在 Python 里,这些类型主要给类型检查器和 IDE 用 ,运行时不做强制;你传的 TypedDict 类(如 State)会作为 state_schema 存起来,类型上就对应 StateT

1.2 和 Java 泛型的区别

  • Java:泛型在编译期检查、运行期擦除;List<String> 限制元素类型。
  • Python:泛型主要是「声明 + 统一含义」------标注「这里/后面都用同一个类型」,由 mypy/Pyright 检查,解释器不强制。

二、几个 Python 语法点

  • :=(海象运算符) :在表达式里完成赋值并返回该值,例如 if (x := get_value()) is not None:
  • 参数名与 * :调用时一旦用了「名字=值」,其后的实参也必须带名字;定义里单独一个 * 表示其后的参数必须用关键字传(keyword-only)。
  • 同一参数不能传两次 :既按位置又按关键字传同一参数会触发 TypeError: got multiple values for argument

三、add_node 在做什么?

  • add_node(fn) / add_node("name", fn) 统一成「节点名 + action」。
  • 校验节点名:不重复、不用 START/END、不含保留字符。
  • 确定 input_schema :优先显式传入 → 否则从函数第一个参数的类型注解推断 → 再否则用图的 state_schema
  • 从返回类型注解里解析 Command/Literal,得到 ends(用于图可视化/路由)。
  • coerce_to_runnable 包装 action,和 input_schema、策略等一起放进 StateNodeSpec ,写入 self.nodes[node],并调用 _add_schema(input_schema) 登记 schema。

四、Channel 和 Trigger 是在哪建的?

  • 状态 channel (state 各 key 的 channel/reducer):在建图阶段 ,通过 _add_schema(schema) ,内部用 _get_channels(schema) 根据 schema 字段注解生成 channel,写入 self.channels
    • 调用时机:StateGraph.__init__(state/input/output schema)和 add_node 末尾(节点的 input_schema)。
  • 分支/汇聚 channel (如 branch:to:xxxjoin:xxx)和 trigger :在 compile() 里,对每个节点/边调用 attach_node / attach_edge 时创建;
    • 例如:每个非 START 节点有 triggers=[branch_channel],多源汇聚时创建 join channel 并 nodes[end].triggers.append(channel_name)

五、数据流转的入口

  • 对外入口Pregel.stream() / invoke()main.py);invoke 内部只是消费 stream() 的迭代器。
  • 第一次把用户 input 写进 channel :在 SyncPregelLoop.enter 里调 _first() ,其中 map_input(input_keys, self.input) 把 input 转成 channel 写入,再 apply_writes(..., PregelTaskWrites((), INPUT, input_writes, []), ...) 应用到 channel。
  • 每一步推进while loop.tick():main.py 2643/2969)里,tick()prepare_next_tasks 算本拍任务、apply_writes 应用上一拍 writes;runner.tick() 执行本拍节点;after_tick() 把本拍 writes 写入 channel、更新 checkpoint、得到 updated_channels 供下一拍用。

六、每次 stream/astream 都会起一个 tick 流转器

  • 每次 stream() / astream() 都会新建一个 SyncPregelLoop / AsyncPregelLoop ,在 with 里跑 while loop.tick():
  • 同步入口:main.py 约 2582(创建 loop)、2643(while loop.tick():);异步:约 2890、2969。
  • tick 的具体逻辑在 _loop.pyPregelLoop.tick() (约 459 行),sync/async 共用;区别是执行节点时 sync 用 runner.tick(),async 用 runner.atick()

七、单次 tick 的完整流程与结构

7.1 主循环

复制代码
while loop.tick():
    match_cached_writes(); runner.tick(...); loop.after_tick()

7.2 tick() 里

  • 检查 step > stop → 若超则返回 False。
  • prepare_next_tasks(...) :根据 checkpoint、checkpoint_pending_writes、updated_channelstrigger_to_nodes 算出本拍 tasks
  • 若 tasks 为空则 status="done" 并返回 False。
  • 若有 checkpoint_pending_writes 则 _match_writes 挂到对应 task.writes。
  • interrupt_before 检查;对已有 writes 的 task 做 output_writes;返回 True。

7.3 after_tick() 里

  • apply_writes(checkpoint, channels, tasks.values(), ...) :把本拍 task.writes 写入 channel,更新 channel_versions,返回 updated_channels
  • 若 updated_channels 与 output_keys 有交集则 _emit("values", ...)。
  • checkpoint_pending_writes.clear()_put_checkpoint(...);interrupt_after 检查。

7.4 关键结构

  • channelsMapping[str, BaseChannel],channel 名 → 当前 step 的 channel 实例(从 checkpoint 恢复);典型 key 包括 __start__、state 各 key、branch:to:{node}join:...
  • trigger_to_nodesdict[str, list[str]],channel 名 → 被该 channel 触发的节点列表,由 PregelNode.triggers 反推(main.py 3243--3248)。
  • updated_channels :上一拍 apply_writes 更新过的 channel 集合,供本拍 prepare_next_tasks 用。
  • Checkpoint:含 channel_values、channel_versions、versions_seen、updated_channels 等(checkpoint.base)。
  • checkpoint_pending_writeslist[(task_id, channel_name, value)],在 tick 里 _match_writes 挂到 task.writes,在 after_tick 里 apply 后 clear。
  • PregelNode:triggers、channels、writers、bound(_read.py)。

八、走到 END 后谁被释放?

  • CompiledStateGraph / Pregel 实例 :不会因为一次 run 结束而释放,可重复 invoke/stream
  • 本轮的 SyncPregelLoop / AsyncPregelLoop :退出 withexit 执行,loop 可被 GC;与这次 run 相关的 checkpoint、channels、tasks 随之不再被引用。

九、执行模型:图 + 类 BFS 的按层推进

  • 结构:有向图(节点、边、channel/trigger)。
  • 执行 :按「拍」(tick/superstep)推进;每拍根据 updated_channelstrigger_to_nodes 算出要跑的节点,跑完后 apply_writes 更新 channel,再进入下一拍------类似 BFS 的「按层」,但由**数据流(channel 写入 + trigger)**决定谁跑,不是显式 BFS 队列。整体是 BSP(Bulk Synchronous Parallel)风格。

十、并发链与 join

  • 何时需要图上并发链 + join :不同链要不同 retry/cache/中断策略、按链观测或恢复、人审按链、多 agent/多工具并行再合并决策等;若只是「一个节点里并发调几个 API」,用 async/Executor 即可。
  • 并发链共享 :同一个 run 里只有一个 loop、一个 step 计数;recursion_limit 也是整图一个。
  • 长链拖短链 :若一条链远长于其他,短链会早到 join 前「空等」,并行度下降、延迟被长链拉长。主要在设计层解决:少用不必的 join、平衡链长、用流水线或子图替代「长链+短链同图 join」等。

十一、后端视角:像传统流程编排 + Spring 式默认机制

  • 图 + 节点 + 边 对应「流程编排 / 微服务式步骤」;对简单业务,除了 checkpoint(及 interrupt/resume),很多用 async + 状态也能实现。
  • 价值一部分来自「默认机制完善」:retry_policy、step_timeout、recursion_limit、config、stream 模式、与 LangSmith 等集成,类似 Spring 的「开箱即用」;另一部分来自针对「有状态、可中断、可流式」agent 的图+state+channel 模型。

十二、部署实践:单例图与 loop 上限

  • 单例 :一个进程/worker 一般只 compile 一次 ,所有 API 复用同一个 CompiledStateGraph ;每次请求只调 invoke(input, config)astream(...),各请求的 state/checkpoint 在各自 run 的 loop 内,互不干扰。
  • 单次 run 的 tick 数 :有上限,由 recursion_limit 决定;默认 10000 (环境变量 LANGGRAPH_DEFAULT_RECURSION_LIMIT 可覆盖)。
  • 并发 run 数 :框架不设上限,由进程与部署决定。
  • max_concurrency :限制的是单次 run 内并发执行的 node 数,不限制并发请求数。
  • QPS:框架没有「默认 QPS」概念,需按业务与部署压测。

十三、代码入口速查

内容 文件 位置
stream 主循环 pregel/main.py 2643:while loop.tick():
astream 主循环 pregel/main.py 2969:while loop.tick():
tick() pregel/_loop.py 459--535
after_tick() pregel/_loop.py 537--570
prepare_next_tasks pregel/_algo.py 369 起
apply_writes pregel/_algo.py 217 起
_trigger_to_nodes pregel/main.py 3243--3248
recursion_limit 默认 _internal/_config.py DEFAULT_RECURSION_LIMIT = getenv(..., "10000")

文档路径:libs/langgraph/ 下;checkpoint 类型见 libs/checkpoint/langgraph/checkpoint/base/

相关推荐
charlie1145141911 小时前
RK3568跑Ubuntu 24.04全路程指南(以正点原子的RK3568开发板为例子)
linux·笔记·ubuntu·rootfs·教程·环境配置·rk3568
babe小鑫1 小时前
大专应用统计学专业学习数据分析的实用性分析
学习·数据挖掘·数据分析
MyFreeIT1 小时前
OpenSSL
linux·运维·服务器
瀚高PG实验室1 小时前
hghac8008漏洞扫描处理
linux·网络·windows·瀚高数据库
简佐义的博客1 小时前
15万单细胞、19种实体瘤:系统学习血管内皮细胞泛癌的单细胞与空间转录组联合分析思路
人工智能·学习
小龙1 小时前
【学习笔记】视频抽帧方法大全
笔记·学习·计算机视觉·视频·视频抽帧
魔力军2 小时前
Rust学习Day5:结构体介绍和使用
开发语言·学习·rust
AD钙奶-lalala2 小时前
Error starting ApplicationContext. To display the condition evaluation···
linux·运维·服务器
71ber2 小时前
RHCSE 实战笔记: LVS 负载均衡集群深度解析
linux·服务器·lvs