LangGraph 概述与入门
如果你用过 LangChain 框架,那么大概率也听说过 LangGraph——甚至很多文章说 LangGraph 比 LangChain 更重要,更应该优先学习。那么 LangGraph 到底是什么?它和 LangChain 是什么关系?什么时候该用它?
LangGraph 是什么?
从字面上看,“Graph” 是”图、流程图”的意思。但 LangGraph 并不是画图工具,而是一个图编排框架,它把 AI 工作流建模为一张状态图(StateGraph)。这张图由三个核心概念构成:
- 节点(Node):负责执行具体的动作,可以理解为一个个操作函数;
- 边(Edge):控制逻辑的流转方向,把各个节点串联起来;
- 状态(State):在节点之间传递和更新的数据。
了解了这三个要素,LangGraph 的定位就清晰了:LangChain 的关键词是”链”,它把模型、提示词、工具等基础能力串成一条链,封装成一个 Agent;而这个 Agent 就可以作为 LangGraph 图里的一个节点。通过边把多个 Agent 节点连接起来,就构成了一个更大的工作流——这就是 LangGraph 的核心作用。
需要注意的是,LangGraph 并不强制依赖 LangChain,它可以独立运行。只不过在实际项目中,图中的节点经常需要调用 LangChain 的模型、Prompt 和工具,所以两者通常是搭配使用的。
为什么需要 LangGraph?
了解了 LangGraph 是什么之后,一个自然的问题是:有了 LangChain 为什么还要 LangGraph?
根本原因在于,实际项目中的工作流很少是”一条直线”走到底的。
直线流程 vs 非直线流程
什么样的流程是直线?比如一个简单的 RAG 项目:
flowchart LR
A[用户提问] --> B[向量检索]
B --> C[LLM 生成答案]
这种线性流程,LangChain 的 Chain 足以胜任。
但更多项目包含以下三类”非直线”元素:
- 条件分支 —— 下一步是继续搜索、重写草稿、调整结构,还是直接提交?
- 循环回退 —— 如果结果不够好,能不能回到前面的步骤再跑一轮?
- 状态驱动决策 —— 下一步怎么走,不靠硬编码,而是根据当前草稿、检索结果、审核结论等状态来决定。
举个例子,开发一个代码助手,它的流程大致是:
flowchart TD
A[用户提问] --> B[分析需求]
B --> C[生成代码]
C --> D[运行测试]
D --> E{测试通过?}
E -->|否| F[分析失败原因]
F --> G[修复代码]
G --> D
E -->|是| H[返回结果]
这是一个带循环和条件分支的流程,显然一条直线无法表达。当然,用 LangChain + 手写胶水代码也能实现,但代价不小:
- 状态管理 —— 测试结果、错误信息、重试次数……都得自己维护
- 中断与恢复 —— 想在修复后停下来让人审一下?自己写持久化和恢复逻辑
- 多 Agent 通信 —— 多个 Agent 协作时,消息传递全靠自己拼
- 调试困难 —— 流程一复杂,日志和断点基本失控
- 维护成本 —— if/while 越堆越多,代码最终变成”面条式”逻辑
LangGraph 的解法
LangGraph 把流程拆解为三个要素:
| 要素 | 作用 | 类比 |
|---|---|---|
| Node(节点) | 执行具体逻辑 | 一个函数 |
| Edge(边) | 控制流转方向 | if/else 判断 |
| State(状态) | 在节点间传递数据 | 全局共享上下文 |
代码里就是定义几个节点函数,它们共享同一个 State:
class CodeState(TypedDict):
question: str
code: str
test_result: str
attempts: int
def generate(state: CodeState) -> dict:
# 根据 question 生成代码
return {"code": generated_code}
def run_test(state: CodeState) -> dict:
# 运行测试,返回结果
return {"test_result": result}
def fix_code(state: CodeState) -> dict:
# 根据 test_result 修复代码
return {"code": fixed_code}
State 在各个节点间自动传递,不需要手写 while 循环来重试,也不需要手写 if/else 来分流——把这些逻辑声明在边上,框架自动调度。
对比手写胶水代码,LangGraph 带来的好处是:
- 声明式定义流程 —— 节点只管业务逻辑,边的条件写在配置里,一目了然
- 内置状态管理 —— 状态自动传递和更新,不用自己维护上下文
- 原生支持循环与分支 —— 图结构天然可以表达复杂拓扑
- 可检查与可恢复 —— 每一步都可以持久化、中断、回放,调试和容错都更方便
Hello World 示例
说了这么多理论,来看一个具体的例子。我们不接入 LLM,纯粹用 LangGraph 把一个字符串拼成 "hello, world",借此了解它的完整使用流程。
安装
# 需要安装 langgraph,我这里使用 uv 安装
uv add langgraph
代码
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
class MyState(TypedDict):
"""整个工作流共享的状态"""
val: str
def add_hello(state: MyState):
return {"val": state["val"] + "hello"}
def add_comma(state: MyState):
return {"val": state["val"] + ", "}
def add_world(state: MyState):
return {"val": state["val"] + "world"}
state_graph = StateGraph(MyState)
state_graph.add_node(add_hello)
state_graph.add_node(add_comma)
state_graph.add_node(add_world)
state_graph.add_edge(START, "add_hello")
state_graph.add_edge("add_hello", "add_comma")
state_graph.add_edge("add_comma", "add_world")
state_graph.add_edge("add_world", END)
app = state_graph.compile()
result = app.invoke({"val": ""})
print(result) # {'val': 'hello, world'}
逐段拆解
1. 定义 State
class MyState(TypedDict):
val: str
TypedDict 是 Python 自带类型,用来声明 State 的字段。这里的 val 就是整个流程中不断累积的字符串。每个节点都会收到这个状态,也可以选择更新它。
2. 定义 Node(节点)
def add_hello(state: MyState):
return {"val": state["val"] + "hello"}
节点就是一个纯函数:接收当前 State,返回要更新的字段。返回值会和当前 State 合并(类似 dict.update),所以只返回你想改的字段即可。
三个节点做的事情完全一样——在 val 后面追加一段文本。
3. 构建 Graph
state_graph = StateGraph(MyState)
先创建一个 StateGraph 实例,传入 State 类型。然后:
add_node()注册节点,节点名默认等于函数名;add_edge()定义边的走向,START是入口,END是终点。
整个图的结构一目了然:
flowchart LR
START --> add_hello --> add_comma --> add_world --> END
4. 编译与执行
app = state_graph.compile()
result = app.invoke({"val": ""})
compile() 会把图定义编译成可执行对象,invoke() 传入初始状态并触发执行。框架会按照边的定义依次调度节点,状态自动流动:
初始: {"val": ""}
→ add_hello: {"val": "hello"}
→ add_comma: {"val": "hello, "}
→ add_world: {"val": "hello, world"}
小结
这个例子虽然简单,但它完整展示了 LangGraph 的四个核心步骤:
| 步骤 | 你的代码 | 说明 |
|---|---|---|
| 1. 定义 State | class MyState(TypedDict) | 声明工作流共享什么数据 |
| 2. 定义 Node | def add_hello(state) → 返回 dict | 每个函数是一个处理单元 |
| 3. 连接 Edge | add_edge(START, ...) / add_edge(..., END) | 声明节点间的执行顺序 |
| 4. 编译 & 调用 | compile() → invoke() | 让框架按图调度执行 |
对比前面说的”手写胶水代码”——如果不用 LangGraph,你大概会写:
val = ""
val += "hello"
val += ", "
val += "world"
这个简单的拼接看不出图的优势。但只要把节点换成 LLM 调用、数据库查询、API 请求,把直线边换成条件边和循环边,LangGraph 的威力就会显现出来。
图结构可视化
上面一直在聊”图”,其实这张图是可以直接”看”到的。LangGraph 提供了三种可视化方式:
| 方式 | 适用场景 | 稳定性 |
|---|---|---|
| ASCII 文本图 | 终端快速查看 | 本地,稳定 |
| Mermaid 代码 | 复制到编辑器二次编辑 | 本地,稳定 |
| PNG 图片 | 导出为文档配图 | 依赖渲染服务,国内可能不稳定 |
以上面的 Hello World 为例,注意需要先安装 grandalf 依赖:
pip install grandalf
方式 1:打印 ASCII 图
print(app.get_graph().print_ascii())
终端会直接输出:
+-----------+
| __start__ |
+-----------+
*
*
*
+-----------+
| add_hello |
+-----------+
*
*
*
+-----------+
| add_comma |
+-----------+
*
*
*
+-----------+
| add_world |
+-----------+
*
*
*
+---------+
| __end__ |
+---------+
最清晰直观,适合开发调试时扫一眼。
方式 2:生成 Mermaid 代码
print(app.get_graph().draw_mermaid())
输出 Mermaid 格式代码:
---
config:
flowchart:
curve: linear
---
graph TD;
__start__([__start__]):::first
add_hello(add_hello)
add_comma(add_comma)
add_world(add_world)
__end__([__end__]):::last
__start__ --> add_hello;
add_hello --> add_comma;
add_comma --> add_world;
add_world --> __end__;
classDef default fill:#f2f0ff,line-height:1.2
classDef first fill-opacity:0
classDef last fill:#bfb6fc
其实我的博客中是可以渲染 Mermaid 图的,上面章节中就有三处渲染的是 Mermaid 图。为了观看代码我去掉了格式渲染,你可以把这段代码粘贴到 Mermaid Live Editor 或 ProcessOn 中查看和二次编辑。
方式 3:生成 PNG 图片
import uuid
png_bytes = app.get_graph().draw_mermaid_png()
output_path = "langgraph_" + str(uuid.uuid4())[:8] + ".png"
with open(output_path, "wb") as f:
f.write(png_bytes)
print(f"图片已生成:{output_path}")
此方法底层调用在线渲染服务,国内网络可能不稳定,仅供导出文档配图时使用。生成效果如下:

总结
本文从三个问题出发——LangGraph 是什么、为什么需要它、怎么用——建立对 LangGraph 的基本认知。以上都只是最基础的概念入门,LangGraph 涉及的东西还是很多的,后面会持续更新相关文章。
最后想说,LangGraph 的出现并不意味着 LangChain 要被替代。Chain 处理简单线性流程时仍然是最趁手的工具;而当应用需要复杂拓扑时,LangGraph 提供了更自然、更可维护的表达方式。两者各司其职,组合使用才是生产级 AI 应用的正确姿势。
评论