LangGraph 入门实战(2)

149 阅读8分钟

LangGraph 入门实战:用 StateGraph 搭建一条可视化状态流

LangGraph 适合用来描述“多个步骤按照既定关系流转,并在步骤之间共享状态”的程序。它经常被用于 AI Agent、工作流编排和多步骤任务,但理解 LangGraph 并不需要先接入大模型。

本文从一个完全确定、无需 API Key 的线性流程开始,使用 StateGraph 串联三个节点:

START -> step1 -> step2 -> step3 -> END

程序会依次修改共享状态、返回最终结果,并将图结构保存为 PNG 图片。

1. 运行环境

本文代码的实际运行环境如下:

Python 3.14.7
LangGraph 1.2.10

建议先为项目创建虚拟环境,避免不同项目的依赖互相影响:

python -m venv .venv
source .venv/bin/activate
python -m pip install -U langgraph typing-extensions

在 Windows PowerShell 中,激活命令为:

.venv\Scripts\Activate.ps1

2. 认识 LangGraph 的核心概念

在编写代码之前,先了解示例中使用的几个概念。

概念作用
State整个流程共享的数据
Node接收状态并返回状态更新的处理函数
Edge指定节点之间的流转关系
START图的固定入口
END图的固定出口
compile将图定义编译为可执行对象
invoke使用初始状态执行一次完整流程

可以把 State 理解为一份在节点之间传递的数据表。每个节点读取需要的字段,并返回本次需要更新的字段。

3. 定义共享状态

示例使用 TypedDict 定义状态结构:

from typing_extensions import TypedDict


class State(TypedDict):
    value1: str
    value2: str
    value3: str

这表示流程状态中包含三个字符串字段:value1value2value3

TypedDict 能为编辑器和类型检查工具提供字段提示,但它不是运行时数据校验器。如果需要在运行时校验数据,还可以根据项目需求使用 Pydantic 模型定义状态。

4. 编写三个处理节点

LangGraph 节点可以是普通 Python 函数。函数接收当前状态,并返回需要写回状态的字典。

step1:更新 value1

def step1(state: State):
    return {"value1": state["value1"] + "这是step1;"}

输入 value1初始值 时,节点返回:

{"value1": "初始值这是step1;"}

节点只返回了 value1。LangGraph 会把它合并到当前状态中,未返回的 value2value3 会继续保留。

step2:读取 step1 的结果

def step2(state: State):
    v = state["value1"]
    return {"value2": f"{v}; 这是step2"}

step2 读取的是 step1 更新后的 value1,然后生成 value2。这正是共享状态的价值:后续节点可以直接访问前面节点的执行结果。

step3:组合两个字段

def step3(state: State):
    v1 = state["value1"]
    v2 = state["value2"]
    return {"value3": f"{v1} : {v2}"}

最后一个节点同时读取 value1value2,将组合结果写入 value3

5. 创建 StateGraph 并注册节点

from langgraph.graph import START, StateGraph


graph_builder = StateGraph(State)
graph_builder.add_node(step1).add_node(step2).add_node(step3)

StateGraph(State) 告诉 LangGraph,这张图中的所有节点都使用前面定义的 State 作为共享状态结构。

add_node 用于注册节点。没有显式传入名称时,LangGraph 会使用函数名作为节点名,因此这里得到的节点名分别是:

step1
step2
step3

add_node 会返回当前图构建器,所以可以像示例一样进行链式调用。

6. 使用边定义执行顺序

graph_builder.add_edge(START, "step1")
graph_builder.add_edge("step1", "step2")
graph_builder.add_edge("step2", "step3")

三条边定义了完整的执行顺序:

  1. START 进入 step1
  2. step1 完成后执行 step2
  3. step2 完成后执行 step3

当前示例中,step3 没有后继节点,因此编译后的图会把它作为流程终点。为了让大型项目中的流程定义更加明确,也可以显式连接 END

from langgraph.graph import END

graph_builder.add_edge("step3", END)

7. 编译并执行图

图的结构定义完成后,需要先调用 compile()

graph = graph_builder.compile()

编译会生成一个可执行图。随后通过 invoke() 传入初始状态:

res = graph.invoke(
    {
        "value1": "初始值",
        "value2": "",
        "value3": "",
    }
)
print("执行结果:", res)

一次 invoke() 会从 START 开始运行,直到流程结束,并返回最终完整状态。

8. 状态是如何变化的

这次执行过程中,状态依次发生如下变化:

阶段value1value2value3
初始状态初始值空字符串空字符串
step1 后初始值这是step1;空字符串空字符串
step2 后初始值这是step1;初始值这是step1;; 这是step2空字符串
step3 后初始值这是step1;初始值这是step1;; 这是step2初始值这是step1; : 初始值这是step1;; 这是step2

这里的两个连续分号 ;; 来自原始代码:step1 的结果末尾已经有一个分号,step2 又在变量后追加了一个分号。这不是 LangGraph 重复执行造成的。如果希望输出更整洁,可以调整节点中的字符串拼接方式。

9. 将图结构导出为 PNG

可执行图可以转换为 Mermaid 图,再渲染为 PNG:

png_bytes = graph.get_graph().draw_mermaid_png()

with open("langgraph_chain.png", "wb") as f:
    f.write(png_bytes)

代码执行后,会在当前目录生成 langgraph_chain.png

langgraph_chain.png

draw_mermaid_png() 默认可能需要访问 Mermaid 渲染服务。如果图片生成阶段出现网络错误,可以先检查网络连接;在对外网络受限的环境中,也可以改用本地 Mermaid 渲染方案。

10. 完整代码

下面是本文实际运行的完整代码:

from langgraph.graph import START, StateGraph
from typing_extensions import TypedDict


class State(TypedDict):
    value1: str
    value2: str
    value3: str


def step1(state: State):
    return {"value1": state["value1"] + "这是step1;"}


def step2(state: State):
    v = state["value1"]
    return {"value2": f"{v}; 这是step2"}


def step3(state: State):
    v1 = state["value1"]
    v2 = state["value2"]
    return {"value3": f"{v1} : {v2}"}


# 构建流程图
graph_builder = StateGraph(State)
graph_builder.add_node(step1).add_node(step2).add_node(step3)

# 线性边流转
graph_builder.add_edge(START, "step1")
graph_builder.add_edge("step1", "step2")
graph_builder.add_edge("step2", "step3")

# 编译图
graph = graph_builder.compile()

# 绘制结构图
png_bytes = graph.get_graph().draw_mermaid_png()
with open("langgraph_chain.png", "wb") as f:
    f.write(png_bytes)

# 执行流程
res = graph.invoke({"value1": "初始值", "value2": "", "value3": ""})
print("执行结果:", res)

说明:项目当前的 index.py 不需要 IPython.display,因此实际源文件已经移除了对应导入。如果准备在 Jupyter Notebook 中直接展示图片,则可以使用:

from IPython.display import Image, display

display(Image(png_bytes))

11. 运行代码和真实输出

进入代码所在目录后执行:

python index.py

本文在本地实际运行得到的控制台输出为:

执行结果: 
{'value1': '初始值这是step1;', 
'value2': '初始值这是step1;; 这是step2',
'value3': '初始值这是step1; : 初始值这是step1;; 这是step2'}

同时,目录中会生成:

langgraph_chain.png

12. 常见问题

12.1 Pylance 提示无法解析 langgraph.graph

通常是 VS Code 选择的 Python 解释器与安装依赖时使用的解释器不一致。先在终端确认:

python -c "import sys; print(sys.executable)"
python -m pip show langgraph

然后在 VS Code 中执行 Python: Select Interpreter,选择当前项目的 .venv/bin/python

12.2 为什么节点只返回一个字段,最终结果却包含全部字段

节点返回的是状态更新,不是用新字典替换整个状态。LangGraph 会按照 State 中每个字段对应的更新规则合并结果。在本例中,普通字符串字段会被节点返回的新值覆盖,其他未返回字段保持不变。

12.3 为什么需要 compile

构建阶段只是在描述节点和边。compile() 会检查图结构并生成真正可调用的运行对象。只有编译后的 graph 才能执行 invoke()、生成图结构或接入检查点等运行能力。

12.4 invoke() 和直接调用三个函数有什么区别

对于三个固定步骤,直接依次调用函数当然也能实现相同结果。LangGraph 的价值在流程变复杂后更加明显,例如:

  • 根据状态进行条件分支;
  • 某些步骤循环执行;
  • 并行执行多个节点;
  • 保存检查点并恢复流程;
  • 在人工确认后继续运行;
  • 将大模型、工具调用和业务节点组合成 Agent。

当前这个线性示例建立了最重要的基础:状态、节点、边、编译和执行。掌握这五个概念后,就可以继续学习条件边、持久化和人工介入等能力。

总结

使用 LangGraph 创建一个基础工作流,可以归纳为五步:

  1. 使用 TypedDict 等类型定义共享状态。
  2. 编写接收状态、返回状态更新的节点函数。
  3. 使用 StateGraph 注册节点。
  4. 使用边描述节点之间的流转关系。
  5. 编译图并通过 invoke() 执行。

这个示例虽然简单,但已经完整展示了 LangGraph 的核心运行模型。后续无论是增加条件判断、接入大模型,还是实现可恢复的 Agent,本质上都是在这套“状态 + 节点 + 边”的结构上继续扩展。