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 实现对话历史的自动追溯。

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

相关推荐
3824278272 小时前
JS正则表达式实战:核心语法解析
开发语言·前端·javascript·python·html
Engineer邓祥浩2 小时前
设计模式学习(10) 23-8 装饰者模式
python·学习·设计模式
ybdesire2 小时前
Joern服务器启动后cpgqls-client结合python编程进行扫描
运维·服务器·python
autho2 小时前
conda
linux·python·conda
viperrrrrrrrrr72 小时前
DeepSeek-V4架构详解
microsoft·deepseek-v4
知乎的哥廷根数学学派2 小时前
基于注意力机制的多尺度脉冲神经网络旋转机械故障诊断(西储大学轴承数据,Pytorch)
人工智能·pytorch·python·深度学习·神经网络·机器学习
测试19982 小时前
用Postman测WebSocket接口
自动化测试·软件测试·python·websocket·测试工具·接口测试·postman
l1t2 小时前
数独优化求解C库tdoku-lib的使用
c语言·开发语言·python·算法·数独
小二·3 小时前
Python Web 开发进阶实战:微前端架构初探 —— 基于 Webpack Module Federation 的 Vue 微应用体系
前端·python·架构