当前位置: 首页 > news >正文

Spring AI遇到429或超时后为什么重复执行工具?重试边界与幂等完整排查

文章摘要

AI接口出现429、超时或连接中断后,开发者通常会增加自动重试。但在Tool Calling场景中,如果重试包裹了整个Agent流程,退款、发送邮件、创建工单、写数据库等工具可能被重复执行。更隐蔽的情况是模型请求超时,但工具其实已经完成;客户端重试后,模型再次发起相同工具调用。本文从模型层、Agent层、工具层和HTTP层四个重试边界出发,给出幂等键、状态机、结果查询和可重试错误分类的完整方案。

一、典型事故

用户说:

给客户创建一个售后工单

执行链路:

模型选择create_ticket → 工具创建工单成功 → 返回模型时连接超时 → Agent整体自动重试 → 再次调用create_ticket → 创建第二个工单

从用户视角只发了一次请求,系统却产生两个业务对象。

如果工具是:

  • 退款;
  • 支付;
  • 发券;
  • 发邮件;
  • 删除数据;
  • 创建订单;

后果会更严重。

二、为什么“重试一次”会跨越多个层级

一个AI请求可能同时存在:

网关重试 HTTP客户端重试 Spring AI Provider重试 Resilience4j重试 Agent步骤重试 工具SDK重试 消息队列重投

如果每层都重试3次,最坏情况不是3次,而可能是乘法放大。

例如:

网关2次 × 应用3次 × 工具SDK3次 = 18次潜在调用

必须明确每层的职责。

三、四种重试边界

1. 模型调用重试

适合:

  • 429;
  • 暂时性5xx;
  • 连接建立失败;
  • 无副作用的模型请求。

风险:

如果模型调用发生在工具执行后,重试可能重新生成工具调用。

2. Agent步骤重试

适合:

  • 结构化输出解析失败;
  • 计划校验失败;
  • 可恢复的推理错误。

风险:

整个步骤可能包含多个工具副作用。

3. 工具调用重试

适合:

  • 只读查询;
  • 明确幂等写入;
  • 服务端支持幂等键。

4. 业务流程重试

适合:

  • 有持久化状态机;
  • 可以查询当前执行状态;
  • 能从检查点继续。

不能简单重新运行整个流程。

四、哪些错误可以自动重试

通常可重试

429 rate_limit_exceeded 502 503 504 连接被拒绝 短暂DNS失败 读超时且确认无副作用

通常不可直接重试

400参数错误 401认证失败 403权限不足 404资源不存在 insufficient_quota 内容安全拒绝 业务校验失败

状态未知

最危险的是:

请求超时

超时只说明客户端没有按时收到结果,并不说明服务端没有执行。

写操作超时后应该:

先查询执行状态 → 再决定是否重试

五、幂等键必须在模型之外生成

不要让模型自己生成随机幂等键。

模型可能每次重试都生成不同值。

正确做法:

业务请求进入 → 应用生成operationId → 同一个业务动作的所有重试复用

例如:

StringidempotencyKey=String.join(":",tenantId,conversationId,requestId,"create_ticket");

如果一次请求中允许创建多个工单,还要加入业务对象标识或步骤编号。

六、工具服务端如何实现幂等

表结构:

CREATETABLEtool_idempotency(idempotency_keyVARCHAR(200)PRIMARYKEY,tool_nameVARCHAR(100)NOTNULL,request_hashVARCHAR(128)NOTNULL,statusVARCHAR(30)NOTNULL,result_jsonTEXT,created_atTIMESTAMPNOTNULL,updated_atTIMESTAMPNOTNULL);

状态:

PROCESSING SUCCEEDED FAILED_RETRYABLE FAILED_FINAL

执行流程:

收到请求 → 插入PROCESSING → 已存在则读取状态 → SUCCEEDED直接返回历史结果 → PROCESSING返回处理中 → 可重试失败按规则执行

伪代码:

@TransactionalpublicToolResultexecute(Stringkey,ToolRequestrequest){Optional<IdempotencyRecord>existing=repository.findById(key);if(existing.isPresent()){returnrestore(existing.get(),request);}repository.insertProcessing(key,hash(request));try{ToolResultresult=doExecute(request);repository.markSucceeded(key,result);returnresult;}catch(RuntimeExceptionex){repository.markFailed(key,ex);throwex;}}

七、相同幂等键但参数不同怎么办

攻击或代码错误可能发送:

相同key +不同参数

例如第一次退款100元,第二次使用同一个key退款200元。

服务端必须比较request_hash

如果不同:

返回409 Conflict

不能把第二次请求当成第一次的成功结果。

八、模型返回的tool_call_id能不能当幂等键

不建议单独使用。

tool_call_id通常只在一次模型响应中唯一。

Agent整体重试后,模型可能生成新的ID。

更稳定的是:

业务operationId +工具名 +步骤ID

可以把tool_call_id作为追踪字段,而不是唯一业务幂等依据。

九、重试应该包裹哪一层

错误:

@Retry(name="ai")publicStringrunAgent(Stringmessage){returnagent.run(message);}

如果agent.run()内部执行写工具,整个流程会重跑。

更安全:

模型只读推理调用 → 可重试 工具写操作 → 幂等执行 最终回答生成 → 可重试,但复用工具结果

将流程持久化:

PLANNED TOOL_EXECUTED ANSWER_GENERATING COMPLETED

最终回答失败后,从TOOL_EXECUTED继续,不再重复执行工具。

十、检查点设计

publicrecordAgentCheckpoint(StringexecutionId,Stringstate,StringtoolName,StringtoolResultLocation,intmodelAttempt,inttoolAttempt){}

执行:

模型选工具 → 保存计划 → 工具执行 → 保存结果 → 模型生成回答

任何一步失败都从最近检查点恢复。

十一、只读工具是否可以随便重试

只读工具通常风险较低,但仍可能有:

  • 外部API计费;
  • 强限流;
  • 数据查询压力;
  • 非稳定快照;
  • 重复下载大文件。

建议设置:

最大重试次数 指数退避 随机抖动 总超时 并发限制

十二、指数退避与Jitter

固定间隔:

1秒、1秒、1秒

大量实例会同时重试,造成惊群。

推荐:

1秒 2秒 4秒

并加入随机抖动。

Resilience4j示例:

resilience4j:retry:instances:aiModel:max-attempts:3wait-duration:1senable-exponential-backoff:trueexponential-backoff-multiplier:2retry-exceptions:-java.io.IOException-java.util.concurrent.TimeoutException

异常列表需要按实际Provider SDK调整。

十三、429的Retry-After要不要遵守

如果响应提供:

Retry-After: 10

应优先遵守。

但还要区分:

rate_limit_exceeded → 等待后重试 insufficient_quota → 不重试

两者都可能是HTTP 429。

十四、熔断器应该包在哪里

建议在模型Provider适配层设置熔断:

业务Service → ModelGateway → Circuit Breaker → Provider

不要用一个熔断器同时覆盖:

  • 模型;
  • 向量库;
  • 所有工具;

否则其中一个工具失败会关闭整个AI系统。

按依赖隔离:

openai-chat qdrant-search order-tool mail-tool

十五、降级策略

模型不可用:

Sol → Terra → Luna → 规则模板

RAG不可用:

生成回答 → 降级为关键词搜索结果

写工具不可用:

自动执行 → 创建待办 → 转人工

降级不能绕过审批和权限。

十六、需要记录哪些指标

model_retry_count tool_retry_count agent_restart_count idempotency_hit_count idempotency_conflict_count unknown_execution_status_count circuit_breaker_open_count fallback_model_count duplicate_business_object_count

重点告警:

同一operationId出现多个业务对象

十七、完整排查清单

□ 是否同时存在多层重试 □ 重试是否包裹整个Agent □ 写工具是否支持幂等键 □ 相同业务动作是否复用同一个key □ 是否保存request_hash □ 超时后是否先查询状态 □ 最终回答失败是否重复执行工具 □ tool_call_id是否被误作唯一幂等键 □ 429是否区分限流与额度不足 □ 熔断器是否按依赖隔离 □ 是否有检查点和状态机

总结

Tool Calling场景中,最大的错误不是“没有重试”,而是:

在错误的边界重试

生产级方案应该做到:

模型调用可重试 +工具写操作幂等 +业务流程有检查点 +超时先查状态 +最终回答复用工具结果

只有把模型推理和业务副作用分开,自动重试才不会变成重复执行。

http://www.jsqmd.com/news/1303759/

相关文章:

  • 如何在Blender中实现3MF格式的完整导入导出:终极指南
  • 河池CMA甲醛检测公司怎么选:只测不除的专业实验室——国康CMA检测及公共卫生检测 - 副本 - 信誉隆金银铂奢回收
  • 2026年8月贵州省移动1000M宽带办理攻略 - 找卡家园
  • 2026年广州地区服装业绩提升机构选择参考指南 - 奔跑123
  • 2026年8月海南省联通300M单宽带申请避坑实录 - 找卡家园
  • 2026年浙江比对旋转锉供应方 逛厂场景的观察笔记 - 奔跑123
  • 德宏CMA甲醛检测公司怎么选:只测不除的专业实验室——国康CMA检测及公共卫生检测 - 信誉隆金银铂奢回收
  • CompressO终极指南:免费开源视频图片压缩工具如何将大文件变“浓缩咖啡“
  • 2026年8月杭州市电信300M单宽带怎么办理 - 找卡家园
  • 7 月 Agent 开发月度回顾:从架构设计到性能优化再到生产落地
  • 梧州CMA甲醛检测公司怎么选:只测不除的专业实验室——国康CMA检测及公共卫生检测 - 副本 - 信誉隆金银铂奢回收
  • 德语论文辅导平台使用感受分享
  • 2026年8月贵州省六盘水市移动宽带办理避坑全攻略 - 找卡家园
  • Kimi 长回答做会议纪要,存 Word 还是 PDF?DS随心转按用途导出 - 【DS随心转】
  • 2026年宁波选购汽车内饰发泡设备的厂家选择指南 - 奔跑123
  • 看完就会:盘点2026年巅峰之作的的降AIGC网站
  • 2026年宁波家装施工地膜采购日常场景经验梳理 - 奔跑123
  • 2026年8月贵州省移动1000M单宽带攻略与避坑指南 - 找卡家园
  • 河池CMA甲醛检测公司怎么选:只测不除的专业实验室——国康CMA检测及公共卫生检测 - 信誉隆金银铂奢回收
  • 如何用TuxGuitar免费吉他软件创作专业级乐谱:从零到精通的完整指南
  • AI Agent 技术月度展望:总结 7 月的收获并规划 8 月的学习与实践方向
  • 梧州CMA甲醛检测公司怎么选:只测不除的专业实验室——国康CMA检测及公共卫生检测 - 信誉隆金银铂奢回收
  • 2026装配式组合水箱实力厂家甄选:SMC/BDF/不锈钢水箱品牌与源头工厂能力洞察 - 优企名品
  • 2026年宁波海克斯康三坐标哪家好 实地使用场景分享 - 奔跑123
  • 2026优选:户外旅游取暖炉定制厂家,为什么千石藤次郎值得信赖? - 装修教育财税推荐2026
  • 德语论文辅导平台使用评测参考
  • 成都CMA甲醛检测公司怎么选:只测不除的专业实验室——国康CMA检测及公共卫生检测 - 信誉隆金银铂奢回收
  • 2026年8月广东省广州市移动单宽带怎么办理 - 找卡家园
  • 广安CMA甲醛检测公司怎么选:只测不除的专业实验室——国康CMA检测及公共卫生检测 - 副本 - 信誉隆金银铂奢回收
  • 2026年8月广东省移动1000M单宽带实测办理全流程 - 找卡家园