03_状态管理和节点
深入理解LangGraph的核心机制:状态管理。本节课将详细讲解State的高级用法,包括列表、嵌套字典、状态合并策略等。通过实战案例,学会如何设计合理的状态结构,以及节点间如何安全高效地共享数据。
🎯 学习目标
学完本节课,你将能够:
- 掌握State的高级类型定义
- 理解状态的合并和累积机制
- 学会设计合理的状态结构
- 实现带历史记忆的多轮对话
- 避免常见的状态管理陷阱
1. 状态的本质
1.1 状态是什么?
回顾: 在Lesson 02中,我们把状态比作快递包裹。现在让我们更深入地理解它。
状态 = 流程的记忆
想象你在餐厅点餐:
- 服务员记下你的菜单(状态:订单)
- 厨房根据订单做菜(读取状态)
- 菜做好了,更新订单状态(修改状态)
- 服务员看到状态变化,上菜(响应状态)
在LangGraph中:
1 | state = { |
1.2 状态的生命周期
1 | [初始状态] → [节点1修改] → [节点2修改] → [节点3修改] → [最终状态] |
关键点:
- 状态在整个流程中持续存在
- 每个节点都能读取和修改状态
- 状态的修改会传递到下一个节点
2. 基础状态类型
2.1 简单类型
1 | from typing import TypedDict |
2.2 列表类型
1 | from typing import TypedDict, List |
示例:累积对话历史
1 | def chat_node(state: State) -> State: |
2.3 字典类型
1 | from typing import TypedDict, Dict, Any |
示例:存储用户信息
1 | def collect_info_node(state: State) -> State: |
2.4 可选字段
使用 NotRequired 标记可选字段:
1 | from typing import TypedDict |
好处:
- IDE不会警告未初始化的可选字段
- 代码更清晰,一眼看出哪些是必需的
3. 状态的合并策略
3.1 默认策略:覆盖
默认情况下,同名字段会被覆盖:
1 | def node1(state: State) -> State: |
3.2 累积策略:列表追加
对于列表,通常希望追加而不是覆盖:
1 | from typing import TypedDict, List, Annotated |
效果:
1 | def node1(state: State) -> State: |
3.3 自定义合并策略
1 | from typing import TypedDict, Annotated |
4. 实战:多轮对话机器人
4.1 需求分析
构建一个记住上下文的对话机器人:
- 用户可以多次提问
- AI能记住之前的对话
- 可以引用历史信息
4.2 代码实现
创建 01_chat_with_memory.py:
1 | """ |
4.3 运行效果
1 | python 01_chat_with_memory.py |
预期输出:
1 | ================================================== |
4.4 代码要点解析
1️⃣ 列表自动累积
1 | # 关键:使用Annotated + add |
这样定义后:
1 | # 节点1 |
2️⃣ 状态在多次invoke间传递
1 | # 第一次调用 |
核心理念: 状态是持续累积的,不是每次都从零开始。
5. 状态设计最佳实践
5.1 原则1:扁平化优于嵌套
❌ 过度嵌套(不推荐):
1 | class State(TypedDict): |
✅ 扁平化(推荐):
1 | class State(TypedDict): |
5.2 原则2:明确字段用途
✅ 清晰的命名:
1 | class State(TypedDict): |
❌ 模糊的命名:
1 | class State(TypedDict): |
5.3 原则3:只存必要数据
❌ 存储过多:
1 | def node(state: State) -> State: |
✅ 只存必要数据:
1 | def node(state: State) -> State: |
5.4 原则4:使用类型注解
✅ 完整的类型注解:
1 | from typing import TypedDict, List, Dict, Optional |
5.5 原则5:分离输入和输出
1 | class State(TypedDict): |
6. 高级示例:信息收集机器人
6.1 需求
构建一个信息收集机器人:
- 依次询问:姓名、年龄、城市
- 收集完毕后,生成总结
- 可以看到收集进度
6.2 实现
创建 02_info_collector.py:
1 | """ |
6.3 运行效果
1 | ================================================== |
6.4 关键技术点
1️⃣ 循环流程
1 | # collect节点执行完后,回到determine节点 |
2️⃣ 条件路由
1 | def route_next(state: State) -> str: |
3️⃣ 状态驱动
整个流程由状态驱动:
to_collect- 定义要收集什么collected_info- 记录已收集的内容is_complete- 决定是否结束
7. 常见陷阱和解决方案
陷阱1:忘记返回state
❌ 错误:
1 | def my_node(state: State): |
✅ 正确:
1 | def my_node(state: State) -> State: |
陷阱2:直接修改列表引用
❌ 错误:
1 | def my_node(state: State) -> State: |
✅ 正确:
1 | def my_node(state: State) -> State: |
陷阱3:状态字段过多
❌ 过度设计:
1 | class State(TypedDict): |
✅ 分组设计:
1 | class UserInfo(TypedDict): |
陷阱4:在state中存储函数或对象
❌ 不要这样做:
1 | def my_node(state: State) -> State: |
✅ 正确做法:
1 | # 在节点外部创建 |
8. 练习题
练习1:购物车(基础)
实现一个购物车状态管理:
- 可以添加商品(名称、价格)
- 可以删除商品
- 计算总价
练习2:问答机器人(进阶)
实现一个三轮问答:
- 第1轮:问”你的爱好是什么?”
- 第2轮:问”为什么喜欢这个爱好?”
- 第3轮:根据前两轮的回答,给出建议
练习3:表单验证(高级)
实现一个带验证的表单收集:
- 收集:用户名、邮箱、手机号
- 验证:
- 用户名:3-20个字符
- 邮箱:包含@和.
- 手机号:11位数字
- 如果验证失败,重新询问该字段
9. 小结
核心要点
✅ 状态的本质: 流程的记忆,在节点间传递数据
✅ 状态类型:
- 简单类型:str, int, bool
- 复合类型:List, Dict
- 可选字段:NotRequired
✅ 合并策略:
- 默认:覆盖
- 列表:使用
Annotated[List[T], add]自动追加 - 自定义:实现自己的合并函数
✅ 设计原则:
- 扁平化优于嵌套
- 明确字段用途
- 只存必要数据
- 使用类型注解
- 分离输入和输出
✅ 常见陷阱:
- 忘记返回state
- 直接修改列表引用
- 状态字段过多
- 存储不可序列化的对象
📚 下一步
现在你已经掌握了状态管理的核心技能!但流程控制不止简单的顺序执行,还需要条件分支、循环、并行等高级功能。
下一课: Lesson 04: 图的流转控制
我们将学习:
- 条件边:根据结果选择不同路径
- 循环:让流程回到之前的节点
- 并行:同时执行多个节点
- 子图:组织复杂的流程
📖 参考资料
- LangGraph State管理文档
- Python TypedDict文档
- typing_extensions文档