先知道失败发生在哪里
这篇继续使用工具契约练习。测试会注入连接异常、慢协程和错误返回值,不需要真的让外部服务出故障。
| 错误类别 | 本练习的处理 | 理由 |
|---|---|---|
| unknown_tool | 直接失败 | 当前注册表不允许这个工具 |
| input_validation | 直接失败 | 相同错误参数再执行没有帮助 |
| output_validation | 直接失败 | 工具返回值违背接口约定 |
| business | 直接失败 | 业务条件不满足,应检查业务原因 |
| timeout / connection | 仅对允许重复执行的工具有限重试 | 暂时故障可能恢复,但重复动作也可能有风险 |
| internal | 返回固定失败类别,不重试 | 未知异常不能默认可恢复 |
这个分类是教学实现的选择。真实 SDK 往往使用自己的异常类型,接入时需要明确映射;不能只捕获 Python 内置 ConnectionError 就声称处理了所有网络错误。
把次数与等待都限制住
配套程序的 max_attempts 包含第一次调用,默认最多尝试三次。第一次成功就结束;连续失败时,只在两次尝试之间等待。它没有无限 while 重试。
delay = min(cap_delay, base_delay * (2 ** (attempt - 1))) * rng()
await sleep(delay)这段使用带上限的指数退避与随机抖动。本例 rng 默认取 0 到 1 的随机数;测试把它固定为 0.5,并替换 sleep 为记录函数,所以能检查等待值,而无需真的等待退避时长。
在默认 base_delay=0.05、cap_delay=0.5 下,测试中两次等待分别为 0.025 与 0.05 秒。这些数值用来演示机制,不是推荐给生产系统的参数。AWS 重试指南强调限制重试、退避与抖动,避免故障时进一步放大压力。
超时不等于动作没发生
程序用 asyncio.wait_for 对每次协程调用设置超时。根据 Python 协程文档,它会尝试取消被等待任务,并等待取消完成。因此它不是强制终止任意代码的硬开关,实际等待时间可能超过设置值。
同步阻塞、吞掉取消异常的协程,以及已经发送到远端的请求,都需要额外考虑。本练习只使用能响应取消的异步工具;调用方主动取消时,CancelledError 会继续向上传递,不伪装成普通业务失败。
如果以后接入写数据库、发邮件或支付工具,超时可能只意味着“没有收到结果”。再次执行可能重复产生副作用。retry_safe 在这里是由程序作者登记的元数据,不能让模型自行决定;设置为 True 也不会自动赋予幂等性。AWS 幂等 API 说明可作为进一步阅读。
用故障测试检查分支
| 测试情景 | 预期 |
|---|---|
| 前两次连接失败,第三次返回 42 | 共调用三次,有两次等待 |
| 一直连接失败 | 达到次数上限后返回 connection |
| 不允许重复执行的工具连接失败 | 只尝试一次,无退避 |
| 工具返回错误类型 | output_validation,不重试 |
| 慢协程超过时限 | 返回 timeout,清理分支执行 |
| 调用方取消运行 | 取消向上传递,不自动重试 |
这些测试在配套程序中已经通过。测试的是可控离线场景,不包含真实限流响应、服务端副作用或数据库事务。HTTP 429、Retry-After、SDK 自带重试与全局时间预算,在真实接入时还要单独设计。
还有哪些没有覆盖
每次调用有超时,不代表整个 Agent 有截止时间;多个工具与多轮推理叠加后,仍可能耗时很长。生产设计需要统一预算和取消路径,也要避免 SDK 与外层重复重试。
工具轮次上限、调用超时、总运行预算分别限制不同维度。先把这几条规则说清楚,再决定在何处加限制。后续可在日志专题中检查每次尝试留下的记录。
来源
承接个人学习摘要中的错误分类、timeout、retry 与 backoff;技术边界核对自上方 Python 与 AWS 官方资料。故障场景由配套测试注入。核对日期:2026-09-21。
试着做一个小练习
把测试工具的 retry_safe 改为 False,检查连接错误是否只调用一次。