项目全景与分包
这是 60 天深度源码之旅的第一天。今天不写代码,先建立"地图感":LangGraph 到底解决什么问题、和 LangChain 是什么关系、为什么它用"图"来建模、仓库里 libs/ 那五个包各管什么、它们怎么互相依赖。地图清楚了,后面 59 天钻进任何一个角落都不会迷路。
LangGraph 到底解决什么问题
官方 README 第一句就把定位讲死了——它不是"又一个 Agent 框架",而是一个底层编排框架:
Low-level orchestration framework for building stateful agents.
(用于构建有状态智能体的低层编排框架)
README 紧接着列了它提供的五项"底层基础设施"(README.md:35-40):
| 能力 | 大白话 | 本课对应 |
|---|---|---|
| Durable execution | 跑一半崩了/关机了,能从断点继续,不用从头再来 | 阶段6 持久化 D33-40 |
| Human-in-the-loop | 跑到一半停下来等人审批、改一改再继续 | 阶段7 中断 D41-46 |
| Comprehensive memory | 短期工作记忆 + 跨会话长期记忆 | D33-40(记忆/Store) |
| Debugging (LangSmith) | 把每一步执行路径、状态变化画出来看 | D26 调试与画图 |
| Production deployment | 把这套有状态流程稳定部署到线上 | D59 运行时与部署 |
while True: 调模型 + 调工具 循环不够用?你自己写一个 Agent 循环,很快会撞到四堵墙:① 中途崩了状态全丢;② 想让人插一脚审批,得把循环拆得稀碎;③ 想让"查天气"和"查股票"两个工具并行跑,得手写线程/异步还要合并结果;④ 想事后复盘"它第 3 步为什么走错了",没有任何记录。这四件事——持久化、人在环、并行合并、可观测——正是 LangGraph 用一套统一机制替你兜住的。和 LangChain 到底什么关系
初学者最大的困惑就是"LangChain 和 LangGraph 是不是二选一"。看依赖就懂了——LangGraph 依赖 langchain-core,但反过来不成立:
dependencies = [
"langchain-core>=1.4.7,<2", # ← 只依赖 core(消息类型/Runnable 接口)
"langgraph-checkpoint>=4.1.0,<5.0.0", # 自家的持久化包
"langgraph-sdk>=0.4.2,<0.5.0", # 自家的客户端 SDK
"langgraph-prebuilt>=1.1.0,<1.2.0", # 自家的预制件(create_react_agent 等)
"xxhash>=3.5.0",
"pydantic>=2.7.4",
]
langchain-core只依赖 core,不是整个 langchain。core 里主要是 BaseMessage(消息类型)、Runnable(统一的"可调用"接口)这些基础积木。LangGraph 借用它们,但不依赖 langchain 里的 chains/agents。langgraph-checkpoint / -sdk / -prebuilt都是 langgraph 自己拆出去的兄弟包(同一个 monorepo,见 L04)。核心引擎只依赖抽象的 checkpoint 接口,具体实现另装。pydantic / xxhashpydantic 用来做状态 schema 校验(D12 会讲);xxhash 是快速哈希,缓存/去重用(D51 缓存会遇到)。LangChain(组件库)
- 提供"积木":模型封装、工具、提示模板、输出解析
- 擅长"把一次调用链起来"(LCEL 管道
|) - 是无状态的线性/树形组合
LangGraph(编排引擎)
- 提供"排产系统":状态、图、循环、并行、持久化
- 擅长"让多个步骤有状态地反复流转"
- 天生有状态、支持环、支持中断恢复
为什么用"图"这个模型
LangGraph 的核心数据结构就是一张有向图:点是"工位",边是"下一步去哪"。看 StateGraph 类开头的官方描述就明白了这个模型的三条铁律:
class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
"""A graph whose nodes communicate by reading and writing to a shared state.
The signature of each node is `State -> Partial<State>`.
Each state key can optionally be annotated with a reducer function that
will be used to aggregate the values of that key received from multiple nodes.
"""
communicate by ... shared state铁律①:节点之间不直接互相调用,而是通过一块共享状态(传送带)间接通信。A 把结果写回状态,B 从状态里读——A 根本不用知道 B 存在。这是解耦的关键。State -> Partial<State>铁律②:每个节点的"型号"都一样——吃进当前状态,吐出状态的一部分更新(只需返回你改动的字段,不用返回整个状态)。reducer function铁律③:多个节点同时写同一个字段时,用一个 reducer(合并函数)决定怎么合(覆盖?追加?相加?)。这解决了并行写冲突——D07 专讲。START → agent(想) → 有工具要调吗? →是→ tools(做) → 回到 agent;→否→ END。看到那条"回到 agent"的边了吗——这就是环,模型和工具来回踏步直到得出答案。链做不到,图轻松表达。
state = node(state))。问题:节点必须小心翼翼地把没动的字段原样带回,漏带一个就丢数据;而且无法表达"多个节点各改一部分再合并"。源码实现:节点只返回改动的字段(Partial<State>),引擎负责用 reducer 把这些"局部更新"合并进主状态。代价是引擎要多做一步合并逻辑,但换来了并行写、增量更新、字段级 reducer——这些是 Agent 场景的刚需。libs 五大分包:一个 monorepo
仓库根 libs/ 下不是一个包,而是一整个 monorepo(多包同仓)。ls libs/ 的真实结果:
libs/
├── langgraph/ # ★ 核心引擎(本课主战场)
├── checkpoint/ # 持久化"接口 + 内存实现"基座
├── checkpoint-sqlite/ # SQLite 持久化实现
├── checkpoint-postgres/ # PostgreSQL 持久化实现(生产用)
├── checkpoint-conformance/ # 给各 checkpoint 实现跑的一致性测试套件
├── prebuilt/ # 预制件:create_react_agent / ToolNode
├── cli/ # 命令行工具(本地起服务、部署)
├── sdk-py/ # Python 客户端 SDK(调远程部署的图)
└── sdk-js/ # JS 客户端 SDK
| 包 | 管什么 | 本课何时深入 |
|---|---|---|
langgraph | StateGraph、Pregel 引擎、channels、graph 构建 | 贯穿全程(D03-32、D41-58) |
checkpoint | BaseCheckpointSaver 抽象 + InMemorySaver + Store | D33-35、D40 |
checkpoint-sqlite / -postgres | 把存档落到真实数据库 | D36、D37 |
prebuilt | 开箱即用的 ReAct Agent、工具节点 | D53-56 |
cli / sdk | 部署与远程调用 | D59 |
langgraph-checkpoint(里面是 BaseCheckpointSaver 接口 + 一个内存实现)。想用 SQLite/Postgres 才额外装 checkpoint-sqlite/-postgres。好处:① 装核心引擎不会被迫拖进 psycopg(Postgres 驱动)这种重依赖;② 第三方能自己实现一个 RedisSaver 而不用改核心;③ checkpoint-conformance 提供统一测试,保证所有实现行为一致。代价:多了几个包、版本要对齐。这是典型的"依赖倒置——核心依赖抽象,实现依赖抽象"。langgraph 是发动机,checkpoint-* 是可换的油箱,prebuilt 是"整车成品",cli/sdk 是钥匙和遥控器。版本与元信息:pyproject 里的线索
一个陌生项目,pyproject.toml 的头部往往一眼透露"它多成熟、支持哪些 Python、官方怎么定位自己":
[project]
name = "langgraph"
version = "1.2.8" # 已是 1.x 稳定版
description = "Building stateful, multi-actor applications with LLMs"
requires-python = ">=3.10" # 至少 Python 3.10
classifiers = [
'Development Status :: 5 - Production/Stable', # ← 生产级稳定
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.13', # 一直支持到 3.13
]
version = "1.2.8"已进入 1.x。这很重要:意味着 API 相对稳定,但也说明你会在源码里遇到不少 v0.x → v1.0 的兼容/弃用代码(下一讲和后面会多次撞见 Deprecated)。multi-actor applications官方自我定位里的关键词 "multi-actor(多参与者)"——图里的多个节点就像多个 actor 各自读写共享状态。这正是它借鉴 Google Pregel 模型的地方(D19 展开)。requires-python >=3.10用到了 3.10 的语法(如 X | Y 联合类型、match 语句)。你本地 Python 低于 3.10 装不上——D02 装环境会用到。Production/Stable成熟度自评"生产级稳定"。配合被 Klarna、Replit 等采用(README 开头提到),可以放心地把它当成"值得逐行精读"的工业级代码。langgraph.constants 里乱 import 东西
这个 1.x 仓库对"什么是公开 API"卡得很严。比如老教程里常见的 from langgraph.constants import Send,在新版会触发弃用警告并被重定向:libs/langgraph/langgraph/constants.py:34-46
def __getattr__(name: str) -> Any:
if name in ["Send", "Interrupt"]:
warn(f"Importing {name} from langgraph.constants is deprecated. "
f"Please use 'from langgraph.types import {name}' instead.", ...)
...
return getattr(import_module("langgraph.types"), name)
所以读源码时看到 __getattr__ 里一堆 warn(...deprecated...),别慌——那是官方在"温柔地搬家",告诉你新家在哪。教训:跟着最新 __all__ 和 types.py 走,别照抄老博客的 import 路径。graph 包的"对外门牌":__init__ 出口
读任何 Python 包,先看它 __init__.py 的 __all__——那是它愿意让你用的门牌。langgraph.graph 只对外露 6 样东西:
from langgraph.constants import END, START
from langgraph.graph.message import MessageGraph, MessagesState, add_messages
from langgraph.graph.state import StateGraph
__all__ = (
"END", # 特殊终点标记
"START", # 特殊起点标记
"StateGraph", # ★ 最常用:构建图的建造器
"add_messages", # 消息列表专用 reducer(D08 深入)
"MessagesState",# 内置的"只有 messages 一个字段"的现成 State
"MessageGraph", # 已弃用的老写法(下面会看到)
)
StateGraph整门课 80% 时间围着它转。它是个建造器(builder):你往里 add_node/add_edge,最后 .compile() 出一个能跑的图。D03 开始逐行拆。START / END两个"虚拟节点"标记,其实就是两个字符串常量(__start__ / __end__,见下)。用来说"图从哪进、到哪停"。add_messages / MessagesState做对话机器人的现成零件:一个负责把新消息合并进历史(去重/更新/删除),一个是配好的现成 State。D06、D08 会用它们搭第一个对话图。MessageGraph还挂在门牌上,但源码里明确标了弃用——见下方证据。留着只为兼容老代码。START/END 的真身其实只是两个"驻留字符串"常量,一点不神秘:
END = sys.intern("__end__")
"""The last (maybe virtual) node in graph-style Pregel."""
START = sys.intern("__start__")
"""The first (maybe virtual) node in graph-style Pregel."""
== "__start__" 的比较可以走极快的指针相等而非逐字符比。引擎里 START/END 会被频繁比较,这点微优化很值。记住结论:START/END 本质就是特殊字符串,图里"从 START 连一条边到你的节点"就是入口。再看 MessageGraph 的弃用证据,坐实"跟新不跟老"的原则:
@deprecated(
"MessageGraph is deprecated in langgraph 1.0.0, to be removed in 2.0.0. "
"Please use StateGraph with a `messages` key instead.",
category=None,
)
class MessageGraph(StateGraph):
...
cat __init__.py 看 __all__——它就是"作者划给你的正门"。带 _ 前缀的(如 _node.py、_branch.py)是内部实现,能读但别在业务里直接 import。60 天怎么走:一张学习路线图
把整门课画成一条"从会用到懂原理"的上升曲线,你随时能定位自己在哪一层:
👶 小白:我只想会用,非得读到 Pregel 引擎那么深吗?
👨🏫 老师:想"会用",阶段①②③(D01-18)足够你写出能跑的 Agent。但一旦线上出怪问题——"为什么这个节点没触发""为什么并行结果被覆盖了""checkpoint 存了啥"——你就必须懂④⑥的原理才能 debug。这门课的价值正在深水区:把黑盒变白盒。
今日小结 + 动手 + 明日预告
🧠 今天你应该能回答
- LangGraph 一句话定位是什么?(low-level orchestration framework,管调度不管零件)
- 它和 LangChain 什么关系?(只依赖 langchain-core,是上下游搭配不是竞品;有状态/能循环/能并行是它的独门)
- 为什么用"图"而不是"链"?(图能表达环、分叉、并行,链只能一条道走到黑)
- 节点的统一型号是什么?(
State -> Partial<State>,只返回改动的字段) - libs 下五大类包各管什么?(langgraph 引擎 / checkpoint 持久化 / prebuilt 预制件 / cli+sdk 部署)
- START/END 的真身?(
sys.intern的两个特殊字符串常量) - 为什么 checkpoint 拆成接口包+实现包?(依赖倒置:不装 Postgres 也能用核心,第三方可自实现)
✋ 10 分钟动手(只需终端,不用写代码)
# 1. 看仓库分了哪些包
ls libs/
# 2. 看核心引擎对外露了什么门牌
sed -n '1,12p' libs/langgraph/langgraph/graph/__init__.py
# 3. 看核心引擎的依赖(印证只依赖 langchain-core)
sed -n '31,40p' libs/langgraph/pyproject.toml
# 4. 亲眼看看 START/END 就是两个字符串
sed -n '24,31p' libs/langgraph/langgraph/constants.py
# 5. 数一下核心包里有多少个 .py 文件(感受工程量)
find libs/langgraph/langgraph -name '*.py' | wc -l
StateGraph——用最小的真实代码,让"节点/边/编译/invoke"这套流程在你机器上真正转起来。跑通那一刻,前面所有抽象概念会突然落地。