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/

相关推荐
ん贤17 小时前
手敲Linux命令
linux·运维·服务器
lkbhua莱克瓦2417 小时前
ZogginWeb 电脑端沉浸式记单词功能优化升级业务需求文档
笔记·电脑
泽020217 小时前
OJBalancer ----- 基于负载均衡仿leetcode的刷题界面
linux·leetcode·负载均衡
花间相见17 小时前
【Linux进阶01】—— tmux原理与实战教程
linux·运维·服务器
Bert.Cai17 小时前
Linux groupadd命令详解
linux·运维
路溪非溪17 小时前
抓取手机的蓝牙HCI日志并分析
linux·arm开发·驱动开发·智能手机
有谁看见我的剑了?17 小时前
新服务器上线优化调整
linux·运维·服务器
成为你的宁宁17 小时前
【apt update突然报错Temporary failure resolving ‘cn.archive.ubuntu.com‘】
linux·运维·ubuntu
kobesdu17 小时前
【ROS2实战笔记-4】Gazebo:从通信桥接到性能瓶颈相关技术梳理
笔记·机器人·ros·gazebo
Zfox_17 小时前
【LangChain】快速上手
langchain·ai编程