LangGraph 动画课堂zero to reliable agents
序章 · 先看见问题
A visual course for absolute beginners

让程序不只会回答,
还会走流程

你不需要先懂 Agent。接下来我们只跟着一个问题走:看它如何被清洗、分类、回答、检查、暂停、恢复,最后成长为一个可靠的学习教练。

State携带事实
Node完成工作
Edge决定路线
01 · 为什么需要图

不是所有代码,
都需要变成

只有两三步时,普通函数最清楚。真正让人头疼的是:分支越来越多、失败要重试、流程会暂停、过几小时还要接着运行。

1
先选下面三种写法

观察任务变复杂后,代码结构发生什么变化。

小白翻译

图不是为了“高级”,而是为了让复杂路线仍然看得见。

读取输出
if 基础if 失败else 重试if 审批else 取消
分类基础回答进阶回答检查

两步固定路线:直接调用函数最简单。此时没有必要引入 LangGraph。

02 · 学习地图

我们不会背术语,
而是把一个程序逐步养大

每到一个新问题,才引入一个新概念。这样你永远知道“它为什么存在”,而不是记住一堆孤立名词。

1
先走通State · Node · Edge
2
会选择分支 · 循环 · Command
3
会思考LLM · 消息 · 工具
4
能恢复Checkpoint · Interrupt
5
可交付并行 · 子图 · 测试

你只需要带着一个问题:“当前这一步知道什么、要做什么、下一步去哪?”后面所有概念都只是这三个问题的不同答案。

03 · 故事开始

用户只说了一句:
“什么是 State?”

程序接到这句话后,不能把所有事情挤在一个函数里。我们先把它拆成四个能独立解释的小步骤。

先别写代码

用嘴说清路线,比复制一段框架代码更重要。

第一次执行 · 问题像接力棒一样移动
收到问题raw_text
清洗去空格
分类基础 / 进阶
回答result

黄色小球不是“数据本身”,而是帮助你想象:同一份共享状态正被一步步补充。

04 · State

State 不是记忆魔法,
只是一张共享表单

流程走到任何一步,都要知道目前已经有哪些事实。State 就把这些事实放在有名字的字段里。

A
输入字段

一开始由用户提供,例如 raw_text。

B
中间字段

节点逐步补充,例如 clean_text、difficulty。

C
输出字段

最后交还调用方,例如 answer。

判断标准

后续节点需要知道,而且值得被保存的事实,才放进 State。

点击“下一步”,看表单被逐项填写
raw_text“ 什么是 State? ”
clean_text等待清洗…
difficulty等待分类…
answer等待回答…

现在只有用户输入;其他字段还没有事实。

05 · Node

Node 是一道工序:
读取 State,返回局部更新

清洗节点只负责清洗。它没有必要复制整张表单,更不应该偷偷修改全局变量。

clean_node.py
def clean_node(state: State):
    clean = state["raw_text"].strip()
    return {"clean_text": clean}
一句话

Node 不交回整份 State,只交回“我刚改了什么”。

Node 像加工机,不像公共黑板
输入 State
{
  raw_text: " 问题 ",
  clean_text: ""
}
clean_node读取 → 处理 → 返回
局部更新
{
  clean_text: "问题"
}
06 · 固定 Edge

Edge 不负责干活,
只回答:下一站是谁?

如果清洗完成后永远都要分类,那么这条路线就是固定 Edge。它像地铁轨道,不查看乘客内容。

线性图 · 每次都走相同路线
START入口
clean清洗
classify分类
END出口
build_graph.py蓝图 → compile → invoke
builder.add_edge(START, "clean")
builder.add_edge("clean", "classify")
builder.add_edge("classify", END)

graph = builder.compile()
graph.invoke({"raw_text": "什么是 State?"})
07 · 条件 Edge

到了岔路口,
路由函数只负责选方向

它读取当前 State,例如 difficulty,然后返回下一节点名称。它不生成答案,也不做数据库操作。

关键边界

路由器只指路;真正回答问题的是 basic_answer 或 advanced_answer 节点。

先预测,再点击左侧输入
清洗完成 判断难度 基础回答 进阶回答

等待选择输入。你认为黄色小球会走向哪条分支?

08 · 第一个完整图

现在把三件事合起来:
State + Node + Edge

输入一个问题。页面会用纯 Python 规则模拟清洗、分类与回答;这里故意不接 LLM,避免同时面对两个未知数。

学习策略

先证明路线和 State 正确,以后接入模型时,才知道错误来自流程还是模型。

State 更新日志
等待运行…

如果你能解释每一步“读了什么、改了什么、去了哪里”,就已经真正理解了第一张图。

09 · Reducer

两个节点同时写一个字段,
到底该覆盖还是追加

普通字段默认使用“新值覆盖旧值”。但日志、消息和并行结果通常需要保留双方内容,所以必须提前写清合并规则。

state.py明确合并语义
import operator
from typing import Annotated

class State(TypedDict):
    results: Annotated[
        list[str],
        operator.add
    ]
小白翻译

Reducer 就像收件规则:新包裹来了,是替换旧包裹,还是排在后面?

让 worker A 和 B 同时写 results
Worker A{ results: ["A"] }
Worker B{ results: ["B"] }
合并
规则
等待选择合并方式…
10 · Parallel & super-step

并行不是“大家随时互相看”,
而是同一轮各自工作,轮末统一交卷

同一个 super-step 中,Worker A 和 B 读取的是这一轮开始时的相同 State。它们看不到对方尚未提交的更新,直到步末由 Reducer 合并。

同一轮的三个时刻
READ共同快照A、B 看到相同输入
WORK彼此独立各自计算局部更新
COMMIT步末合并Reducer 决定结果

不要依赖并行任务谁先完成。需要稳定顺序时,给结果附带 order,再在汇总节点排序。

两个各耗时 1 秒的任务
串行
≈ 2s
并行
≈ 1s
适合并行的前提

A 不需要 B 的结果,B 也不需要 A 的结果;它们的等待时间才能真正重叠。

11 · Loop

“不合格就修改”很容易,
难的是确保一定能停

循环需要两个出口:内容已经通过,或者修改次数达到上限。revision_count 必须放进 State,因为路由、存档和恢复都要看到真实次数。

请选择一条路径,再观察圆点如何移动。
双保险

State 中维护最大修改次数,调用图时再设置 recursion_limit,避免任何意外死循环。

draft → check → revise / end
草稿 draft检查 check修改 revise结束 END
12 · Command

有时一个节点完成决定后,
需要同时说清“改什么”与“去哪”

这时返回 Command。它把状态更新和动态跳转放在同一个结果里,特别适合审批、交接和工具内部路由。

UPDATE STATE{ status: "approved" }

先把审批结果写入共享状态。

+
GOTO NODEgoto="publish"

再明确下一步直接进入发布节点。

不要给同一节点再添加一条普通静态 Edge。Command 已经动态跳转,再加固定路线可能让两条分支同时执行。

13 · Python or LLM?

不是把流程“全交给 AI”,
而是让它只处理语义任务

能用确定规则写清的事情,Python 更快、更便宜、更稳定。只有理解含义、生成语言或评价质量时,才需要模型。

操作方法

点击右侧任务,先在心里判断。蓝色代表 Python,珊瑚色代表 LLM。

已判断 0 / 6

把职责交给合适的人
14 · Single model node

模型只初始化一次,
节点只负责调用它

不要在每个节点里重复创建模型。统一模块便于切换供应商、设置超时与重试,也能避免配置散落。

nodes.py可观测的单节点调用
def generate_answer(state: State):
    started = time.perf_counter()
    try:
        response = model.invoke(
            state["question"]
        )
        return {
          "answer": response.content,
          "latency_ms": elapsed(started)
        }
    except Exception as error:
        return {"error": type(error).__name__}
日志边界

记录输入摘要、输出摘要、耗时和异常类型;永远不要记录 API Key。

一次模型调用周围应该看见什么
input
“解释 State”
LLM唯一初始化实例
latency
842 ms
output
“State 是…”
error
TimeoutError
15 · MessagesState

聊天不只是字符串,
每条消息都有角色与顺序

HumanMessage 表示用户说了什么,AIMessage 表示模型回答或提出工具调用,ToolMessage 把真实执行结果送回模型。

H
human

用户的意图与补充信息。

A
AI

模型文本或结构化 tool_calls。

T
tool

必须用 tool_call_id 匹配原调用。

为什么用列表

对话是连续发生的事件,新消息应该追加,而不是覆盖上一轮。

逐条追加,不覆盖历史
HUMAN

300 分钟课程,每天学 60 分钟,需要几天?

AI · TOOL CALL

safe_calculator(a=300, b=60, operation="divide")

TOOL · call_7f3

5

AI

每天学习 60 分钟,5 天可以完成。

16 · Structured output

程序不要解析“差不多”的话,
而要接收有类型的结果

让模型自由回答“我觉得这可能是进阶题,因为……”很适合人读,却很不适合程序路由。Schema 把允许字段、类型和枚举提前写清。

不是让模型更聪明

结构化输出只是让模型与程序之间的交接更稳定、更容易验证。

同一个分类结果,两种交付形式
“这个问题好像有点进阶吧,我建议也许交给高级回答模块处理。”

程序:我要 split 哪个符号?“也许”算不算确定?
17 · Workflow patterns

先看任务依赖,
再选择串联、路由或并行

它们不是三种“高级程度”,只是三种不同依赖关系:下一步依赖上一步、只需选择一个方向、或者多个任务彼此独立。

提取要点生成解释检查质量

后一步需要前一步结果,所以按顺序串联。

判断类型代码专家/概念专家

输入只进入最适合的一条路线。

查课程算时间找风险

三项任务互不依赖,可以同时开始,最后汇总。

18 · Checkpointer & thread

Checkpoint 是存档,
thread_id 是存档槽编号

图在 super-step 边界保存 StateSnapshot。下次携带相同 thread_id,运行时才能找到这一段会话;换一个编号,就像打开全新的存档。

不要混淆

thread_id 是状态指针,不是用户权限、API Key,也不是副作用幂等键。

切换 thread,观察两条历史互不串线
S0收到问题
S1完成分类
S2回答完成

thread-A 保存:小明正在学习 State,已完成第 2 步。

19 · State history & time travel

Time travel 不是修改过去,
而是从旧快照重新出发

Replay 使用旧 checkpoint 的 config 重新执行后续节点;Fork 先用 update_state 创建新 checkpoint,再沿新状态继续。原历史不会被删除。

当前历史:route 读取 decision="approve",最终进入发布。
重要提醒

旧快照之后的 LLM、API 与 interrupt 都会重新执行,所以副作用仍要安全。

从 route 前的 checkpoint 分出第二条时间线
生成计划旧快照
before_route
approve
发布
reject
重规划
20 · Streaming

同一次运行,
可以从三个角度观看

values 看每一步后的完整 State;updates 只看节点刚改了什么;messages 专门看模型 token 或消息片段。

V
values

适合做完整状态监视器。

U
updates

最容易定位哪个节点改坏了字段。

M
messages

面向用户实现逐字出现的回答。

别把它们当三次运行

它们只是同一条执行轨迹的不同投影。

step 1 { raw_text: "State?", clean_text: "State?" }step 2 { ..., difficulty: "basic" }step 3 { ..., answer: "State 是共享表单" }
21 · Tools

工具不是神秘插件,
而是一个描述清楚的函数

模型要知道工具名称、用途和参数类型;执行层要知道如何验证输入、捕获错误并返回稳定结果。

safe_calculatora: float · b: float · operation: enum
search_course_catalogquery: str · level: optional[str]
convert_study_durationtotal_minutes: int · daily_minutes: int
计算器为什么不能 eval()

工具输入来自用户或模型;eval 会把“算式字符串”扩大成任意代码执行入口。

一个可验证的工具契约
tools.pyread-only
class CalculatorInput(BaseModel):
    a: float = Field(ge=-1e9, le=1e9)
    b: float = Field(ge=-1e9, le=1e9)
    operation: Literal[
      "add", "subtract",
      "multiply", "divide"
    ]

@tool(args_schema=CalculatorInput)
def safe_calculator(...):
    """执行白名单内的四则运算。"""
    ...
22 · Tool-calling agent loop

模型不会直接执行工具,
它只会提出一个结构化申请

模型产生 tool_calls;工具节点从白名单中找到函数并执行;结果以 ToolMessage 返回;模型再决定回答还是继续调用。

1
Model

选择工具并给出名称、参数和调用编号。

2
Tool node

验证、执行、捕错,返回匹配编号的结果。

3
Model again

读取工具结果,组织最终自然语言回答。

循环出口

最后一条 AIMessage 没有 tool_calls 时进入 END;同时必须设置最大调用次数。

黄色圆点代表一轮请求在四个角色间移动
用户
“需要几天?”
模型
tool_call
计算器
返回 5
模型
最终回答
23 · Reliability boundaries

模型可以做决定,
安全边界必须是代码

未知工具、缺少参数、超时和工具异常都要返回明确的 ToolMessage,让模型知道发生了什么,而不是让整张图无提示崩溃。

点击一种失败,查看系统应该怎样回应。
本阶段只做只读工具

查询与计算不改变外部世界;发送、保存、扣费等副作用要留到人工审批之后。

四道不可交给模型自觉遵守的围栏
白名单工具名必须已注册
Schema类型、枚举、范围
Timeout等待不能无限长
Limit最大调用与成本

不要根据模型生成的名称动态 import 或执行同名函数。模型的输出只是候选请求,白名单才是最终边界。

24 · interrupt()

程序不是卡死,
而是保存现场后等你回来

interrupt() 把可序列化提示交给外部,并让图暂停。Checkpointer 保存 State;恢复时使用相同 thread_id 和 Command(resume=...)。

恢复值去了哪里

Command(resume="小明") 中的“小明”,会成为节点里 interrupt() 的返回值。

运行车辆在闸门前保存 checkpoint
运行中的
State
interrupt
准备开始:status="draft",thread_id="review-001"
25 · Approve / edit / reject

人工决定不只改变路线,
有时还要修改 State

approve 保持原内容继续;edit 把人工内容写回 State 后继续;reject 记录原因并进入取消或重新规划分支。

等待人工决定:draft="每天学习 30 分钟"

人工输入也要验证:action 必须来自允许列表;edit 必须带 edited_draft;reject 最好带可执行的 reason。

26 · Idempotency

恢复后节点会从头执行,
但发送、保存、扣费不能重复生效

幂等不是“绝不执行第二次”,而是相同业务操作即使被重试,最终外部结果仍然和执行一次相同。

1
中断前只准备

校验和计算可以重复;真正副作用放在中断后或独立节点。

2
生成稳定幂等键

例如 send:plan-001:v1,同一业务重试时保持不变。

3
数据库唯一约束

键已成功处理时,直接返回第一次收据。

亲手试一次

连续点击右侧“发送”。你会看到函数被调用多次,但只有一张有效收据。

idempotency_key = send:plan-001:v1

调用次数 0 · 外部有效发送 0

27 · Send & map-reduce

任务数量运行前不知道,
就让 Planner 动态发出N 份工作单

Planner 先产生子任务;每个 Send 包含目标 worker 和该次独立输入;worker 并行返回局部结果;Reducer 合并后再统一排序和总结。

dispatch.py运行时 fan-out
def dispatch(state: OverallState):
    return [
      Send("worker", task)
      for task in state["tasks"]
    ]
Map → Reduce

Map 是把大题拆成多份独立工作;Reduce 是把多份结果合成一个可靠答案。

主题:制作一份 LangGraph 入门报告
Planner
产生 3 个 task
Worker 1 · 核心概念
Worker 2 · 示例代码
Worker 3 · 常见错误
Reducer
排序并汇总

等待 Planner。worker_results 使用追加 reducer。

28 · Subgraph

当“生成 → 审核 → 修改”已经很复杂,
把它收进一个可独立测试的小图

父图只关心 topic 和最终 draft;review_feedback、revision_count 等内部细节留在子图。边界越清晰,修改内部流程时越不容易影响父图。

共享字段

父子图都需要,例如 topic、draft。

子图私有字段

只服务内部循环,例如 review_feedback。

字段不同时显式映射

用包装节点转换输入与输出,不依赖全局变量。

父图看见一个节点,内部其实是一张完整小图
父图 · 学习计划流程
生成
generate
审核
review
修改
revise 后返回审核

共享:topic、draft | 隔离:review_feedback、revision_count

29 · Router

Router 像医院分诊台:
先判断类型,再送往合适专家

一个明确的路由步骤对输入分类,可以选择一个专家,也可以把独立子问题并行分给多个专家,最后统一汇总。

适合

领域边界清楚、预处理明确、每次请求相对独立。

谨慎

长对话频繁换专家时,Router 本身通常不维护自然连续的角色状态。

控制权在哪里

控制权在路由步骤或工作流;专家只处理被分配的请求。

输入先到中央分诊,再进入某个垂直领域
Router分类与分发
课程专家catalog
计算专家duration
概念专家explain
综合结果synthesize
30 · Subagents

主 Agent 不交出方向盘,
只把明确任务作为工具委派

主 Agent 理解用户整体目标,决定调用哪个专家、给它多少上下文,再把专家返回的局部结论汇总成统一回答。

保持对话与记忆

用户始终面对主 Agent;它拥有全局上下文。

干净的局部上下文

子 Agent 通常只看到完成任务所需信息,返回后结束。

多一次汇总调用

集中控制带来额外模型调用与延迟,不要无理由增加专家。

控制权在哪里

一直在主 Agent。专家不会直接接管用户后续对话。

主 Agent 往返咨询多个专家,再统一回答
主 Agent计划与汇总
课程专家作为工具
安全专家作为工具
代码专家作为工具
评估专家作为工具
31 · Handoff

Handoff 不是“问专家一句”,
而是把后续对话正式交接

例如售前确认需求后,把用户交给实施顾问;接下来几轮都由新角色负责。状态中的 active_agent 或 current_step 会持续影响行为。

必要上下文必须随行

目标、已完成步骤、承诺、限制和未决问题。

新角色直接面对用户

不需要每一轮都回到原主 Agent 汇总。

上下文工程更难

传少了信息断层,传多了又会膨胀和混乱。

控制权在哪里

从旧角色转移给新角色,并在 State 中跨轮保持。

黄色接力棒从学习顾问交给课程规划师
学习顾问确认目标
课程规划师接管后续多轮

handoff payload:目标=掌握 LangGraph;基础=会 Python;时间=每天 60 分钟;未决=选择练习项目。

32 · Custom workflow

真正可靠的系统通常是混合物:
代码守边界,Agent 做语义

用 LangGraph 明确校验、分类、Agent、并行、人工审批和副作用节点。每一步是普通函数、模型调用,还是完整 Agent,都由图设计者决定。

校验输入Python · deterministic
生成计划Agent · semantic
检查预算Python · deterministic
人工审批interrupt · human
幂等保存Python · side effect

这是多数真实业务的推荐起点。先用明确 Workflow 固定风险边界,再在确实需要语义判断的位置放 Agent。

33 · Pattern lab

不要问“哪个最强”,
要问谁掌控下一步

点击场景。先自己判断,再看右侧答案与理由。

等待选择场景?

模式不是由 Agent 数量决定,而是由任务依赖、控制权与上下文需求决定。

34 · Testing & evaluation

Agent 测试要同时检查:
结果、路径与边界

一句回答看起来正确,不代表系统可靠。它可能调用了错误工具、绕过审批、循环太多次,或者把另一个 thread 的状态带进来。

单元测试

用假模型测试节点、路由、Reducer 和错误分支,快速、离线、可重复。

deterministic
轨迹断言

计算题必须经过 safe_calculator;普通问候绝不能触发工具。

path
模型评估

清晰度与完整性等语义质量,可用数据集、人工或 LLM judge 比较。

quality
2

最低限度保留两条关键测试:一条证明“该调用工具时确实调用”,一条证明“不该调用时没有乱调用”。

35 · Production readiness

Demo 会跑只是起点,
生产还要知道坏在哪里、如何恢复

持久化 checkpoint、身份隔离、超时重试、成本上限、日志脱敏、状态保留策略和副作用幂等,决定系统是否能长期运行。

Tracing

记录节点输入摘要、输出、耗时、错误与工具轨迹。

Persistent checkpointer

生产不能依赖进程内存;还要定义加密和保留周期。

Hard limits

recursion、concurrency、timeout、retry、tool calls、成本预算。

安全原则

API Key 不进代码、不进 State、不进 checkpoint、不进日志。

一次真实运行的可观测轨迹
00:00.000  validate_input  OK 4ms00:00.006  classify        OK 612ms  tokens=18400:00.621  course_tool     OK 38ms   readonly00:00.661  human_review    PAUSE checkpoint=cp_1702:14.220  save_plan       OK idempotency=plan:42:v1
20recursion limit
30stool timeout
3max retries
4max concurrency
36 · Capstone

现在回到最初的问题,
把它升级成一个可靠学习教练

它理解目标、查询课程、计算时间、生成计划、接受人工修改、保存会话,还能在失败后解释原因并恢复。

校验目标Python · input schema
建立画像LLM · structured output
查询与计算Tools · readonly
并行规划Send · reducers
审稿子图generate · review · revise
人工审批interrupt · Command
幂等保存side-effect safety
追踪与恢复checkpoint · streaming

毕业标准不是“用过很多 Agent”,而是能画出路径、解释 State、写出边界、验证轨迹,并在失败后定位和恢复。

37 · One mental model

你不需要背完 API,
只要永远能回答四个问题

当前知道什么?这一步做什么?下一步为什么去那里?失败或暂停后如何安全继续?

State现在知道的事实
+
Node这一小步的工作
+
Edge下一步的路线
+
Boundary终止、权限与恢复
最后一句

LangGraph 的核心不是让 AI 更自由,而是让复杂、长时间、会失败的 AI 流程仍然可理解、可控制、可恢复。

Presenter controls

像播放 PPT 一样学习

下一页,也可以按空格或 PageDown 上一页,也可以按 PageUp R重新播放当前页动画 M打开整套课件地图 F进入或退出全屏 Esc关闭地图、弹窗或退出全屏