Agent 工具调用失败后,别只重试:先设计回滚和补偿

📅 2026/7/1 9:58:01
Agent 工具调用失败后,别只重试:先设计回滚和补偿
Agent 工具调用失败后别只重试先设计回滚和补偿很多 Agent demo 的工具调用看起来很顺模型决定调哪个 tool传参数拿结果再继续下一步。但真正进生产以后最麻烦的往往不是“工具调不通”而是工具已经对外部系统产生了一半影响工单已经改了状态但客户消息没发出去订单已经创建但库存锁定失败设备配置下发了一部分另一部分超时CRM 里写了跟进记录但审批流没有同步Agent 以为失败了于是重试结果外部系统执行了两次。这类问题不是 prompt 能解决的。它属于生产系统里的状态变更设计。如果 Agent 要接入真实工具尤其是会写业务系统、发消息、改配置、触发设备或影响资金的工具我会先看一件事失败之后系统能不能知道已经发生了什么并且有办法收住。下面是一套我在做 production AI agent systems 审查时会优先看的回滚与补偿清单。1. 先把工具动作分成三类不要把所有工具都叫“tool”。生产里至少要先分清三类动作。第一类是只读动作查询知识库查询工单状态读取设备遥测拉取订单详情。这类动作失败后通常可以重试风险主要在超时、缓存和数据新鲜度。第二类是可撤销写动作写内部草稿创建待审批工单生成未发送消息写入临时任务表。这类动作可以自动化但要有明确的撤销或废弃路径。第三类是外部不可轻易撤销动作发客户消息改生产配置触发设备指令退款、扣款、发货、关闭账户写入会被下游系统消费的正式记录。第三类动作不要只靠“模型判断”。第一版通常应该走人工确认、幂等保护、前置校验和完整审计。一个简单的动作分级表可以这样写tools:get_ticket:action_type:readretry:safecreate_internal_draft:action_type:reversible_writerollback:discard_draftsend_customer_message:action_type:external_side_effectrequire_confirmation:trueidempotency_key:requiredcompensation:create_followup_ticket重点不是 YAML 本身而是让每个工具在进入 Agent 编排之前就已经带着“失败后怎么处理”的语义。2. 每个写工具都要有幂等键Agent 很容易触发重复调用。常见原因包括模型没理解返回结果又调了一次网络超时系统不知道外部是否执行成功worker 重启后重新消费同一条任务用户重复点击上游队列至少一次投递。如果工具没有幂等设计“重试”就可能变成重复发消息、重复创建记录、重复扣减库存。我一般会要求写工具至少带三个字段operationId这次业务动作的唯一编号idempotencyKey外部系统用于识别重复请求previousStateRef执行前状态快照或版本号。示例typeToolCallEnvelopeT{operationId:string;idempotencyKey:string;actor:{userId:string;role:string;};target:{system:crm|ticket|device|billing;resourceId:string;};previousStateRef?:string;payload:T;};这里有一个判断标准如果同一个idempotencyKey被提交两次外部系统应该返回同一个结果而不是执行两次。很多 Agent 事故不是模型“想作恶”只是系统没有把重复调用当成默认会发生的情况。3. 写之前先保存前置状态回滚不是一句“失败就恢复”。你必须知道恢复到哪里。对任何会改变外部状态的工具我会要求执行前保存一份足够小但可用的前置快照。例如改工单状态时至少记录原状态责任人当前版本号关键字段摘要这次变更的原因调用链路 trace id。如果是设备配置或资金相关动作前置快照还要更严格甚至不能让 Agent 直接执行只能生成待审批变更。一个常见的错误是只记录“调用了某个工具”但不记录调用前是什么状态。事后想回滚时只能靠人工去日志里猜。4. 回滚和补偿不是一回事很多系统把 rollback 和 compensation 混在一起但这两件事差别很大。回滚是把状态恢复到之前。补偿是承认某个外部动作已经发生了不能原样撤回只能追加一个修正动作。比如内部草稿写错了可以删除或覆盖这是回滚客户消息已经发出去了不能“撤回成没发生”只能发更正说明或创建人工跟进这是补偿设备指令已经下发了可能需要下发反向指令也可能必须人工现场确认订单已经进入下游履约可能只能创建取消单或异常工单。所以每个高风险工具都应该提前定义这个动作能不能回滚不能回滚时补偿动作是什么补偿动作由谁触发补偿失败时升级给谁。可以用一张很朴素的流程图描述否是否是成功超时或不确定否是Agent 计划调用写工具动作是否有外部副作用允许自动执行是否可幂等转人工确认保存前置状态执行工具调用结果是否确认成功记录审计日志按 idempotencyKey 查询最终状态外部状态已改变?安全重试或终止执行回滚或补偿计划图里的关键点是“不确定”不能直接等同于“失败”。很多外部系统在超时时已经完成了动作如果这时盲目重试事故会被放大。5. 失败状态要分细不要只返回 failed工具返回值如果只有success: true/false对 Agent 来说太粗了。至少要区分参数校验失败权限不足外部系统超时外部系统明确失败外部系统结果未知部分成功已被幂等键拦截需要人工确认。一个更适合编排层消费的返回结构可以是typeToolResultT{status:|succeeded|validation_failed|permission_denied|timeout_unknown|partial_success|compensation_required|human_review_required;operationId:string;idempotencyKey:string;externalRequestId?:string;changedResources?:string[];nextAction?:retry|query_status|compensate|handoff|stop;result?:T;errorSummary?:string;};这样模型或编排器才知道下一步该查状态、补偿、升级人工还是停住。我更倾向于把这类判断放在工具层和编排层而不是把所有失败语义都丢给模型自由发挥。6. 审计日志要记录“为什么执行”不是只记录“执行了什么”工具调用日志只记接口名、参数和返回值还不够。生产里更有价值的是这几类信息用户原始意图Agent 为什么选择这个工具调用前看到了哪些证据是否经过人工确认幂等键是什么前置状态是什么外部系统返回了什么失败后走了哪个补偿路径。如果后面发生争议团队要能回答“这个动作为什么被执行”而不是只能说“日志显示它确实执行了。”这也是 AI Agent Production-Readiness Review 里我会重点看的部分tool-calling、citations、audit logs 和 safe actions 能不能串起来。7. 一个够用的上线前检查清单如果团队准备让 Agent 接入真实写工具我会先问这 10 个问题这个工具是只读、可撤销写还是外部副作用动作这个动作有没有幂等键超时后如何确认外部状态执行前有没有保存前置状态失败后是回滚还是补偿补偿动作本身失败怎么办哪些状态必须人工确认工具返回值是否区分部分成功和结果未知审计日志能否解释为什么执行是否有一条测试用例专门覆盖“执行了一半失败”这 10 个问题里只要有 3 个答不上来就不适合把这个工具直接交给 Agent 自动执行。结论工具调用越强系统越不能只靠“失败就重试”。对生产 Agent 来说真正重要的是把每个外部动作放进可控的状态机里执行前知道原状态执行时有幂等保护失败后能区分未知、部分成功和明确失败必要时能补偿或人工接管。这样做会让第一版 Agent 看起来保守一点但它也会更接近真实生产系统。因为生产环境里最贵的不是少自动化一步而是自动化已经改了外部世界却没人知道该怎么收回来。