Agent 状态机设计:为什么复杂任务不能只靠 Prompt

"LangGraph Persistence 文档把 checkpoint 用于 thread-scoped graph state snapshot,并说明它支撑 conversation continuity、human-in-the-loop、time travel 和 fault tolerance。"
报表 Agent 在第 5 步发邮件前失败了。运维重跑任务,Agent 从第 1 步重新执行,生成了新报表,覆盖了前一次已审批版本。审批状态丢失,审批人签字的记录被新结果替换,没有任何日志能证明第一版报表经过了审批。
这不是数据库事务回滚,也不是消息队列重试。Prompt 里只剩一句”继续处理”,模型重新推理整个流程,完全不知道第 1-4 步已经产生过副作用:调用了审批接口、生成了报表、写入了临时文件。失败点在第 5 步,但副作用从第 2 步就开始了。
真正的问题不是”模型能力够不够”,而是任务进度藏在 prompt 自然语言里,根本没有可恢复的状态快照。Prompt 携带的 messages 只是模型上下文,不是执行事实。
要把这类事故修好,不能只在 prompt 里补一句”继续前先检查进度”。更稳的做法是把当前节点、已发生副作用、下一步动作和失败补偿写进可恢复的状态表。
事故关键点
报表 Agent 执行流程:
| 步骤 | 操作 | 副作用 | 幂等性 |
|---|---|---|---|
| 第 1 步 | 数据查询 | 调用数据库,查询用户数据 | 幂等(读操作) |
| 第 2 步 | 报表生成 | 调用报表生成工具,生成 PDF | 不幂等(覆盖文件) |
| 第 3 步 | 审批等待 | 发送审批请求,等待人工批准 | 幂等(接口支持) |
| 第 4 步 | 审批通过 | 收到 approve 事件 | 幂等(查询状态) |
| 第 5 步 | 发邮件 | 调用邮件 API,发送报表 | 失败(超时) |
失败原因:第 5 步邮件发送超时(外部 API 限流),任务被标记为 FAILED。
重跑逻辑:从 prompt 里读取”当前进度”,prompt 只有一句”审批通过,继续处理”。实际执行:重新从第 1 步开始 -> 第 2 步重新生成报表(覆盖已审批版本)-> 第 3 步再次审批 -> 第 5 步发送成功。
业务影响:已审批报表被替换,审批记录不一致;用户投诉第一次审批的报表内容和最终收到的报表内容不一致;审批流程浪费,审批了两个版本但只有一个版本真正发送。
反模式识别表
判断你的 Agent 是否命中这些反模式:
| 反模式 | 表现 | 隐患 | 修正方案 |
|---|---|---|---|
| 进度写在 Prompt | ”当前在第 3 步” 自然语言摘要 | 重启后丢失,无法恢复 | 用 State 字段记录当前节点 |
| Trace 当成 State | 有完整 Trace 就以为有状态 | Trace 不记录下一步决策 | State 记录下一步应该做什么 |
| Retry 无幂等检查 | 失败就从头重跑 | 副作用重复执行 | 幂等键 + 已执行检查 |
| 审批后 Resume 无检查 | 直接继续执行 | 未回到正确执行点 | checkpoint + thread_id |
1. 状态机基础:State、Event、Transition、Guard、Action
状态机不是所有 Agent 的必要架构。简单客服问答用 messages 数组就够了,但复杂任务—多步骤、有审批、有外部系统调用、失败后需要恢复—必须把任务进度显式化。
1.1 核心术语定义表
状态机基础术语来自 Stately 官方文档:
| 术语 | 定义 | Agent 示例 | 来源 |
|---|---|---|---|
| State(状态) | 机器所处模式,对应唯一语义意图 | INIT、PLAN_READY、TOOL_RUNNING、APPROVAL_PENDING、FAILED、COMPLETED | Stately state machines |
| Event(事件) | 触发状态变化的外部信号 | timeout、approve、reject、retry、resume、task_received | Stately state machines |
| Transition(转移) | 状态之间的允许路径,确定性映射 | INIT -> PLAN_READY(event: task_received) | Stately state machines |
| Guard/Condition(守卫) | 进入某状态的前提检查 | ”预算充足”才能进入 TOOL_RUNNING | Stately state machines |
| Action(动作) | 转移时执行的操作 | 进入 TOOL_RUNNING 时调用工具 | Stately state machines |
| Checkpoint(快照) | 状态快照,用于恢复 | LangGraph checkpointer 保存 graph state | LangGraph Persistence |
确定性原则:同一 State + Event 组合应指向唯一下一状态,避免歧义。有限状态集:状态机不是无限流程图,而是有限可达状态 + 明确转移规则。
1.2 Trace vs State vs Audit 对照表
Trace、Audit Log、State Snapshot 解决三个不同问题:
| 概念 | 解决什么问题 | 是否是业务状态 | 是否决定下一步 | Agent 示例 |
|---|---|---|---|---|
| Trace(追踪) | 观测与诊断骨架 | 不是 | 不决定 | OpenAI Agents SDK trace(workflow_name、trace_id) |
| Audit Log(审计日志) | 合规记录,审计追溯 | 不是 | 不决定 | 权限模型的审计字段(actor、traceId、action、result) |
| State Snapshot(状态快照) | 决定下一步的当前状态 | 是 | 决定 | LangGraph checkpoint(当前节点、已执行步骤、下一步应该做什么) |
核心区分:Trace 帮助观测”发生了什么”,但不是业务状态本身。Audit Log 记录合规历史,用于审计追溯。State Snapshot 决定”下一步应该做什么”,是恢复的核心依据。三者不能互相替代:有 Trace 不等于有 State;有 Audit 不等于可恢复。
2. LangGraph 如何做状态持久化
Checkpoint 不是 prompt 里的自然语言摘要,而是可恢复、可检查、可回放的状态快照。LangGraph persistence 文档把 checkpoint 定义为 graph state snapshot,包含完整状态和下一步待执行节点。
2.1 Checkpointer 与 Thread State
核心机制(来源:LangGraph Persistence 官方文档):
- Checkpointer:保存 thread-scoped 状态快照(graph state snapshot)
- Store:保存跨线程长期数据(application-defined store)
- Thread_id:恢复具体线程状态的唯一入口
- 四类用途:conversation continuity、human-in-the-loop、time travel、fault tolerance
LangGraph persistence 把短期 thread-scoped 状态交给 checkpointers,把跨线程长期数据交给 stores。Checkpoint 包括 state snapshot 和 application-defined store。Thread_id 是恢复入口,相同 thread_id 可以从暂停点继续。
LangGraph checkpoint 包含 graph state、下一步待执行节点列表、checkpoint_id、时间戳、版本号。敏感数据不进 checkpoint:某些 graph state 字段可能包含敏感信息,需要显式配置不保存。
2.2 Interrupts 与恢复机制
核心机制(来源:LangGraph Interrupts 官方文档):
- interrupt():在图节点内动态暂停执行,保存 graph state,等待外部输入
- 恢复方式:使用同一个 thread_id 和 Command(resume=…)
- 常见模式:审批、review/edit、tool call review、human input validation
- 幂等副作用警告:interrupt 前的副作用必须是幂等的,因为恢复时节点会从调用 interrupt 的节点开头重新执行
审批暂停必须是状态机的一种暂停状态,而不是让模型”记得等人批准”。恢复需要同一个 thread cursor。
恢复时使用同一个 thread_id 和 Command(resume=…)。幂等副作用是恢复的前提条件:如果审批前有副作用(如调用外部 API),必须确保幂等,否则恢复时节点重新执行会重复调用 API。
3. 工程类比:Temporal Durable Execution
长任务可靠性不是新问题。Temporal durable execution 提供了成熟范式对照。
3.1 Durable Execution 定义
核心概念(来源:Temporal Durable Execution 官方文档):
- Durable Execution 定义:workflow execution 在失败、崩溃或服务中断时仍能保留 state/progress
- Event History:记录每一步状态,失败后可从最后记录事件恢复
- 三特性:Resumable(可恢复)、Recoverable(可恢复执行)、Reactive(可响应)
长任务可靠性来自 event history 和可恢复执行,而不是单次进程内存或 prompt 上下文。Agent 状态机需要类似机制:checkpoint/event log + 业务状态,而不是只靠模型重新推理。
Temporal 的 Event History 和 LangGraph 的 checkpoint 概念相似:记录执行历史,支持从失败点恢复。区别在于 Temporal 是完整的 workflow 引擎,LangGraph 是 Agent 状态管理框架。Agent 开发者可以从 Temporal 的设计中学到:durable execution 需要结构化状态历史,而不是依赖进程内存或模型上下文。
4. 状态表设计模板:可复制的 Agent State Table
状态机概念抽象,落地需要具体状态模型。以下提供三套模板:状态表、事件表、事故驱动状态表实例。
4.1 状态表模板(可执行步骤块)
模板结构:
| State(状态) | Event(触发事件) | Guard(守卫条件) | Action(必须动作) | Next(下一状态) |
|---|---|---|---|---|
| INIT | task_received | 无 | 初始化上下文、记录开始时间 | PLAN_READY |
| PLAN_READY | plan_generated | plan_valid | 生成执行计划、记录工具序列 | TOOL_RUNNING |
| TOOL_RUNNING | tool_completed | budget_sufficient | 调用工具、记录结果、更新预算 | APPROVAL_PENDING 或 COMPLETED |
| APPROVAL_PENDING | approve | approval_required | 发送审批请求、记录审批人 | COMPLETED |
| APPROVAL_PENDING | reject | 无 | 记录拒绝原因、通知用户 | FAILED |
| FAILED | retry | retry_count < max | 检查幂等、回退到上一个 checkpoint | TOOL_RUNNING 或 APPROVAL_PENDING |
| COMPLETED | 无 | 无 | 记录完成时间、清理资源 | Terminal |
模板说明:State 列定义所有可达状态(INIT、PLAN_READY、TOOL_RUNNING、APPROVAL_PENDING、FAILED、COMPLETED)。Event 列定义触发转移的事件(task_received、approve、reject、retry)。Guard 列定义进入某状态的前提检查(budget_sufficient、retry_count < max)。Action 列定义转移时的必须动作(调用工具、记录结果、发送审批)。Next 列定义下一状态(确定性转移)。
4.2 事件表模板(补充状态表)
模板结构:
| Event(事件名称) | 触发条件 | 前置状态要求 | 后置状态 | 是否产生副作用 |
|---|---|---|---|---|
| task_received | 用户提交任务 | INIT | PLAN_READY | 不产生 |
| plan_generated | LLM 生成了执行计划 | PLAN_READY | TOOL_RUNNING | 不产生 |
| tool_completed | 工具执行完成 | TOOL_RUNNING | APPROVAL_PENDING 或 COMPLETED | 产生(调用外部 API) |
| approve | 审批人批准 | APPROVAL_PENDING | COMPLETED | 产生(发邮件、扣预算) |
| reject | 审批人拒绝 | APPROVAL_PENDING | FAILED | 不产生 |
| retry | 失败重试请求 | FAILED | TOOL_RUNNING 或 APPROVAL_PENDING | 需幂等检查 |
| timeout | 执行超时 | TOOL_RUNNING | FAILED | 不产生 |
事件表说明:前置状态要求明确事件只能在哪些状态上触发。是否产生副作用标记哪些事件需要幂等或补偿。
4.3 事故驱动状态表实例(从报表覆盖事故推导)
完整示例:报表 Agent 状态表(从事故开场推导)
| State | Event | Guard | Action | Next | 幂等/补偿检查 |
|---|---|---|---|---|---|
| INIT | task_received | 无 | 初始化 thread_id、记录开始时间 | QUERY_RUNNING | 不需要 |
| QUERY_RUNNING | query_completed | 无 | 查询数据、保存结果到 state | REPORT_GENERATING | 不需要 |
| REPORT_GENERATING | report_generated | 无 | 生成报表、保存报表 ID 到 state | APPROVAL_PENDING | 幂等检查:如果报表已存在,跳过生成 |
| APPROVAL_PENDING | approve | 无 | 记录审批人、审批时间 | EMAIL_SENDING | 不需要 |
| APPROVAL_PENDING | reject | 无 | 记录拒绝原因 | FAILED | 不需要 |
| EMAIL_SENDING | email_sent | 无 | 发送邮件、记录邮件 ID | COMPLETED | 幂等检查:如果邮件已发送,跳过 |
| EMAIL_SENDING | timeout | retry_count < 3 | 记录失败、检查幂等 | EMAIL_SENDING(重试)或 FAILED | 幂等键:email_id + thread_id |
| FAILED | retry | retry_count < max | 检查幂等、从上一个 checkpoint 恢复 | QUERY_RUNNING 或 REPORT_GENERATING 或 EMAIL_SENDING | 根据 checkpoint 决定恢复点 |
| COMPLETED | 无 | 无 | 记录完成时间、清理资源 | Terminal | 不需要 |
事故复盘修正:第 5 步失败(EMAIL_SENDING -> timeout)-> 应从 EMAIL_SENDING 恢复,而不是从 QUERY_RUNNING 恢复。需要 checkpoint 记录当前节点(EMAIL_SENDING)、已执行步骤(QUERY、REPORT_GENERATED、APPROVAL_APPROVED)、下一步应该做什么(EMAIL_SENDING)。幂等检查:报表生成、邮件发送需要幂等键,避免重复执行。
5. 幂等与补偿:恢复不只是 checkpoint
有 checkpoint 不等于能安全恢复所有副作用。恢复还需要幂等性、事务、补偿和外部系统状态检查。
5.1 幂等与补偿概念
定义:
- 幂等(Idempotent):多次执行结果相同,不会重复产生副作用
- 补偿(Compensation):撤销已发生副作用,恢复到一致状态
- 事务回滚:原子性操作,失败时自动回滚
- 外部系统状态检查:恢复前检查外部系统状态,避免重复操作
状态一致性三支柱:幂等性标识(action_id + schema_hash)、状态快照链(snapshot + prev_hash + delta)、补偿动作注册(undo_op)。
5.2 幂等与补偿判断清单
判断哪些操作需要幂等、哪些需要补偿:
| 操作类型 | 是否需要幂等 | 是否需要补偿 | 幂等键设计 | 补偿方案 |
|---|---|---|---|---|
| 数据查询(无副作用) | 不需要 | 不需要 | - | - |
| 报表生成(覆盖文件) | 需要 | 需要 | report_id + thread_id | 删除旧报表、恢复审批版本 |
| 邮件发送(外部 API) | 需要 | 困难 | email_id + thread_id | 发送撤销邮件(部分场景) |
| 扣减库存(数据库) | 需要 | 需要 | inventory_id + order_id | 增加库存(补偿扣减) |
| 创建工单(外部系统) | 需要 | 需要 | ticket_id + thread_id | 关闭工单(补偿创建) |
| 扣减预算(内部状态) | 需要 | 需要 | budget_id + thread_id | 增加预算(补偿扣减) |
| 发送审批请求(无副作用) | 不需要 | 不需要 | - | - |
判断逻辑:是否产生外部副作用决定是否需要幂等。可撤销的操作需要补偿。跨系统调用时幂等键应包含外部系统标识。原子操作可以事务回滚。
恢复不只是 checkpoint,还需要幂等、事务、补偿和外部系统状态检查。有 checkpoint 就能安全恢复所有副作用的说法不准确。
6. Agent 任务状态清单:可恢复 vs 不可恢复
不是所有 checkpoint 都能恢复。Terminal state 是 workflow execution 的终止状态:完成、失败、超时、取消。Terminal state 无法恢复,只能重新执行或补偿。
6.1 状态分类表
| 状态类型 | 是否可恢复 | 恢复条件 | 恢复方式 | 示例 |
|---|---|---|---|---|
| Failed(失败) | 可恢复 | retry_count < max | 从上一个 checkpoint 恢复 | 工具调用超时 |
| Retry(重试) | 可恢复 | 幂等检查通过 | 从失败节点重新执行 | 邮件发送失败 |
| Compensation(补偿) | 部分可恢复 | 补偿方案存在 | 执行 undo_op | 扣减库存失败 |
| Approval Pause(审批暂停) | 可恢复 | approve/reject 事件 | Command(resume=…) | 审批等待 |
| Terminal(终态) | 不可恢复 | 无 | 无恢复路径 | COMPLETED、FAILED(retry_count = max) |
状态清单说明:Failed 状态可通过 retry 恢复(如果 retry_count < max)。Retry 状态需要幂等检查,从失败节点重新执行。Compensation 状态部分可恢复(如果补偿方案存在)。Approval Pause 状态可恢复(通过 approve/reject 事件)。Terminal State 终态不可恢复(COMPLETED 或 FAILED 达到最大重试次数)。
7. 下一步延伸阅读
状态机设计只是起点。状态建模需要结合具体业务场景,不同的任务有不同的状态粒度和恢复策略。
系列上下游导航
| 文章 | 关系 | 链接 |
|---|---|---|
| Human-in-the-loop Agent 设计:哪些步骤必须人工审批 | 审批暂停细节 | /blog/zh/posts/ai/20260707-human-in-the-loop-agent-approval-design/ |
| Agent 成本控制:模型路由、工具预算与失败重试怎么设计 | 预算、重试策略 | /blog/zh/posts/ai/20260707-agent-cost-control-model-routing-tool-budget-cache-retry/ |
| LangGraph 状态管理实战:2026年 Agent 架构最佳实践 | LangGraph 状态管理 | /blog/zh/posts/ai/20260424-langgraph-agent-architecture/ |
| AI Agent 监控告警与失败恢复:从日志到状态机的设计实践 | 监控、恢复 | /blog/zh/posts/ai/20260527-ai-agent-monitoring-recovery/ |
| LangGraph vs AutoGen 状态追踪 | 框架对比 | /blog/zh/posts/ai/20260526-langgraph-autogen-state-tracking/ |
| Agent 评测数据集与回归测试:如何避免”改一处坏全局” | 评测、回归测试 | 预告,同系列下一篇 |
外部参考资料
高置信度来源:
| 来源 | 置信度 | 主题 | 链接 |
|---|---|---|---|
| LangGraph Persistence 官方文档 | high | Checkpointer、Store、Thread State、Checkpoint | https://docs.langchain.com/oss/python/langgraph/persistence |
| LangGraph Interrupts 官方文档 | high | interrupt()、Command(resume=…)、thread_id | https://docs.langchain.com/oss/python/langgraph/interrupts |
| Temporal Durable Execution 官方文档 | high | Event History、Durable Execution、Resumable/Recoverable | https://docs.temporal.io/temporal |
| OpenAI Agents SDK Tracing 官方文档 | high | Trace、Span、workflow_name、trace_id | https://openai.github.io/openai-agents-python/tracing/ |
| AWS Step Functions State Machines 官方文档 | high | State Machine、Flow State、Task State、StartAt、Next | https://docs.aws.amazon.com/step-functions/latest/dg/concepts-statemachines.html |
| Stately: State machines and statecharts | medium | State、Event、Transition、Guard、Action、Hierarchy | https://stately.ai/docs/state-machines-and-statecharts |
状态机不是所有 Agent 的必要架构,但复杂任务必须显式化。下一步不是引入更多框架,而是根据业务场景设计合适的 State、Event、Transition、Guard、Action,把任务进度从 prompt 自然语言搬到结构化状态。
设计复杂 Agent 的状态机
把复杂 Agent 任务拆成显式 state、event、guard、action、checkpoint、retry、compensation 和 terminal state,避免把任务进度只藏在 prompt 里。
⏱️ 预计耗时: 45 分钟
- 1
步骤 1: 列出风险点
列出任务的外部副作用、人工暂停点、失败点和终止条件。 - 2
步骤 2: 定义最小状态集
定义 pending、running、waiting_approval、retrying、compensating、succeeded、failed、cancelled 等最小 state 集合。 - 3
步骤 3: 绑定事件和下一状态
为每个 state 写清允许接收的 event,以及 event 后的 next state。 - 4
步骤 4: 补上守卫条件
为危险 transition 添加 guard,包括权限、预算、审批、幂等键和外部资源状态。 - 5
步骤 5: 隔离工具动作
把工具调用放到 action 层,并记录 input 摘要、output 摘要、traceId 和副作用结果。 - 6
步骤 6: 定义失败策略
为失败定义 retry policy、terminal state 和 compensation policy。 - 7
步骤 7: 保存恢复依据
为恢复定义 checkpoint 或 event log,并把 prompt 只当作临时上下文,不当作唯一事实源。
常见问题
执行到第 5 步失败,重跑从第 1 步还是从 checkpoint 继续?
任务状态存 prompt、数据库、LangGraph checkpoint 还是队列 job?
状态机和 workflow/流程图有什么区别?
审批后怎么保证 Agent 回到同一个执行点?
retry 和 compensation 写在 prompt 还是状态转移规则?
简单客服 Agent 是否需要状态机?
14 分钟阅读 · 发布于: 2026年9月17日



评论
使用 GitHub 账号登录后即可评论