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.add 等 Reducer 函数。

8 总结与扩展

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

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

相关推荐
BK贝壳5 小时前
亿级用户签到位图选型复盘:用 bool-hybrid-array 扛冷启动打卡,也把它和 Roaring/Redis 的边界测清楚了
数据结构·python·性能优化·numpy
2601_966949656 小时前
如何根据研究的起止时间截取分钟级行情?Python 实战与 QuantDash 数据处理指南
开发语言·python·pandas·量化交易·1024程序员节·股票数据·quantdash
在世修行10 小时前
智学文枢:页面切换与跨页数据预填
python·桌面应用·signal·1024程序员节·pyside·跨页跳转·状态预填
2601_9628857211 小时前
如何用 Python 把 K 线重采样成任意周期?
开发语言·python
打工仔折腾 AI11 小时前
在 Ubuntu ARM64 部署 Q1 V3.0 MuJoCo 模型:加载、单步推理与执行器响应解析
linux·运维·人工智能·python·深度学习·ubuntu·性能优化
信誓旦旦的程序猿12 小时前
【Python量化实战宝典 #21】北向资金跟随手册:聪明钱择时信号(Python 可跑)
python·1024程序员节·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口
严谨的麻辣烫12 小时前
静态 ISP 代理请求超时怎么排查?Python 与 curl 实战
python·http·requests·1024程序员节·网络排障
KeepRobots14 小时前
11-01 温哥华不回拨:用 60 行 Python 查你的工单系统会不会差一小时(KeepRobots 工程笔记)
python
xiao77mwq14 小时前
你以为 OCR 慢?实测 81.3 s 里全是故障代价:如何把「定格态」消掉,让耗时从 6.5 s 到 13 倍差距显出原形
python
深圳市益普科技有限公司14 小时前
半导体良率管理YMS:良率与设备参数的相关性,先拉哪几组数据
人工智能·python·算法