46 LangGraph State:设计可恢复的 Agent 状态
LangGraph State:设计可恢复的 Agent 状态
State 是 Agent 节点之间共享的数据契约。它决定任务能否暂停、恢复、测试和排查。
[TOC]
1. 先把 Agent 想清楚
很多人一听到 Agent,就会想到“让大模型自己决定下一步”。这个理解只对了一半。
在真实项目里,Agent 更像是一个被工程规则约束住的任务执行器:
模型负责理解自然语言、提出候选动作
代码负责权限校验、状态流转、工具执行
业务接口负责最终事实和写操作
日志审计负责事后追踪
如果所有东西都塞进一个 Map,后面每个节点都能随便改字段,Agent 很快会变成无法维护的状态泥潭。
所以这一阶段的博客不要写成“模型越来越聪明”。更准确的写法是:我们正在学习如何把模型的灵活性放进一个可控的工作流里。
2. 一个业务场景
用户创建工单时,流程停在“等待确认”。如果服务重启,系统必须知道用户是谁、要执行什么动作、冻结参数是什么、下一步从哪里继续。
这个场景里面最重要的不是模型能不能说出一段好听的话,而是:
它选了哪个工具
为什么能调用这个工具
参数从哪里来
权限在哪里校验
失败后状态怎么变
用户确认前会不会产生真实写入
把这些问题问清楚,Agent 项目才不会变成一个不可解释的聊天黑盒。
这一节要先问:为什么 Agent 需要状态?
判断顺序可以这样写:
1. 先列出一次售后任务中会跨节点传递的信息:用户问题、意图、订单、工具结果、确认状态
2. 把必须稳定传递的信息放进 state,不把临时解释性文本当核心状态
3. 状态字段要能支持恢复、重试、人工介入和审计
4. 写操作前的关键状态必须明确,例如 pendingConfirmation、ticketDraft、idempotencyKey
5. 最后用一条完整链路检查:每个节点读什么、写什么、为什么写
它解决的是流程可恢复问题:没有状态,Agent 只是在连续聊天;有清晰状态,才是可控任务流。
3. 核心概念
1. State 字段
保存跨节点需要共享的信息,例如用户输入、意图、实体、工具结果、待确认动作和最终答案。
这一点在项目里要落到具体规则:State 是节点契约。
2. Reducer
多个节点更新同一字段时,需要明确追加、覆盖还是合并。
这一点在项目里要落到具体规则:更新规则要明确。
3. Checkpoint
把状态持久化,支持暂停、恢复和重试。
这一点在项目里要落到具体规则:恢复依赖检查点。
4. 版本管理
State 结构会升级,旧检查点要能迁移或安全失效。
这一点在项目里要落到具体规则:状态也要版本化。
4. 整体流程
graph TD;
A["初始化 State"] --> B["意图识别写入 intent"]; B["意图识别写入 intent"] --> C["工具节点写入结果"];
C["工具节点写入结果"] --> D["确认节点写入 pendingAction"]; D["确认节点写入 pendingAction"] --> E["检查点持久化"];
E["检查点持久化"] --> F["恢复后继续执行"];
这张图里,模型只是其中一个节点。工具、状态、权限、确认和审计同样重要。
如果一个 Agent 图里只有“用户 -> 模型 -> 答案”,那它还只是 Chat,不是能进入业务系统的 Agent。
5. 落地步骤
1. 定义 State Schema
先列字段、类型、生产节点、消费节点和是否持久化。
完成后应该能看到:State 字段表。
2. 控制状态大小
大文档、完整订单详情不长期放 State,只保存引用 ID 和摘要。
完成后应该能看到:状态大小约束。
3. 设置 schemaVersion
后续字段变化时能识别旧状态。
完成后应该能看到:schemaVersion 字段。
4. 写节点契约测试
给节点固定输入 State,断言输出只修改允许字段。
完成后应该能看到:节点测试。
6. 状态、接口或工具字段
| 项目 | 说明 |
|---|---|
| conversationId | 会话或任务 ID |
| rawInput | 用户原始输入 |
| intent | 识别出的意图 |
| entities | 抽取实体 |
| toolResults | 结构化工具结果 |
| pendingAction | 待确认动作 |
| status | 当前状态 |
字段设计要尽量结构化。不要让下游通过一段自然语言去猜“成功了没有”“下一步要干什么”。
7. 示例
State 示例
- schemaVersion
- conversationId
- userId
- rawInput
- intent
- entities
- retrievedEvidence[]
- toolCalls[]
- pendingAction
- finalAnswer
- error
- status
示例里的字段不一定照搬,但思想要保留:输入、状态、输出、错误和审计信息要分开。
8. 哪些事不能交给模型自由发挥
模型不能随意改状态结构。它可以输出候选意图和实体,但 State 的写入、合并和校验要由节点代码控制。
这一节特别重要。Agent 项目最容易踩坑的地方,就是把“理解意图”和“执行业务动作”混成一件事。
模型可以建议动作,但最终执行必须经过代码和业务系统的规则。
9. 测试路径
建议至少准备下面几类测试:
正常路径:输入完整,工具正常返回
缺字段路径:订单号、联系方式、问题描述不完整
权限路径:查询别人的订单、访问无权知识库
失败路径:工具超时、模型输出非法、检索为空
恢复路径:等待确认时服务重启、重复确认、超时确认
Agent 的测试不能只看最终回答。你要检查状态、工具调用、审计日志和是否产生真实副作用。
10. 工程取舍
- State 越完整,恢复越容易;State 越大,成本和隐私风险越高。
- 旧检查点兼容会增加复杂度,但生产很重要。
- 把业务数据放外部存储,用 ID 关联更稳。
取舍讲清楚以后,读者才能知道你为什么这样设计,而不是以为所有问题都要上 Agent。
11. 常见坑
- 把所有临时变量都放进 State。
- 多个节点无规则覆盖同一字段。
- 旧 State 无法恢复。
- State 中保存完整敏感订单信息。
这些坑本质上都和一个问题有关:没有把模型输出当成“不可信的候选结果”。
12. 验收清单
- ☐ State 字段有类型
- ☐ 更新规则明确
- ☐ 检查点可恢复
- ☐ 状态大小受控
- ☐ 旧版本有处理策略
如果这些检查项没过,先不要急着加更多工具。工具越多,边界越乱,排查越难。
13. 落地清单
- ☐ 设计售后 Agent State
- ☐ 标注每个字段生产和消费节点
- ☐ 写 pendingAction 结构
- ☐ 设计 schemaVersion 升级策略
练习时可以先用 Mock,不要一开始就连真实订单或工单系统。等状态、权限和幂等设计清楚后,再接真实接口。
14. 小结
State 是 Agent 工程化的脊梁。没有清晰 State,节点越多,系统越难维护。
Agent 的价值来自灵活性,但安全来自边界。能把这两件事同时讲清楚,才算真正理解 Agent 工程化。