跳转到内容

Agent 可靠性工程:超时、重试、幂等与恢复

Agent 系统最危险的错误,不一定是“工具返回失败”,而是“系统不知道刚才到底成功没有”。

发送邮件的请求超时,可能是服务根本没收到,也可能邮件已发出、只是响应丢了。此时简单重试,可能把周报发两遍;不重试,又可能漏发。

可靠性工程处理的正是这种现实:网络会断、进程会重启、模型会选错动作、外部状态会变化。系统不能依靠“再试一次”的直觉,而要用错误分类、幂等、检查点、补偿和终态验证控制不确定性。

选错工具、参数错误、错误理解目标或陷入循环。解决手段包括更清楚的 Tool Schema、状态约束、Eval 和验证器,不应靠无限重试同一 Prompt。

余额不足、订单已发货、权限不允许、Schema 不通过。重复请求不会改变结果,应进入失败、降级或人工处理。

限流、短暂网络中断、服务过载。适合在预算内指数退避,并加入随机抖动,避免大量任务同时重试。

接口已删除、凭据无效、参数协议不兼容。自动重试只会放大压力,应快速失败并告警。

请求可能已产生副作用,但响应丢失。这一类不能按普通网络错误盲目重试,必须通过幂等键或查询接口确认。

可靠系统的第一步不是“增加重试”,而是把错误分到正确的恢复路径。

一个完整重试策略至少说明:

  • 哪些错误允许重试;
  • 最大尝试次数;
  • 单次超时和总截止时间;
  • 退避算法与抖动;
  • 每次尝试是否使用同一幂等键;
  • 重试前是否需要查询外部状态;
  • 预算耗尽后的终态。
attempt 1 -> 429 -> wait 1.2s
attempt 2 -> 429 -> wait 2.6s
attempt 3 -> success

固定间隔会形成“惊群”:上千个 Agent 同时失败,又同时重试。指数退避加随机抖动能分散压力,但仍要受全局截止时间约束。

模型重试也要改变量。若工具 Schema 始终不通过,可以把具体错误返回给模型一次;同样输入、同样上下文连续生成十次,通常只是消费预算。

幂等意味着相同业务请求重复执行,不会产生额外副作用。常见做法是调用方生成稳定 idempotency_key,服务端保存键与结果。

例如:

key = tenant + order_id + operation + business_version

相同键重复请求,服务端返回第一次结果,而不是再次退款。

如果每次尝试生成新 UUID,服务端会把它们当成不同业务请求。

“发送周报”按周和收件组幂等;“发送提醒”可能允许每天一次。键的设计来自业务语义,不是技术层随意拼接。

当响应丢失,调用方应能按幂等键查询结果。只有写接口、没有状态查询,未知状态很难恢复。

四、Exactly Once 通常是业务效果,不是网络魔法

Section titled “四、Exactly Once 通常是业务效果,不是网络魔法”

分布式系统很难保证网络层真正只发送一次。更现实的目标是:消息可以至少传递一次,但业务处理通过幂等记录只产生一次效果。

因此,“Exactly-once Effect”通常由以下组合实现:

  • 稳定业务 ID;
  • 原子写入或事务;
  • 去重记录;
  • 可查询结果;
  • 消费者幂等;
  • Outbox/Inbox 等消息模式;
  • 环境终态验证。

Agent 只是调用方的一部分,不能用 Prompt 承诺 Exactly Once。

五、Checkpoint 保存什么,何时保存

Section titled “五、Checkpoint 保存什么,何时保存”

第 12 讲介绍了 Durable Execution。可靠性角度看,检查点至少要在副作用边界前后保存:

READY_TO_SEND
-> checkpoint(intent + idempotency_key)
-> external side effect
-> checkpoint(observed result)
-> VERIFIED

检查点应包含:

  • 当前状态和状态版本;
  • 已完成步骤;
  • 外部调用的幂等键与资源 ID;
  • 尝试次数与截止时间;
  • 工件引用和哈希;
  • 当前代码、Policy 和 Workflow 版本;
  • 下一步恢复动作。

不能只保存聊天历史。恢复需要的是机器可执行状态,而不是让模型重新猜刚才进行到哪里。

有些副作用无法真正回滚:邮件已被阅读、通知已发送、外部报价已暴露。Saga 模式中的补偿动作只是尽量减轻影响,例如退款的补偿可能是重新扣款或人工复核,并不等价于时间倒流。

设计补偿时应明确:

  • 原动作是否可逆;
  • 补偿由谁授权;
  • 补偿失败怎么办;
  • 用户是否需要收到说明;
  • 审计记录是否保留原始事实。

对不可逆、高影响动作,更好的策略通常是提交前预览和人工批准,而不是依赖事后补偿。

七、Circuit Breaker、Bulkhead 与 Backpressure

Section titled “七、Circuit Breaker、Bulkhead 与 Backpressure”

当下游持续失败时,Agent 不能继续制造调用风暴。

连续错误达到阈值后临时断路,快速失败;冷却后允许少量探测,再决定恢复。

为不同任务、租户或工具隔离并发池。一个慢网页抓取服务不应耗尽所有退款任务资源。

队列超过容量时拒绝、延迟或降级,不让任务无限堆积。长任务应向用户显示排队和预计状态,而不是一直“思考中”。

这些模式不是模型特有,却是 Agent 进入生产后不可回避的基础设施。

八、预算是可靠性控制,不只是费用控制

Section titled “八、预算是可靠性控制,不只是费用控制”

每次运行需要多维预算:

  • 最大模型调用次数;
  • 最大 Tool Calls;
  • 最大 Token 或费用;
  • 墙钟截止时间;
  • 最大并发 Worker;
  • 最大恢复次数;
  • 人工审批等待期限。

预算耗尽应进入明确状态,例如 BUDGET_EXHAUSTEDNEEDS_HUMAN,并保存已有工件。不要让模型用一句“任务已基本完成”掩盖非成功终态。

Loop Engineering 的一个核心价值,就是把继续、重试、验证和停止条件放入可审计控制面,而不是全部留在 Prompt。[1]

LoopX 状态内核示意页面

图 1:旧连载调研中的 LoopX 页面强调显式状态内核;2026-07-20 复核时原仓库已返回 404,因此只作为历史截图,不列为当前可用项目。

九、CI Sweeper 案例:为什么自动修复必须是有界 Loop

Section titled “九、CI Sweeper 案例:为什么自动修复必须是有界 Loop”

一个常见 Coding Agent 场景是:读取 CI 失败、修改代码、运行测试、再次修复,直到通过。

CI Sweeper 项目页面

图 2:CI Sweeper 是把 Issue/CI、Coding Agent 与 GitHub 工作流连接的实例。图片来自项目页面,来源见文末。

这类 Loop 至少要限制:

  • 最大修复轮数;
  • 允许修改的路径;
  • 测试命令与超时;
  • 每轮 Diff 和 Commit 绑定;
  • 禁止自动修改秘密、发布和分支保护;
  • 测试必须针对当前树重新运行;
  • 连续相同错误触发停止,而非死循环。

“测试通过”也不能只取 Agent 的自述,应读取真实退出码、报告和当前 Commit。

十、SLO 应围绕用户任务,而不是模型调用

Section titled “十、SLO 应围绕用户任务,而不是模型调用”

模型 API 99.9% 可用,不代表 Agent 任务 99.9% 成功。端到端还经过检索、工具、队列、审批和验证。

可定义的 SLI 包括:

  • 任务在截止时间内达到已验证终态的比例;
  • 高风险动作零越权率;
  • 重复副作用率;
  • 恢复后成功率;
  • 人工接管等待时间;
  • 每个成功任务成本。

例如:“过去 28 天,95% 的只读研究任务在 5 分钟内完成并附带可访问来源;任何发布动作必须 100% 获得绑定参数的人工批准。”

不同任务风险不同,不能共用一个 SLO。

十一、真实实验:副作用已发生,响应却超时

Section titled “十一、真实实验:副作用已发生,响应却超时”

本讲的 fault_injection_idempotency.py 模拟邮件服务:第一次调用已经把邮件写入外部状态,但故意抛出 response_lost_after_side_effect

Loop 没有立即重发,而是按相同幂等键查询。查询确认邮件已存在,因此终态成功,外部发送计数仍为 1。

{
"external_send_count": 1,
"deduplicated": true,
"terminal": "success"
}

完整结果见 experiment-result.json

实验中的 example.invalid 是保留域名,不会发送真实邮件。它验证了关键恢复原则:副作用状态未知时先查询,不盲目重试。

为实验加入三种故障:

  1. 请求在服务处理前超时;
  2. 请求处理成功但响应丢失;
  3. 服务返回确定性业务拒绝。

分别设计策略:第一种可用相同幂等键重试;第二种先查询;第三种直接失败。再增加两次进程重启,把意图和观察结果写入检查点,确认恢复后仍只发送一次。

最后加入截止时间和最大尝试次数,确保服务持续不可用时进入 NEEDS_HUMAN,而不是永久运行。

十三、故障注入比“等线上出错”更便宜

Section titled “十三、故障注入比“等线上出错”更便宜”

上线前主动测试:

  • 工具 429、500、超时和畸形 JSON;
  • 模型返回空输出或未知工具;
  • 检查点写入失败;
  • 进程在副作用前后崩溃;
  • Worker 重复消费同一消息;
  • 用户在审批期间取消;
  • Policy 或工具版本在恢复前变化。

每次注入都检查领域终态、重复副作用、Trace 和恢复时间。可靠性不是“没有报错”,而是报错时系统仍能进入受控状态。

Agent 可靠性工程的核心,是承认外部世界存在失败和未知状态。

错误分类决定能否重试;幂等和查询控制重复副作用;检查点支持恢复;补偿处理不可避免的部分完成;预算、断路器和背压防止故障扩散;终态验证则决定任务是否真的完成。

下一讲讨论安全:当外部内容能够影响模型,而模型又能调用工具时,Prompt Injection 怎样变成真实权限攻击,系统又该如何防御。


[1] Peter Steinberger, Just Talk To It. https://steipete.me/posts/just-talk-to-it

[2] Temporal, Building Reliable Applications with Durable Execution. https://pages.temporal.io/download-building-reliable-apps-durable-execution

[3] AWS, Retry with backoff pattern. https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/retry-backoff.html

[4] Microsoft Azure Architecture Center, Compensating Transaction pattern. https://learn.microsoft.com/en-us/azure/architecture/patterns/compensating-transaction

[5] CI Sweeper. https://github.com/sweepai/sweep

[6] LoopX 历史仓库,2026-07-20 复核时已不可访问;本文仅保留旧截图用于概念演进说明。