我在从零重学 LangGraph。开工之前先给自己写了一篇导读——把「它到底是个什么东西」
「有哪些概念是必须先认识的」「什么场景真的该用它」这三件事捋清楚,免得又变成抄样例。
这篇不涉及具体代码调试,就是一张地图。
一句话:它不是"做 Agent 的库",是一台状态机引擎
我一开始的误解是把 LangGraph 当成"LangChain 的 Agent 升级版"。
后来发现它的核心抽象里,没有一个词跟 LLM 有关:
State(状态)、Node(节点)、Edge(边)、Checkpoint(检查点)、Interrupt(中断)
拿掉模型,这套东西照样成立。所以更准确的描述是:
LangGraph = 一台带持久化的状态机执行引擎,附带一层给 LLM 应用准备好的组件。
它的调度模型来自 Google 的 Pregel(图计算框架):把计算表达成"节点 + 消息传递",
一轮一轮推进。这也是为什么它叫 Graph 而不是 Chain。
几个容易搞混的定位,我理了一下:
| 是什么 | 什么时候用 | |
|---|---|---|
| LangChain | 模型/工具/检索器的统一封装层(ChatOpenAI、@tool、向量库接口) |
想统一切换模型、复用现成组件 |
| LangGraph | 编排引擎:状态、分支、循环、持久化、中断 | 流程需要分支/循环/暂停/回溯 |
| LangSmith | 追踪与评估平台(看每次调用的输入输出、算指标) | 想知道线上到底发生了什么 |
| LangGraph Platform | 把图部署成服务(API、队列、Studio 调试) | 要交付给前端/团队用 |
补一个我查了才确认的细节:LangGraph 的依赖里没有 langchain 主包,只有 langchain-core
(用它的消息和工具抽象)。所以你可以只用 LangGraph + 自己的模型调用代码,
不必把整个 LangChain 生态搬进来。
为什么会需要这么个东西
Agent 的本质其实很朴素,一个 while 循环就能写:
while True:
resp = model.call(messages) # 问模型下一步干什么
if not resp.tool_calls:
break # 不用工具了,收工
for call in resp.tool_calls:
messages.append(run_tool(call)) # 执行工具,结果喂回去
我自己手写过这个循环,它能跑。真正跑不动的是后面这些需求:
| 需求 | 裸 while 循环怎么办 |
|---|---|
| 第 7 轮工具调用要人工审批才能执行 | 得把循环拆成两半,中间状态自己找地方存 |
| 服务重启了,用户希望接着上次聊 | 自己设计快照格式、自己序列化 |
| 想回到第 3 轮改个参数重跑 | 基本没法做 |
| 三个检索源想并行 | 自己上 asyncio,还要处理结果合并 |
| 排查线上问题:它当时为什么选了这个工具 | 只有日志,没有结构化的执行轨迹 |
| 三个 Agent 分工协作 | 循环嵌循环,很快就没法维护 |
这些需求有个共同点:都要求"流程本身"是一等公民 —— 能被暂停、存档、回看、分叉。
而 while 循环里的流程是隐式的,藏在 Python 的调用栈里,栈一销毁就什么都没了。
LangGraph 干的事,就是把这个隐式流程变成显式的数据结构:
每一步都有名字、有边界、有快照。
核心概念地图
我把概念按四层理,从里到外。
第 1 层 · 结构:这张图长什么样
| 概念 | 是什么 | 我的备注 |
|---|---|---|
| State | 整张图共享的那个字典,节点之间唯一的通信方式 | 节点之间不互相调用、不传参 |
| Reducer | 「这个键被多次写入时怎么合并」的规则,写在 State 的类型注解上 | 默认是覆盖不是累加,第一大坑 |
| Node | 一个普通函数:收 state,返回「要改哪几个键」 | 返回局部更新,不是整个 state |
| Edge | 普通边 = 无条件下一步 | 用 add_edge("a", "b")
|
| 条件边 | 一个路由函数决定下一个节点是谁 | 分支和循环都靠它,Agent 就是一个环 |
| Command | 节点自己 return 一个"去哪 + 改什么" | 路由逻辑复杂时比条件边清楚 |
| START / END | 两个虚拟节点,标记入口和出口 | 实际就是 "__start__" / "__end__"
|
| compile() | 把"图纸"变成可执行运行时 | 持久化、断点、缓存都在这一步挂上去 |
最小的样子:
class State(TypedDict):
messages: Annotated[list, add_messages] # 注解里那个 add_messages 就是 reducer
builder = StateGraph(State)
builder.add_node("agent", call_model)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", tools_condition) # 要调工具吗?
builder.add_edge("tools", "agent") # ← 这条边让图成环
app = builder.compile()
四行边,就是一个完整的 ReAct Agent。add_edge("tools", "agent") 这一条回边,
就是"Agent"和"流水线"的全部区别。
第 2 层 · 执行:它是怎么一步步跑的
| 概念 | 是什么 | 我的备注 |
|---|---|---|
| Superstep(超步) | 一轮执行。同一超步里所有"当前该跑的节点"一起跑,跑完统一合并状态 | 并行的坑全在"统一合并"这四个字上 |
| invoke / stream | 前者只给最终结果,后者给过程 |
invoke 的返回值是最终完整 state,不是最后一个节点的返回值 |
| stream_mode | 实测有 7 种:values updates messages custom checkpoints tasks debug
|
常用前四个:整份状态 / 每步增量 / token 级流式 / 自定义进度事件 |
| durability | 状态落盘时机:sync / async / exit
|
是 invoke/stream 的运行时参数,不是 compile 参数 |
| recursion_limit | 最多走多少步,默认 25 | 环写错了靠它兜底,报 GraphRecursionError
|
"超步"这个概念看着学术,但它解释了一个高频报错:
两个并行节点写同一个键、又没给这个键配 reducer,就会报
InvalidUpdateError: At key 'x': Can receive only one value per step.
因为同一个超步里这个键收到两份提交,引擎不知道听谁的。Reducer 是并行的前置条件。
第 3 层 · 状态与记忆:它凭什么能暂停和回溯
这层是我认为 LangGraph 最有价值、也最难被别的框架替代的部分。
| 概念 | 是什么 | 我的备注 |
|---|---|---|
| Checkpointer | 每个超步之后存一份 state 快照 |
MemorySaver(内存)/ SqliteSaver / Postgres |
| thread_id | 会话隔离键。一个 thread = 一条独立的对话历史 | 挂了 checkpointer 不传它会直接报错 |
| Store | 跨 thread 的长期记忆,按 namespace 存(比如按 user_id) | checkpointer 管"这次会话",Store 管"这个人" |
| interrupt() | 在节点里一句话让整张图暂停,等人给输入 | 恢复用 Command(resume=...)
|
| 时间旅行 |
get_state_history() 列出所有快照,可以从任意一个恢复、甚至改了再跑 |
调试 Agent 的杀手锏 |
这层能力串起来是这样一件事:
用户点了"退款"→ 图跑到退款节点 →
interrupt()暂停 → 触发它的那个进程可以直接退出 →
主管第二天在后台点了"批准" → 另一个进程用同一个thread_id恢复 → 图从暂停处继续走完。
用 while 循环实现同样的东西,你得自己设计状态序列化、恢复点、幂等。这就是我认为值得上框架的地方。
顺带一个我特别喜欢的设计:interrupt() 恢复后,是整个节点重新执行一遍,
不是从 interrupt() 那一行往下走。所以有副作用的代码要写在 interrupt() 之后。
第 4 层 · 规模:图大了怎么办
| 概念 | 是什么 | 我的备注 |
|---|---|---|
| Subgraph | 把一张图当成另一张图的节点 | 父子 state 靠共享键连通,没共享键就写转换函数 |
| Send | 动态 fan-out:运行时决定"分裂成几个并行分支" | map-reduce 靠它,分支数可以由上一步算出来 |
| defer | 让汇总节点等所有分支跑完再执行 | fan-in 的正确姿势 |
| retry / timeout / cache | 节点级的 retry_policy、timeout、cache_policy
|
配置项,图写对之后再加 |
| prebuilt |
create_react_agent、ToolNode、tools_condition
|
官方现成件,省掉手写循环 |
create_react_agent 一行就能造一个工具型 Agent。我查了它的参数:
model tools prompt response_format pre_model_hook post_model_hook
state_schema checkpointer store interrupt_before interrupt_after……
它不是黑盒,它就是"agent ⇄ tools 两个节点 + 一条条件边"那张图。
知道这一点,才知道什么时候该抛开它自己画图。
它真正擅长的六类活
概念说完,说场景。这几个是我调研时反复看到、也确实适合用图表达的形状。
1. 工具型 Agent(最常见,也是 prebuilt 覆盖的部分)
START → agent ⇄ tools → END
↑______↓
模型自己决定调不调工具、调哪个,调完回到模型再判断。
用到:条件边成环、ToolNode、MessagesState。
适合:客服问答顺便查库、内部数据查询助手、运维小助手。
这类需求其实 create_react_agent 一行就够了,不需要自己画图。
2. 带自省循环的 RAG(这个才是 LangGraph 的主场)
START → 检索 → 评估相关性 →(不相关)→ 改写查询 → 回到检索
↓(相关)
生成答案 → 校验有没有幻觉 →(有)→ 重新生成
↓(没有)
END
朴素 RAG 是一条直线,检索到垃圾就只能拿垃圾回答。
加上"评估 → 改写 → 重检索"的环之后,效果差别很大(业界叫 Self-RAG / CRAG 这一类)。
用到:条件边 + 循环、recursion_limit 防止改写死循环、多路检索并行(reducer 去重合并)。
为什么值得用图:这是天然的状态机——每一步都要判断"够不够好,要不要再来一轮"。
3. 审批流 / 工单流(人在环里)
START → 理解意图 → 查询信息 → [敏感操作:退款/改单]
↓ interrupt() 暂停
等人:批准 / 驳回 / 改金额
↓ Command(resume=...)
执行 → 记录 → END
用到:interrupt() + Command(resume) + Checkpointer(跨进程恢复)。
适合:任何"AI 可以建议但不能自己拍板"的业务——退款、发工单、改数据、发对外邮件。
我觉得这是最被低估的一类。很多业务不敢上 Agent,卡点不是模型不够聪明,
而是没有一个可靠的"停下来等人"的机制。
4. 多 Agent 协作
┌→ 检索员 ─┐
主管 ───┼→ 撰写员 ─┼→ 回到主管判断下一步 → END
└→ 审校员 ─┘
两种主流拓扑:Supervisor(主管调度谁上场)和 Handoff / Swarm(平级之间直接交接)。
后者靠 Command(goto=..., graph=Command.PARENT) 实现。
用到:子图、Command 跳转、消息历史的共享或隔离。
适合:内容生产(检索→撰写→审校)、需求→开发→评审这类有明确角色分工的流程。
一句提醒(也是提醒我自己):动手之前先问"单 Agent 多给几个工具能不能解决"。多数情况能。
多 Agent 的成本是上下文翻倍、调试难度指数上升。
5. map-reduce 批处理
START → 拆分 → Send ⇒ [处理#1] [处理#2] … [处理#N] ⇒ 汇总 → END
(并行,N 运行时决定)
用到:Send 动态 fan-out、累加型 reducer 合并结果、defer 等齐分支。
适合:100 份合同各自抽要素再汇总、一个大问题拆成子问题分别检索、批量给数据打标。
Send 的价值在于分支数不用写死——上一步算出 37 个子任务,就开 37 个并行分支。
6. 长会话业务机器人
START → 加载记忆(Store) → 对话/工具 → 更新记忆 → 裁剪历史 → END
↑ 同一个 thread_id 反复进入,checkpointer 自动续上
用到:Checkpointer(本次会话)+ Store(跨会话记住这个人)+ RemoveMessage 裁剪上下文。
适合:客服、销售助理、个人助理——所有"用户明天回来还得记得他是谁"的场景。
这里有个概念区分我一开始搞混了:
Checkpointer 管"这条对话说到哪了",Store 管"这个用户是谁、有什么偏好"。 两者都要,职责不同。
顺带第 7 类:深度研究 / 代码类 Agent
START → 规划 → 执行子任务(并行) → 反思"够了吗" →(不够)→ 补充规划 → 回到执行
↓(够了)
产出报告 → END
plan → execute → reflect 的环,加上并行执行和中途可干预。
这类现在很热(deep research、coding agent),形状上就是前面几种的组合。
什么时候别用它
我觉得这部分同样重要,不然容易上头。
| 场景 | 更好的选择 |
|---|---|
| 单轮问答、一次性抽取/翻译/分类 | 直接调模型 SDK,别绕 |
| 固定 3 步、不需要暂停和回看的流水线 |
for 循环 + 三个函数(我第一篇的 demo 就是这个结论) |
| 只要一个工具型 Agent,流程无定制 |
create_react_agent 一行,或者别的更轻的方案 |
| 队友完全不想理解状态机 | 硬上会变成"只有你能维护的图" |
我给自己的判断标准:要不要上 LangGraph,看的不是"这是不是个 AI 应用",
而是"这条流程需不需要被中断和回看"。 不需要,for 循环就够了。
我的学习顺序
调研完我决定这么学(也算是这篇的结论):
-
先不接 LLM,用纯 Python 函数当节点,把 State / Reducer / 条件边 / stream 搞明白。
接了模型调试,出 bug 分不清是"模型犯蠢"还是"图写错",而且每次都要等网络、花钱。 -
再手搓一遍 ReAct 循环(自己解析
tool_calls、自己拼ToolMessage),
最后才用create_react_agent。顺序反了,学到的是"会调 API"而不是"知道它替我做了什么"。 - 然后才是生产必备那几样:checkpointer、Store、interrupt、时间旅行、结构化输出。
- 最后是规模问题:子图、
Send并行、多 Agent、重试缓存、部署。
核心判断就一句:LangGraph 的难点不在 Agent,在"图"。 Agent 只是图的一个特例——一张有环的图。
我在从零重学 LangGraph,边学边把笔记整理出来,这是开篇的导读。
环境:Python 3.13 + LangGraph 1.2.10。文中的 API 事实(stream_mode的 7 种取值、
durability是运行时参数、create_react_agent的参数表、依赖里没有 langchain 主包)
都是在这个版本上实际查证的,不同版本可能有变化。
有理解错的地方欢迎指出,也欢迎交流你在生产里踩过的坑。