Skip to content

工作流状态机专题

版本:v1.1

最后更新:2026-07-07

很多 AI 工作流在原型阶段看起来只是几步调用串起来:

  • 收到请求
  • 检索资料
  • 生成回答
  • 调工具执行

但一旦进入真实生产,很快就会出现这些问题:

  • 某一步失败后到底回退到哪
  • 用户打断后系统现在算暂停、取消还是待恢复
  • 审批卡住时前后端应该显示什么状态
  • 长任务切到后台后如何继续追踪和恢复
  • 工具已经写成功但本地没落盘时,后续能不能重放

这时候,系统真正需要的就不再是“再写几个 if else”,而是一套明确的状态机。

1. 状态机到底在解决什么

更实用的理解是:

  • 定义系统当前处于什么阶段
  • 定义每个阶段允许转到哪里
  • 定义什么事件会触发流转
  • 定义异常、中断、超时、审批和恢复时该怎么走

状态机不是为了让系统“更像后端课本”,而是为了让工作流在复杂情况下仍然能被解释、被恢复、被审计。

如果没有状态机,最常见的后果是:

  • 同一个任务在不同服务里状态不一致
  • 前端显示“处理中”,后端其实已经失败
  • 工具被重复调用
  • 审批通过了,但流程没有恢复
  • 任务失败后只能全量重跑

2. 为什么 AI 工作流比普通接口更依赖状态机

普通接口很多时候是单次请求、单次响应。AI 工作流则常常具备这些特点:

  • 多步骤
  • 异步执行
  • 依赖模型推理
  • 包含工具调用
  • 包含人工审批或人工接管
  • 可能存在长时间暂停和恢复

只要流程开始跨越一个以上的时刻、一个以上的系统边界或一个以上的责任角色,状态机几乎就成了刚需。

3. 不要只把状态理解成“页面文案”

很多团队会把状态理解成前端展示字符串:

  • 处理中
  • 已完成
  • 失败

这远远不够。真正有工程价值的状态至少要同时服务三类对象:

  • 业务方:知道任务到了哪一步
  • 工程系统:知道当前允许做什么、不能做什么
  • 运维与审计:知道任务为什么停在这里,之后怎样恢复或追责

所以状态机更像系统契约,而不是 UI 标签。

4. 一套更实用的状态设计方法

建议先从三个问题开始:

  1. 这个任务会经过哪些稳定边界。
  2. 每个边界之间允许发生哪些动作。
  3. 哪些动作会产生不可逆副作用。

“稳定边界”通常包括:

  • 输入已校验
  • 计划已生成
  • 检索已完成
  • 审批已发起
  • 工具已执行
  • 结果已回写

这些边界通常比“模型思考到了哪一步”更适合设计状态。

5. 建议把状态分成业务状态和执行状态

一个很常见的坑是把所有信息都堆进一个字段里。更稳妥的方式是分层:

5.1 业务状态

回答业务视角的问题:

  • 订单处理到哪了
  • 审批现在是什么结果
  • 客服 case 是待处理还是已完成

5.2 执行状态

回答工作流引擎视角的问题:

  • 当前步骤是否在运行
  • 是否在等待工具
  • 是否已暂停
  • 是否可恢复

这两类状态不一定一一对应,但必须能映射。

6. AI 工作流里常见的核心状态

以下是一个比较常见、可扩展的最小状态集合:

状态含义
received请求已接收,尚未校验
validated输入已校验,可进入编排
planned计划或执行路径已确定
retrieving正在检索或收集外部信息
awaiting_tool已发起工具调用,等待结果
awaiting_approval等待人工审批
running正在执行主要步骤
paused主动暂停,可恢复
retryable_failed失败但允许自动或人工重试
non_retryable_failed失败且不应自动重试
compensating正在执行补偿或回滚
completed流程完成
cancelled被用户或系统取消

状态名不是重点,边界清晰才是重点。

7. 转移规则比状态名更重要

一个状态机是否可用,关键不在于状态数量,而在于转移是否明确。

例如:

text
received
 -> validated
 -> planned
 -> retrieving
 -> awaiting_tool
 -> running
 -> completed

同时必须定义异常分支:

text
awaiting_tool
 -> retryable_failed
 -> running

awaiting_approval
 -> approved
 -> running

awaiting_approval
 -> expired
 -> cancelled / manual_review

如果一个状态能“随便跳到任何状态”,那它实际上不是状态机,只是枚举字段。

8. 每个状态都要明确进入条件和退出条件

这是让状态机真正稳定的关键。

例如对 awaiting_approval

  • 进入条件:高风险动作已生成审批单,且审批对象已持久化
  • 退出条件:收到 approvedrejectedexpiredcancelled 事件

例如对 awaiting_tool

  • 进入条件:工具请求已发出,且记录了工具名、参数、幂等键
  • 退出条件:收到工具结果、超时或不可重试错误

如果进入和退出条件不清楚,系统就容易陷入“看起来在这个状态,但实际上不完整”的半残状态。

9. 用事件驱动来描述状态机会更稳

比起直接说“把状态改成 X”,更稳妥的方式通常是:

  • 定义事件
  • 再由事件驱动状态变化

常见事件例如:

  • request_validated
  • plan_generated
  • retrieval_completed
  • tool_call_started
  • tool_call_succeeded
  • tool_call_failed
  • approval_requested
  • approval_granted
  • approval_rejected
  • timeout_reached
  • human_takeover_started

这样做的好处是:

  • 更容易审计
  • 更容易回放
  • 更容易和 observability 体系对齐

10. 审批、人机协同和状态机天然绑定

审批最容易暴露状态设计问题,因为它会把原本连续执行的流程硬切开。

常见场景包括:

  • 模型生成了高风险操作建议
  • 系统发起审批单
  • 审批人过了十分钟才回来
  • 审批通过后流程继续执行

如果没有明确状态机,团队常见会踩这些坑:

  • 审批单存在,但任务没暂停
  • 任务暂停了,但前端看不到
  • 审批通过了,但不知道从哪恢复
  • 审批超时了,任务还一直挂着

所以审批不是状态机的附属品,而是状态机最重要的验证场景之一。

11. 长任务和后台执行为什么特别需要状态机

OpenAI 的 Background mode、会话状态和长任务恢复相关资料,本质上都指向同一件事:

  • 异步执行会把“任务还活着”这件事本身变成一个需要管理的状态问题

一旦任务跨秒、跨分钟甚至跨天运行,就必须回答:

  • 当前还在执行哪一步
  • 是在等待人,还是在等待工具
  • 是否可以恢复
  • 恢复后从哪一个 checkpoint 继续

这也是为什么状态机往往要和:

  • checkpoint
  • pause / resume
  • retries
  • compensation

一起设计,而不是独立存在。

12. 重试不是简单的回到上一状态

很多系统把“失败重试”理解成:

  • 状态从 failed 改回 running

这通常不够。

更稳妥的方式是显式区分:

  • 哪个步骤失败
  • 是否允许重试
  • 重试次数上限
  • 是否具备幂等条件
  • 是否需要回到上一个安全 checkpoint

例如:

text
awaiting_tool
 -> retryable_failed
 -> awaiting_tool

text
awaiting_tool
 -> non_retryable_failed
 -> manual_review

是两条完全不同的治理路径。

13. 超时状态不要省略

很多流程不是“失败”,而是“没人回”。典型包括:

  • 外部工具一直无响应
  • 审批长时间无人处理
  • 后台任务卡死

所以建议把超时视为一等公民,而不是笼统算进失败。

常见做法:

  • awaiting_tool -> timed_out -> retryable_failed
  • awaiting_approval -> expired -> cancelled / escalate
  • running -> timed_out -> paused / manual_review

超时如果没有独立语义,后面很难分清是系统故障、人工缺位还是策略本身有问题。

14. 补偿和回滚也应该是状态

只要工作流里存在写操作,状态机里就应该给补偿留位置。

例如:

  • 已创建工单,但后续步骤失败
  • 已发外部消息,但审批追认失败
  • 已写 CRM,但后续校验不通过

这时系统不应该只写一条错误日志,而应该进入:

  • compensating
  • 补偿成功后再进入 cancelledcompleted_with_compensation

这样状态机才能真实反映系统对外部世界造成过什么影响。

15. 一个可执行的状态对象建议包含什么

建议至少保留这些字段:

字段作用
task_id业务任务唯一标识
run_id本次执行实例标识
workflow_version当前工作流定义版本
state当前状态
step_id当前步骤标识
entered_at进入当前状态时间
last_event最近触发的事件
retry_count当前步骤重试次数
checkpoint_ref最近恢复点
approval_id审批对象引用
side_effect_log已执行外部动作记录
owner当前责任角色或服务

有了这些字段,状态机才真正具备恢复、观测和审计价值。

16. 观测体系必须围绕状态转移来做

只记录“任务成功/失败”对复杂工作流是完全不够的。

建议至少记录这些事件:

  • 状态进入
  • 状态退出
  • 状态转移原因
  • 工具开始/完成/失败
  • 审批发起/通过/拒绝/超时
  • 人工接管开始/结束
  • checkpoint 写入
  • 补偿开始/结束

如果后续想做回放、trace grading 或事故复盘,这些事件是最关键的证据。

17. 状态 schema 版本化和工作流升级要提前考虑

很多团队会给代码做版本管理,却忽略一个更隐蔽的问题:

  • 线上还有很多“跑到一半的旧任务”

一旦你改了工作流节点、状态名、事件名或 checkpoint 结构,就可能出现:

  • 新代码读不懂旧状态
  • 旧任务恢复到一个已经不存在的步骤
  • 前端、告警和审计系统对同一状态含义理解不一致

所以状态机除了 state 本身,最好再显式保留:

  • workflow_version
  • state_schema_version
  • migrated_from_version
  • state_payload_ref

更稳妥的升级策略通常是:

  1. 新旧版本并行一段时间。
  2. 只让新任务进入新版本。
  3. 旧任务继续按旧图跑完,或显式执行迁移。
  4. 迁移规则单独评测,而不是直接在线上硬切。

如果没有版本化,状态机最常见的事故之一就是:

  • 代码升级本身没有报错,但恢复链路在几小时后才开始批量出问题

18. 所有权、租约和心跳决定“谁有资格改状态”

状态机不是只有“当前是什么状态”,还要回答:

  • 现在是谁在负责推进这个状态

只要你的工作流跨 worker、跨服务、跨后台任务,就要考虑:

  • 是否可能被重复领取
  • 某个执行者失联多久后允许被接管
  • 审批恢复后由谁继续持有执行权

建议至少补上这些字段:

  • owner
  • lease_expires_at
  • heartbeat_at
  • resume_token
  • last_transition_by

根据 Temporal Workflow Execution 与 Durable Execution 文档在 2026-07-07 可访问的说明,durable workflow 的核心不是“永远不会失败”,而是通过事件历史和执行控制,保证失败后仍能从一致状态继续推进。

根据 LangGraph Interrupts 文档在 2026-07-07 可访问的说明,interrupt 会在暂停时保存 graph state,等外部输入到达后再恢复执行。这也说明:

  • 暂停恢复不是 UI 行为,而是状态所有权与持久化行为

如果没有所有权和租约设计,最典型的问题是:

  • 旧执行器还在推进状态
  • 新执行器也拿到了恢复权限
  • 两边都以为自己是合法执行者

19. 建议单独监控这些状态机指标

  • state_transition_rate
  • stuck_in_state_rate
  • avg_time_in_state
  • approval_expired_rate
  • retryable_failed_rate
  • manual_takeover_rate
  • compensation_success_rate
  • unexpected_transition_rate

其中 unexpected_transition_rate 很重要,因为它能直接说明:

  • 是不是有代码绕过了状态机契约

20. 一个客服审批型 Agent 的状态流示例

下面是一个更贴近生产的例子:

text
received
 -> validated
 -> planned
 -> retrieving
 -> running
 -> approval_requested
 -> awaiting_approval
 -> approved
 -> awaiting_tool
 -> running
 -> completed

异常支路可能包括:

text
awaiting_tool
 -> timed_out
 -> retryable_failed
 -> awaiting_tool

awaiting_approval
 -> expired
 -> manual_review

running
 -> non_retryable_failed
 -> compensating
 -> cancelled

这个例子最重要的价值,不是状态数量,而是每个关键暂停点、写操作点和恢复点都被显式表达出来了。

21. 常见反模式

21.1 把状态写死在 Prompt 里

模型可以知道阶段,但系统状态不能只存在于模型文本里。

21.2 没有失败状态

所有异常都混成“处理中”,最后只会让排障越来越痛苦。

21.3 审批和执行状态混在一起

会导致既无法清晰展示,也无法恢复。

21.4 工具执行前后没有状态边界

一旦出错,就难以判断副作用是否已经发生。

21.5 长任务恢复时只能全量重跑

这通常意味着状态机没有和 checkpoint、补偿一起设计。

22. 推荐搭配阅读

23. 重点官方资源

以下资源是本次补写时重点参考的官方资料,适合继续补强状态机、暂停恢复、人工审批和工作流编排设计:

24. 落地检查清单

  • 是否为关键业务流程定义了独立于 UI 文案的真实状态机
  • 是否为每个状态定义了明确的进入条件、退出条件和允许转移
  • 是否将审批、超时、暂停、恢复、补偿视为显式状态,而不是异常分支注释
  • 是否为工具调用前后建立了清晰状态边界和副作用记录
  • 是否保留了 task_idrun_idcheckpoint_reflast_event 等恢复所需字段
  • 是否为状态 schema 和工作流定义维护了版本号与迁移策略
  • 是否设计了 owner、lease 和 heartbeat,而不是默认同一任务永远只会被一个执行器推进
  • 是否记录了状态转移事件,能支持回放、复盘和审计
  • 是否能通过指标识别卡死状态、异常转移和恢复薄弱环节