文章按调用是否可能产生副作用区分权限拒绝、并发冲突、额度耗尽和发送后结果不明等错误,反对统一退避重试。涉及支付或状态变更时,重试策略应建立在调用执行阶段的判断上。
Agent 调用工具后收到了错误,该不该再试一次?
大多数 Agent 循环都用同一条规则回答这个问题:遇到错误,就退避重试几次。我认为,只要工具涉及资金转移或状态变更,这条规则就不适用,因为“错误”至少涵盖四种截然不同的情况,其中只有一种可以安全地盲目重试。
这篇文章介绍了我在 Agent Middleware(AMW)中使用的错误分类。AMW 是我正在开发的工具调用网关,为 Agent 的工具调用提供许可和回执管理。下面的代码只是通用示例,并非摘自我的代码仓库。
在决定是否重试之前,Agent 需要知道:这次调用是否已经执行到了可能产生副作用的阶段。我按照这个问题的答案,对所有错误进行分类。
尚未预留任何资源,就被拒绝。 许可不允许这次调用:可能是工具不匹配、许可已过期,或者某项约束后端无法强制执行。没有写入任何数据,没有扣款,也没有发送任何内容。
尚未写入任何数据,就在并发竞争中落败。 两次调用同时尝试更新同一份许可,这次调用没抢到。没有写入任何数据,没有扣款,也没有发送任何内容。
因为达到限额而被拒绝。 许可为每个工具设置了调用次数上限,而次数已经用完。没有产生新的扣款或发送操作,但此前的调用确实已经发生。
请求已发出,却一直没有收到响应。 请求已经离开网关,但响应丢失了。副作用可能已经发生,也可能没有发生。
只有第二种情况,才属于“再试一次就行”的错误。
预留资源前被拒绝:不要重试。 重试完全相同的请求,只会得到完全相同的拒绝。这个问题需要由许可签发方解决。在 AMW 中,这种情况会返回一份带签名的拒绝回执,让 Agent 留下停止执行的原因记录。
并发竞争失败:使用相同的 idempotency key 重试。 AMW 会返回 HTTP 409;如果调用的是 MCP endpoint,则返回 JSON RPC error -32005。事务已经回滚,这次尝试对应的幂等记录已经释放,也没有转移任何额度。这里必须复用相同的 key:如果第一次尝试由于某种原因其实已经完成,相同的 key 会返回已保存的回执,而不会再次派发请求。
达到限额:不要重试,更不要换一个新的 key。 新 key 代表一次新调用,而不是对原调用的重试。如果许可为每个工具设置了调用次数上限,并且次数已经用完,那么使用新 key 会收到 cap exceeded 错误,不会派发请求,也不会扣款。如果许可没有设置调用次数上限,新 key 就会在许可的预算和有效期内再执行一次。所以,只有设置了调用次数上限,它才能起到保护作用。
请求已发出但没有响应:挂起处理。 我专门写过一整篇文章讨论这种情况。简而言之:AMW 会将这次尝试记录为 delivery uncertain,保留扣费,并且绝不自行重发。在进行任何后续操作之前,需要由人工或对账任务向服务提供方核实。
def handle_tool_error(err, call):
if err.kind == "denied":
# Permit does not allow it. Retrying changes nothing.
return stop(reason=err.code, receipt=err.receipt)
if err.kind == "contended":
# Nothing was written. Safe to retry with the SAME key.
return retry(call, idempotency_key=call.idempotency_key,
backoff=jittered(base_ms=50, max_tries=3))
if err.kind == "cap_exceeded":
# Earlier calls happened. A new key would be a new call.
return stop(reason="cap reached", receipt=err.receipt)
if err.kind == "delivery_uncertain":
# Side effect may exist. Do not retry at all.
return park(call, for_review=True)
# Unknown error class: treat it as uncertain, not as safe.
return park(call, for_review=True)
最后一行是我最在意的部分。当 Agent 无法判断错误属于哪一类时,安全的默认行为应该是停下来询问,而不是继续循环。
这些措施都不能保证远端副作用恰好发生一次。调用次数上限设为 1,意味着对于这份许可和这个工具,网关最多派发一次请求。至于服务提供方是否真的只执行一次,仍然取决于它是否遵守 idempotency key 的约定。这套错误分类只是防止 Agent 把一次结果不确定的调用,变成两次确定发生的调用。
AMW 还处于早期阶段,目前由我独自开发。我正在寻找开发支付类 Agent,或使用其他不可逆操作工具的 Agent 的人,希望你们来试着找出它的问题。
你的 Agent 循环如何判断哪些错误可以安全重试?我很想听听这套分类有哪些不对的地方。
更多内容见 thisisatest.tech。
如需采取进一步措施,你可以考虑屏蔽此人,或举报滥用行为。