Appearance
工作流状态机专题
版本:
v1.1最后更新:
2026-07-07
很多 AI 工作流在原型阶段看起来只是几步调用串起来:
- 收到请求
- 检索资料
- 生成回答
- 调工具执行
但一旦进入真实生产,很快就会出现这些问题:
- 某一步失败后到底回退到哪
- 用户打断后系统现在算暂停、取消还是待恢复
- 审批卡住时前后端应该显示什么状态
- 长任务切到后台后如何继续追踪和恢复
- 工具已经写成功但本地没落盘时,后续能不能重放
这时候,系统真正需要的就不再是“再写几个 if else”,而是一套明确的状态机。
1. 状态机到底在解决什么
更实用的理解是:
- 定义系统当前处于什么阶段
- 定义每个阶段允许转到哪里
- 定义什么事件会触发流转
- 定义异常、中断、超时、审批和恢复时该怎么走
状态机不是为了让系统“更像后端课本”,而是为了让工作流在复杂情况下仍然能被解释、被恢复、被审计。
如果没有状态机,最常见的后果是:
- 同一个任务在不同服务里状态不一致
- 前端显示“处理中”,后端其实已经失败
- 工具被重复调用
- 审批通过了,但流程没有恢复
- 任务失败后只能全量重跑
2. 为什么 AI 工作流比普通接口更依赖状态机
普通接口很多时候是单次请求、单次响应。AI 工作流则常常具备这些特点:
- 多步骤
- 异步执行
- 依赖模型推理
- 包含工具调用
- 包含人工审批或人工接管
- 可能存在长时间暂停和恢复
只要流程开始跨越一个以上的时刻、一个以上的系统边界或一个以上的责任角色,状态机几乎就成了刚需。
3. 不要只把状态理解成“页面文案”
很多团队会把状态理解成前端展示字符串:
- 处理中
- 已完成
- 失败
这远远不够。真正有工程价值的状态至少要同时服务三类对象:
- 业务方:知道任务到了哪一步
- 工程系统:知道当前允许做什么、不能做什么
- 运维与审计:知道任务为什么停在这里,之后怎样恢复或追责
所以状态机更像系统契约,而不是 UI 标签。
4. 一套更实用的状态设计方法
建议先从三个问题开始:
- 这个任务会经过哪些稳定边界。
- 每个边界之间允许发生哪些动作。
- 哪些动作会产生不可逆副作用。
“稳定边界”通常包括:
- 输入已校验
- 计划已生成
- 检索已完成
- 审批已发起
- 工具已执行
- 结果已回写
这些边界通常比“模型思考到了哪一步”更适合设计状态。
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:
- 进入条件:高风险动作已生成审批单,且审批对象已持久化
- 退出条件:收到
approved、rejected、expired或cancelled事件
例如对 awaiting_tool:
- 进入条件:工具请求已发出,且记录了工具名、参数、幂等键
- 退出条件:收到工具结果、超时或不可重试错误
如果进入和退出条件不清楚,系统就容易陷入“看起来在这个状态,但实际上不完整”的半残状态。
9. 用事件驱动来描述状态机会更稳
比起直接说“把状态改成 X”,更稳妥的方式通常是:
- 定义事件
- 再由事件驱动状态变化
常见事件例如:
request_validatedplan_generatedretrieval_completedtool_call_startedtool_call_succeededtool_call_failedapproval_requestedapproval_grantedapproval_rejectedtimeout_reachedhuman_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_failedawaiting_approval -> expired -> cancelled / escalaterunning -> timed_out -> paused / manual_review
超时如果没有独立语义,后面很难分清是系统故障、人工缺位还是策略本身有问题。
14. 补偿和回滚也应该是状态
只要工作流里存在写操作,状态机里就应该给补偿留位置。
例如:
- 已创建工单,但后续步骤失败
- 已发外部消息,但审批追认失败
- 已写 CRM,但后续校验不通过
这时系统不应该只写一条错误日志,而应该进入:
compensating- 补偿成功后再进入
cancelled或completed_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_versionstate_schema_versionmigrated_from_versionstate_payload_ref
更稳妥的升级策略通常是:
- 新旧版本并行一段时间。
- 只让新任务进入新版本。
- 旧任务继续按旧图跑完,或显式执行迁移。
- 迁移规则单独评测,而不是直接在线上硬切。
如果没有版本化,状态机最常见的事故之一就是:
- 代码升级本身没有报错,但恢复链路在几小时后才开始批量出问题
18. 所有权、租约和心跳决定“谁有资格改状态”
状态机不是只有“当前是什么状态”,还要回答:
- 现在是谁在负责推进这个状态
只要你的工作流跨 worker、跨服务、跨后台任务,就要考虑:
- 是否可能被重复领取
- 某个执行者失联多久后允许被接管
- 审批恢复后由谁继续持有执行权
建议至少补上这些字段:
ownerlease_expires_atheartbeat_atresume_tokenlast_transition_by
根据 Temporal Workflow Execution 与 Durable Execution 文档在 2026-07-07 可访问的说明,durable workflow 的核心不是“永远不会失败”,而是通过事件历史和执行控制,保证失败后仍能从一致状态继续推进。
根据 LangGraph Interrupts 文档在 2026-07-07 可访问的说明,interrupt 会在暂停时保存 graph state,等外部输入到达后再恢复执行。这也说明:
- 暂停恢复不是 UI 行为,而是状态所有权与持久化行为
如果没有所有权和租约设计,最典型的问题是:
- 旧执行器还在推进状态
- 新执行器也拿到了恢复权限
- 两边都以为自己是合法执行者
19. 建议单独监控这些状态机指标
state_transition_ratestuck_in_state_rateavg_time_in_stateapproval_expired_rateretryable_failed_ratemanual_takeover_ratecompensation_success_rateunexpected_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. 重点官方资源
以下资源是本次补写时重点参考的官方资料,适合继续补强状态机、暂停恢复、人工审批和工作流编排设计:
- OpenAI Background mode:https://developers.openai.com/api/docs/guides/background
- OpenAI Conversation state guide:https://developers.openai.com/api/docs/guides/conversation-state
- OpenAI Guardrails and human review:https://developers.openai.com/api/docs/guides/agents/guardrails-approvals
- OpenAI Agents guide:https://developers.openai.com/api/docs/guides/agents
- LangGraph Workflows and agents:https://docs.langchain.com/oss/python/langgraph/workflows-agents
- LangGraph Interrupts:https://docs.langchain.com/oss/python/langgraph/interrupts
- LangGraph Persistence:https://docs.langchain.com/oss/python/langgraph/persistence
- Temporal durable execution and workflows:https://docs.temporal.io/workflows
- Temporal Workflow Execution:https://docs.temporal.io/workflow-execution
24. 落地检查清单
- 是否为关键业务流程定义了独立于 UI 文案的真实状态机
- 是否为每个状态定义了明确的进入条件、退出条件和允许转移
- 是否将审批、超时、暂停、恢复、补偿视为显式状态,而不是异常分支注释
- 是否为工具调用前后建立了清晰状态边界和副作用记录
- 是否保留了
task_id、run_id、checkpoint_ref、last_event等恢复所需字段 - 是否为状态 schema 和工作流定义维护了版本号与迁移策略
- 是否设计了 owner、lease 和 heartbeat,而不是默认同一任务永远只会被一个执行器推进
- 是否记录了状态转移事件,能支持回放、复盘和审计
- 是否能通过指标识别卡死状态、异常转移和恢复薄弱环节