langGraph从入门到精通(四)——基于LangGraph的State状态模式设计

1 导语

在构建AI Agent时,状态(State)是连接各个节点的灵魂。LangGraph 采用消息传递机制来驱动程序的运行,而 State 则是信息的载体。本文将深入探讨 LangGraph 中最基础也最核心的部分------State的定义模式设计 。通过学习本文,你将掌握如何利用字典和 TypedDict 构建可靠的图状态,理解节点间数据流动的底层逻辑,为开发复杂多代理系统打下坚实基础。

2 技术栈清单

  • Python == 3.11.14
  • langgraph == 1.0.5
  • langchain-core == 1.2.7
  • typing-extensions == 4.15.0

3 项目核心原理

LangGraph 的核心在于状态管理 。State 本质上是一个共享的数据结构,它在图中从左向右流动。每个节点执行完毕后,会将更新的部分"广播"给状态。默认情况下,状态更新采用覆盖机制 (Overwriting),但通过 Reducer 函数 (如 operator.add),我们可以实现更复杂的逻辑,例如消息的自动追加

AI Agent应用程序的设计中,场景的复杂性直接决定了构建图的复杂度。例如,最简单的场景可能仅涉及一个大模型的问答流程,形式为:START -> Node -> END(其中大模型的交互逻辑被封装在Node中)。而更复杂的场景则可能涉及多个AI Agent的协同工作,包括多个分支和循环的构成。无论是简单还是复杂的图,LangGraph的价值永远不在于如何去定义节点,如何去定义边,而是在于如何有效管理各个节点的输入和输出,以保持图的持续运行状态LangGraph底层图算法采用消息传递机制来定义和执行这些图中的交互流程,其中状态(State)组件扮演着关键的载体角色,负责在图的各个节点之间传递信息 。这也就意味着,LangGraph框架的核心在于State的有效使用和掌握。在复杂的应用中,State组件需要存储和管理的信息量会显著增加。核心功能如工具使用、记忆能力和人机交互等,都依赖State来实现和维护。所以,接下来我们对LangGragh框架的探索,都将紧密围绕State的实现和应用机制展开,这包括LangGraph内置封装好的工具/方法的使用,以及我们自定义构建功能时的实现方法。

LangGraph构建的图中的每个节点都具备访问、读取和写入状态的权限。当某一个节点去修改状态时,它会将此信息广播到图中的所有其他节点。这种广播机制允许其他节点响应状态的变化并相应地调整其行为。如上图所示,从初始状态(Initial State)开始,其中包含了一条消息 { "x": "10" },随着消息在节点间通过边传递,每个节点根据其逻辑对状态进行更新。Node 1 和 Node 2 分别对状态进行了处理和变更,结果是在图的末端,我们得到了一个包含三条消息的最终状态 { "x": "10" }, { "x": "11" }, { "y": "9" }。**从开发的角度来看,State实际上是一个共享的数据结构。如上图所示,状态表现为一个简单的字典。通过对这个字典进行读写操作,可以实现自左而右的数据流动,从而构建一个可运行的图结构。那么根据前面学习的内容,我们可以利用这个流程来复现并理解图中的动态数据交换,整体的设计如下:

4 实战步骤

4.1 环境准备

在开始代码实现前,确保安装了核心依赖:

bash 复制代码
pip install langgraph==1.0.5 typing-extensions==4.15.0

4.2 代码实现

我们通过两个示例对比"字典模式"与"TypedDict模式"。代码保存在 02-state-schema.py

python 复制代码
import operator
from typing import Annotated
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict

# 1. 使用字典类型定义状态 (灵活但缺乏约束)
def test_dict_state():
    # 初始化一个以字典为状态模式的图
    builder = StateGraph(dict)
    
    def addition(state):
        # 节点仅返回需要更新的部分,LangGraph 内部会自动合并
        return {"x": state["x"] + 1}

    def subtraction(state):
        # 新增一个键 y
        return {"y": state["x"] - 2}

    builder.add_node("addition", addition)
    builder.add_node("subtraction", subtraction)
    builder.add_edge(START, "addition")
    builder.add_edge("addition", "subtraction")
    builder.add_edge("subtraction", END)

    graph = builder.compile()
    result = graph.invoke({"x": 10})
    print(f"字典状态执行结果: {result}")

# 2. 使用 TypedDict 定义状态 (推荐:强类型约束)
class State(TypedDict):
    x: int
    y: int

def test_typeddict_state():
    builder = StateGraph(State)

    def addition(state: State):
        # 关键逻辑:读取当前状态 x 并加 1
        return {"x": state["x"] + 1}

    def subtraction(state: State):
        # 关键逻辑:基于更新后的 x 计算 y
        return {"y": state["x"] - 2}

    builder.add_node("addition", addition)
    builder.add_node("subtraction", subtraction)
    builder.add_edge(START, "addition")
    builder.add_edge("addition", "subtraction")
    builder.add_edge("subtraction", END)

    graph = builder.compile()
    result = graph.invoke({"x": 10})
    print(f"TypedDict状态执行结果: {result}")

# 3. 使用 Reducer 实现列表追加 (进阶:解决状态覆盖问题)
class ReducerState(TypedDict):
    # 使用 Annotated 标注该字段的更新方式为 operator.add (追加而非覆盖)
    messages: Annotated[list[str], operator.add]

def test_reducer_state():
    builder = StateGraph(ReducerState)

    def node_1(state: ReducerState):
        # 节点 1 返回第一条消息
        return {"messages": ["这是来自节点1的消息"]}

    def node_2(state: ReducerState):
        # 节点 2 返回第二条消息
        return {"messages": ["这是来自节点2的消息"]}

    builder.add_node("node_1", node_1)
    builder.add_node("node_2", node_2)
    builder.add_edge(START, "node_1")
    builder.add_edge("node_1", "node_2")
    builder.add_edge("node_2", END)

    graph = builder.compile()
    # 初始状态带有一条消息
    result = graph.invoke({"messages": ["初始消息"]})
    print(f"Reducer(追加模式)执行结果: {result}")

if __name__ == "__main__":
    test_dict_state()
    test_typeddict_state()
    test_reducer_state()

4.3 功能测试

运行脚本,观察两种模式下状态是如何在节点间传递并完成计算的。

5 核心代码解析

5.1 StateGraph(dict) 灵活模式

python 复制代码
builder = StateGraph(dict)
  • 核心作用:定义了一个没有任何模式约束的图。
  • 关联逻辑 :这种方式下,节点可以自由增加或修改任何键值。虽然灵活性极高,但在大型项目中容易导致键名冲突或类型错误。

5.2 TypedDict 强约束模式

划重点:这是工业级开发的标准选择。

python 复制代码
class State(TypedDict):
    x: int
    y: int
  • 核心作用:通过 Python 的类型提示(Type Hinting)显式声明状态结构。
  • 为何这样实现StateGraph(State) 允许 LangGraph 在编译阶段(Compile Time)进行初步校验。解析重点在于它规定了节点返回的字典必须符合声明的 Key 和 Value 类型,有效防止了动态运行时因为访问不存在的键而导致的中断。

5.3 Annotated 与 Reducer 机制

划重点:这是实现对话记忆(Memory)的底层技术。

python 复制代码
messages: Annotated[list[str], operator.add]
  • 核心作用 :告诉 LangGraph,当多个节点向 messages 写入数据时,不要覆盖,而是将结果 add(追加)到一起。
  • 关联逻辑:这使得我们可以轻松追踪对话历史。每个节点只需要返回当前产生的消息,LangGraph 会自动维护一个完整的消息链条。

6 效果验证

通过 graph.invoke() 传递初始状态后,系统会顺序执行各节点,并返回合并后的最终状态字典。

【图片占位符:核心功能运行效果截图 - 终端显示 x 与 y 的最终合并结果】

7 踩坑记录

7.1 KeyDoesNotExist 错误

  • 错误现象KeyError: 'x'
  • 根因分析 :在节点函数中尝试访问 state["x"],但初始输入或前置节点未提供该键。
  • 解决方案 :确保在 invoke 时传入完整的必要字段,或在 TypedDict 中通过 total=False 设置可选键。

7.2 节点返回值类型不匹配

  • 错误现象 :状态更新后数据类型从 int 变成了 str
  • 根因分析 :节点返回了错误类型的值,虽然 TypedDict 有提示但 Python 运行时默认不强制校验。
  • 解决方案亲测有效 的方案是配合 Pydantic 进行更严格的运行时校验,或者在节点内进行类型转换。

7.3 状态覆盖问题

  • 错误现象:后一个节点的返回覆盖了前一个节点的同名键。
  • 根因分析:LangGraph 默认使用"覆盖"机制。
  • 解决方案 :若需保留历史记录(如对话历史),必须使用 Annotated 配合 operator.addReducer 函数。

8 总结与扩展

掌握 State 的定义模式 是开启 LangGraph 高阶开发的大门。通过 TypedDict 我们建立了一套可预测的数据契约。划重点 :状态在任何给定时间只包含来自一个节点的更新信息,但 LangGraph 内部的合并机制让它看起来像一个全局共享池。下一步,我们将研究如何通过 Annotatedadd_messages 实现对话历史的自动追溯。

欢迎评论区留言讨论核心主题相关的问题~

相关推荐
用户8356290780514 分钟前
使用 Python 设置 Excel 数据验证
后端·python
Nick_zcy18 分钟前
小说在线阅读网站和小说管理系统 · 功能全解析
java·后端·python·springboot·ruoyi
*Lisen24 分钟前
从零手写 FlashAttention(PyTorch实现 + 原理推导)
人工智能·pytorch·python
用户83562907805140 分钟前
用 Python 轻松在 Excel 工作表中应用条件格式
后端·python
red1giant_star44 分钟前
Python根据文件后缀统计文件大小、找出文件位置(仿Everything)
后端·python
ZhiqianXia1 小时前
《The Design of Design》阅读笔记
前端·笔记·microsoft
雷欧力1 小时前
如何使用 Claude API?3 种接入方案实测,附完整代码(2026)
python·claude
神仙别闹1 小时前
基于 Python 实现 BERT 的情感分析模型
开发语言·python·bert
NQBJT1 小时前
VS Code配置Python人工智能开发环境
开发语言·人工智能·vscode·python
浮游本尊1 小时前
一文讲透巡检链路:采集程序 → 上传数据包 → 后端解析入库 → 分析出报告
python