Agent 的危险不在于“会调用工具”,而在于运行边界含糊:它什么时候算完成,什么时候只是等待确认,工具失败后应该重试哪一步,进程重启后又从哪里继续。如果实现只有一个 while (true) 和一句“直到任务完成”,模型就同时承担规划、退出和故障判断,应用失去了控制。
本文面向已经实现过 Responses 工具循环或 Agents SDK run 的开发者。你需要熟悉状态机、幂等和持久化。目标是把一个应用级 turn 设计成有限、可恢复、可审计的运行单元。
图 1(1600 × 900):WEB/SUN 原创 SVG。暂停不是失败,重试也不是回到起点;每条边都要有应用判断和预算。
先定义“一次 run”
OpenAI 的Agents SDK 运行指南把一次 run 定义为一个应用级 turn:运行器调用当前 Agent 的模型,检查输出;有工具就执行后继续,有 handoff 就切换专家后继续,只有得到不再需要工具的最终回答才返回结果。工具、流式和审批都建立在同一循环上。
这意味着“模型生成了一条消息”不一定是终点,“HTTP 请求结束”也不一定是业务终点。应用应给 run 一个稳定 ID,并保存:当前状态、当前 Agent、输入版本、已完成 step、待处理调用、预算消耗、最后一个可恢复游标和终态原因。对话历史是输入材料,不等于运行状态。
最小状态可以是:
type RunPhase =
| 'received'
| 'model_step'
| 'tool_step'
| 'paused_for_approval'
| 'retry_wait'
| 'completed'
| 'cancelled'
| 'failed';
终态 completed/cancelled/failed 互斥;paused_for_approval 是可持久化的中间态。每次状态迁移都记录触发者和前置条件,避免同一 run 在两个 worker 中同时推进。
退出条件必须由应用组合判断
“模型说完成”只是一项信号。真正完成至少满足:输出类型正确;没有未决工具或审批;业务后置条件成立;输出 guardrail 通过;结果已经以幂等方式提交。比如“退款已完成”必须由退款系统状态证明,不能由模型句子证明。
同时设置硬预算:最大模型 step、最大工具调用数、单工具重复次数、墙钟截止时间、令牌或费用预算、handoff 次数和并行分支数。任何一项耗尽都进入带原因的受控终止,或者暂停等待人工,不再问模型“要不要继续”。预算数值取决于业务,必须通过真实 trace 和评测调整,不能抄一个通用常量。
软退出条件处理低质量循环:连续提出相同工具与规范化参数;新 step 没有产生新事实;答案在两个状态间反复;工具持续返回同一不可重试错误。检测到后可以让一次诊断 step 总结缺失信息,但诊断也消耗最后一份固定预算,不能打开新的无限循环。
暂停与失败必须分开
审批不是异常。官方Guardrails 与人工审核指南说明,需要审核的工具调用会产生 interruption,结果带回待决项和可恢复 state;应用批准或拒绝后,从同一 state 恢复,而不是创建一个新的用户 turn。这样历史、轮次与服务端 continuation 才保持一致。
暂停记录需要包含工具、规范化参数、影响摘要、审批策略版本和过期时间。审批者确认的必须是稳定参数哈希;恢复前重新验证身份、资源状态和权限。若订单已经被其他流程取消,旧审批不能继续执行。
流式 run 也遵循同一模型。官方指南要求等待 stream settle 后检查 interruption;取消流时,若要继续同一 turn,应从保存的 state 恢复。页面关闭只是客户端事件,不应把 run 自动标成失败,更不能假定已启动的外部副作用回滚。
按故障域恢复,不做整轮盲重试
恢复策略取决于失败发生在哪里:
| 故障域 | 先确认 | 典型恢复 |
|---|---|---|
| 模型请求前 | 是否已创建远端响应 | 安全重试当前 model step |
| 流式中断 | 是否已有 Response ID 与终态 | 查询、恢复或标记不完整 |
| 只读工具 | 是否可重复且仍在截止时间内 | 限次退避重试 |
| 写工具 | 外部系统是否已提交 | 用业务幂等键对账后恢复 |
| guardrail | 是策略拒绝还是校验器故障 | 拒绝结束或人工复核 |
| 审批过期 | 资源与权限是否变化 | 重新生成影响预览并再确认 |
官方函数调用流程允许一轮产生更多工具调用,因此恢复点应在每个完整 step 之后提交:先持久化模型 output 和调用意图,再领取执行租约,执行工具并原子保存结果,最后才进入下一次模型调用。进程崩溃后,worker 可以区分“尚未执行”和“结果已写但未回传”。
写工具必须拥有业务幂等键。call_id 负责协议关联,却不足以覆盖恢复后生成的新调用 ID。若外部 API 支持幂等请求,把同一键传过去;若不支持,使用本地唯一约束与状态查询。对不可逆动作,在提交前留下审批和审计证据,在提交后采用补偿流程,不声称数据库回滚能撤销外部世界。
状态快照要可序列化,也要可迁移
官方Results 与 state强调,interrupted run 返回的是可恢复 snapshot 而非最终答案。应用保存 snapshot 时还应记录代码版本、Agent 配置、工具 Schema 与策略版本。否则部署后恢复旧 run,新代码可能误读旧参数。
迁移有三种选择:兼容读取旧状态;在恢复前把状态升级成新版本;明确终止过旧 run 并要求用户重新确认。对包含待执行副作用的 run,禁止静默迁移语义。序列化内容应加密、设保留期并做租户隔离,工具密钥与瞬时连接对象不能进入快照。
并发控制可以使用 run revision 与短租约。worker 读取 revision 7,只能写入 revision 8;若另一个 worker 已推进,则放弃本地结果并检查幂等记录。不要依靠“通常只有一个请求”来保证顺序,重连、队列重投和多标签页都会制造并发。
可观察性与评测
每个 step 记录类型、开始结束、Agent、模型配置、输入输出 item ID、工具与结果类别、预算前后值、handoff/审批和终止原因。日志内容最小化并脱敏;trace 用于还原控制流,不是复制所有私人上下文。
评测要断言路径,而不只看最终文案:正常任务在预算内完成;缺信息时询问而非猜测;同参数循环被切断;审批暂停后可跨进程恢复;拒绝审批不会执行工具;写工具在响应丢失后仍只产生一次副作用;状态版本不兼容时安全终止。
注入依赖故障同样重要:在每一个 step 前后模拟超时和崩溃,验证恢复游标是否准确。若测试只在工具成功返回后断网,就无法发现“副作用已提交但结果未落库”的最危险窗口。
失败模式与取舍
常见错误包括:把最终自然语言当业务完成;审批后启动新 turn;所有异常都从用户输入重跑;工具输出没有持久化就开始下一 step;只用 call ID 去重;预算耗尽后让模型决定是否超限;旧状态在新工具 Schema 上直接恢复。
细粒度状态机增加存储与编排代码,短任务看起来比一个循环笨重。可按风险分级:纯文本草稿只需少量状态和轮次限制;跨系统读写、人工审批与长任务必须保存 step、幂等和版本证据。抽象层可以不同,但终点和恢复语义不能含糊。
结论
可靠 Agent 不是“持续思考直到成功”,而是一个由应用控制的有限状态机。完成需要后置条件,暂停需要可序列化 state,失败需要按故障域恢复,副作用需要幂等与对账,所有路径都受明确预算约束。
当进程能在任意 step 前后崩溃而不重复动作,审批能隔天从同一 run 恢复,循环能在没有进展时稳定退出,Agent 才从演示脚本变成可运维的运行时。
Sources
- Running agents — OpenAI,访问于 2026-07-16。
- Results and state — OpenAI,访问于 2026-07-16。
- Guardrails and human review — OpenAI,访问于 2026-07-16。
- Function calling — OpenAI,访问于 2026-07-16。