第一次学 LangGraph:用一个快递案例搞懂 State、Node、Edge 和 compile

我挖掘了一个巨牛的 人工智能 学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站

如果你刚开始学习 LangGraph,大概率会遇到这样一种感觉:

State、Node、Edge、START、END、compile......每个单词单独看都不难,但真正让自己从零写一个完整 Graph 时,却很容易不知道该从哪里开始。

尤其是第一次看到这种代码:

复制代码
graph = StateGraph(State)

graph.add_node(...)
graph.add_edge(...)

app = graph.compile()
app.invoke(...)

很多人都会有一种非常熟悉的感觉:

每一行代码好像都认识,但它们为什么要这样组合起来,我并没有真正理解。

我刚开始学习 LangGraph 时也遇到了类似的问题。

比如:

  • 为什么一定要先定义 State?
  • Node 节点本质上到底是什么?
  • Edge 和条件 Edge 有什么区别?
  • 为什么 Node 里面一般都要 return 一个字典?
  • 为什么不能直接修改 state?
  • compile() 明明是"编译",为什么和 C++ / Java 的编译完全不一样?

所以这篇文章不准备单纯带你"复制代码跑通案例"。

我想通过一个非常直观的 「快递配送系统」,把 LangGraph 最核心的一套开发流程完整串起来。

我们最终要实现这样一条业务流程:

START → 揽收站 → 分拣中心 → 普通配送 / 加急配送 → 派送站 → END

其中:

普通包裹走陆运;

加急包裹走空运;

整个配送过程中,包裹状态、配送历史和运输里程都会保存在全局 State 中不断更新。

通过这个案例,你会完整理解 LangGraph 最基本的开发流程:

定义 State → 定义 Node → 注册节点 → 添加 Edge → 条件分支 → compile → invoke

如果把 LangGraph 最核心的思想压缩成一句话,其实就是:

State 负责保存数据,Node 负责处理数据,Edge 负责决定下一步去哪里。

下面,我们从最基础的 Graph API 开始。


一、先搞懂 LangGraph 的标准开发流程

构建一个 LangGraph 图,实际上存在一套非常固定的开发流程。

整体可以概括为 5 个步骤:

  1. 定义全局 State
  2. 创建 StateGraph
  3. 定义并注册 Node
  4. 添加 Edge
  5. 调用 compile() 编译并执行 Graph

也可以进一步记成一条非常简单的路线:

State → Graph → Node → Edge → Compile → Invoke

其中:

  • State:保存整个工作流共享的数据
  • Node:执行具体业务逻辑
  • Edge:控制节点之间如何跳转
  • compile():把前面定义好的图组装成可以执行的工作流
  • invoke():传入初始状态,真正运行整张图

理解这条主线以后,再看 LangGraph 的代码会轻松很多。


二、一个容易误解的地方:LangGraph 的 compile 到底在"编译"什么?

第一次看到:

复制代码
delivery_system = delivery.compile()

如果之前学过 Java 或 C++,很容易下意识把这个 compile() 理解成传统意义上的"代码编译"。

但实际上,两者完全不是一回事。

LangGraph 中的 compile() 并不会:

  • 把 Python 代码翻译成机器码
  • 生成 .exe
  • 生成 JVM 字节码
  • 做传统编译器意义上的代码链接

它真正做的事情更接近于:

把我们前面定义好的 State、Node、Edge 和条件路由组装成一个真正可以运行的工作流对象。

也就是说:

Java / C++ 的编译主要解决的是:

代码怎么变成计算机能够执行的程序。

而 LangGraph 的 compile 主要解决的是:

你定义的这张图,怎么变成一个真正可以执行的工作流。

理解这一点之后,后面的代码就不会那么容易混淆了。


三、完整实战:用 LangGraph 模拟一个快递配送系统

接下来开始正式写代码。

我们设计一个简单的快递配送系统。

每一个包裹都有:

  • 包裹编号
  • 始发地
  • 目的地
  • 配送优先级
  • 当前配送状态
  • 历史流转记录
  • 累计运输距离

整个流程如下:

包裹进入系统

↓

揽收站

↓

分拣中心

↓

根据 priority 判断:

普通 → 标准陆运

或者:

加急 → 空运加急

↓

派送站

↓

签收完成


3.1 第一步:定义 State,保存整张图共享的数据

LangGraph 中的 State 可以理解为:

整个工作流所有节点共享的一份数据。

后续不管包裹经过揽收站、分拣中心还是派送站,大家看到的都是同一个 State 中的数据。

因此,我们首先定义:

python 复制代码
from typing import TypedDict, Annotated
from operator import add

class PackageState(TypedDict):

    package_id: str
    origin: str
    destination: str

    status: str

    history: Annotated[list[str], add]

    total_distance: Annotated[int, add]

    priority: str

这里有一个非常重要的知识点:

State 中不同字段的更新方式并不完全一样。

例如:

复制代码
status: str

默认采用覆盖更新。

旧值:

复制代码
"待揽收"

节点返回:

复制代码
"已揽收"

最终就会直接变成:

复制代码
"已揽收"

而:

复制代码
history: Annotated[list[str], add]

则代表使用 operator.add 作为 reducer。

因此:

复制代码
["北京揽收"]

再返回:

复制代码
["进入上海分拣中心"]

不会覆盖前面的数据,而会变成:

复制代码
[
    "北京揽收",
    "进入上海分拣中心"
]

这也是 LangGraph State 中非常重要的一个设计:

不同字段,可以定义不同的状态合并规则。


3.2 第二步:创建 StateGraph

State 定义完成之后,创建整张图:

复制代码
from langgraph.graph import StateGraph, START, END

delivery = StateGraph(PackageState)

这里的:

复制代码
PackageState

相当于告诉 LangGraph:

接下来这张 Graph 中流转的数据,都按照 PackageState 这个结构管理。

需要注意:

此时我们只是创建了一个图构建器。

里面:

没有节点;

没有边;

没有业务流程。

因此现在还不能直接执行。


3.3 第三步:定义 Node 节点

LangGraph 中的 Node,本质上就是一个 Python 函数。

例如揽收站:

复制代码
def receive_package(state: PackageState):

    origin = state["origin"]

    return {
        "status": "已揽收",
        "history": [f"在{origin}揽收"]
    }

它做的事情非常简单:

读取当前 State 中的:

复制代码
origin

然后告诉 LangGraph:

这一次节点执行完以后,需要更新两个字段:

复制代码
status
history

后面的分拣、运输、派送节点,本质上也都是同样的逻辑。


四、我学习时踩到的第一个坑:Node 为什么一定要 return?

这个问题我第一次写 LangGraph 时也比较疑惑。

例如:

复制代码
def receive_package(state: PackageState):

    origin = state["origin"]

    return {
        "status": "已揽收",
        "history": [f"在{origin}揽收"]
    }

为什么一定要:

复制代码
return

能不能直接:

复制代码
state["status"] = "已揽收"

然后不返回?

理解这个问题,其实就理解了 LangGraph State 更新机制很重要的一部分。

Node 更推荐采用这样的思维方式:

读取旧 State → 执行业务逻辑 → 返回本次需要更新的数据。

例如:

复制代码
return {
    "status": "已揽收"
}

相当于告诉 LangGraph:

我这个节点执行完成之后,请把 status 更新成 "已揽收"。

然后 LangGraph 会再根据这个字段对应的 reducer,完成真正的状态合并。

因此,写 Node 时不需要把完整 State 全部重新返回。

比如当前 State 有:

复制代码
package_id
origin
destination
status
history
total_distance
priority

揽收节点只修改:

复制代码
status
history

那么只需要:

复制代码
return {
    "status": "已揽收",
    "history": ["完成揽收"]
}

其他字段仍然会继续保留。

如果这个节点只是打印日志,不修改任何 State,可以:

复制代码
def log_node(state: PackageState):

    print(state["package_id"])

    return {}

所以我们可以把 Node 的写法总结成一句话:

函数体负责"计算",return 负责告诉 LangGraph"哪些 State 需要更新"。


五、Node 写好了,为什么还要 add_node?

例如我们已经定义:

复制代码
def receive_package(state):
    ...

但这个时候,它还只是一个普通 Python 函数。

LangGraph 并不知道:

它是不是 Graph 的节点;

节点叫什么;

其他节点应该怎么连接它。

因此还需要:

复制代码
delivery.add_node("揽收站", receive_package)

这里实际上完成了一次绑定:

复制代码
节点名称:揽收站
        ↓
执行函数:receive_package

后面 Edge 使用的:

复制代码
"揽收站"

实际上就是这个节点的唯一标识。


六、Edge:决定数据下一步去哪里

节点定义完以后,还需要告诉 LangGraph:

每个节点执行完成之后,下一步应该去哪个节点?

这就是 Edge。

例如:

复制代码
delivery.add_edge(START, "揽收站")
delivery.add_edge("揽收站", "分拣中心")

表示:

复制代码
START
  ↓
揽收站
  ↓
分拣中心

这种路线永远不会变化,因此属于:

固定边。

但是实际业务中,并不是所有流程都是固定的。

例如:

普通包裹:

复制代码
分拣中心
   ↓
标准配送

加急包裹:

复制代码
分拣中心
   ↓
加急配送

这时候就需要:

复制代码
add_conditional_edges()

根据当前 State 动态决定下一跳。

这也是 LangGraph 相比普通顺序函数调用非常重要的能力之一:

Graph 的执行路径,可以由当前状态动态决定。


七、最后一步:compile + invoke

所有 State、Node、Edge 都设计好以后:

复制代码
delivery_system = delivery.compile()

到这里,前面的"图设计"才真正变成一个:

可以运行的 LangGraph 工作流。

之后:

复制代码
result = delivery_system.invoke(package)

就会从:

复制代码
START

开始运行。

然后依次经过:

复制代码
揽收
↓
分拣
↓
路线判断
↓
运输
↓
派送
↓
END

整个过程中,同一个 State 会不断被各个节点读取和更新。


写在最后

如果第一次学习 LangGraph 时感觉概念特别多,其实不用急着记各种 API。

先把最核心的一条主线记住:

State → Node → Edge → Compile → Invoke

其中:

State:数据是什么

Node:数据怎么处理

Edge:下一步去哪

Compile:把整张图组装起来

Invoke:真正开始执行

如果再压缩成一句话:

LangGraph = 用 State 保存数据,用 Node 处理数据,用 Edge 控制流程。

这就是整个 LangGraph 最基础的骨架。

后面的:

  • 条件路由
  • 循环
  • Agent
  • Tool Calling
  • Checkpoint
  • Memory
  • 多 Agent

本质上都是在这套骨架上不断增加能力。

所以刚开始学习 LangGraph 时,我并不建议一上来就直接研究复杂 Agent。

先自己真正写明白一次:

复制代码
START
 ↓
Node
 ↓
条件分支
 ↙   ↘
Node Node
 ↘   ↙
 END

再去看复杂案例,会容易很多。

如果你也正在学习:

Python / LangChain / LangGraph / AI Agent / 大模型应用开发

可以收藏这篇文章。

以后忘记 LangGraph 的基本开发流程时,只需要回来记住这一条:

State → Node → Edge → Compile → Invoke。

相关推荐
IvorySQL2 小时前
PostgreSQL 日报 |RLS 机密性绕过漏洞(9 月 27 日)
数据库·postgresql
夕除2 小时前
redis--RDB AOF
数据库·redis·缓存
用户250458106092 小时前
数据库索引为什么会失效?从执行计划理解 SQL 性能优化
sql·mysql
Web极客码3 小时前
抹平首包开销:我用 Claude 调优 Nginx 的 Brotli 与 Gzip 动态压缩,平衡 CPU 与网络吞吐
服务器·agent 工作流·agent 图
数据工匠老o3 小时前
"能用应用层解决的不用存储过程",十年数据库经验总结
数据库·架构
AC赳赳老秦3 小时前
公开音频转写信息提取:OpenClaw 处理发布会与听证会文本并提取核心决策信息
大数据·开发语言·汇编·数据库·人工智能·deepseek·openclaw
此时不提桶,更待何时3 小时前
03-04-B-连接池与DB治理面试与生产事故实战
mysql·面试
老纪的技术唠嗑局3 小时前
异步索引特性解析:OceanBase 如何提升持续写入场景下的索引检索性能
数据库
云杂项3 小时前
tmux指南:安装、配置与高效使用(Ubuntu上)
linux·服务器·ubuntu