Spring AI 1.x 系列【54】Retry 机制分析

Spring AI 1.x 系列【54】Retry 机制分析 文章目录1. 概述2. spring-ai-retry2.1 TransientAiException2.2 NonTransientAiException2.3 RetryUtils — 默认配置工厂1DEFAULT_RESPONSE_ERROR_HANDLER2DEFAULT_RETRY_TEMPLATE3SHORT_RETRY_TEMPLATE3. 自动配置SpringAiRetryAutoConfigurationBean 1RetryTemplateBean 2ResponseErrorHandler4. 配置属性4.1 配置一览4.2 配置示例4.3 退避间隔计算5. 重试生命周期完整请求流程6. 涉及的模型范围7. 流式 vs 非流式8. 关键设计要点8.1 不使用 AOP 注解8.2 不使用过滤器/拦截器8.3 WebFlux 的动态支持8.4 独立使用也具备重试能力8.5 异常体系9. 自定义与扩展9.1 通过配置文件调整9.2 覆盖 Bean — 自定义 RetryTemplate9.3 覆盖 Bean — 自定义 ResponseErrorHandler9.4 针对单个模型覆盖总结1. 概述Spring AI的Retry机制是一个两层架构负责处理AI API调用失败时的自动重试策略。层级职责核心组件异常分类层拦截 HTTP 响应将错误分为可重试和不可重试两类ResponseErrorHandler重试执行层基于异常类型决定是否重试执行退避策略RetryTemplate整体依赖Spring Retry项目采用编程式RetryTemplate.execute()方式而非AOP注解方式。2. spring-ai-retryMaven坐标org.springframework.ai:spring-ai-retry:1.1.4该模块包含三个关键类2.1 TransientAiExceptionpublicclassTransientAiExceptionextendsRuntimeException{publicTransientAiException(Stringmessage){super(message);}publicTransientAiException(Stringmessage,Throwablecause){super(message,cause);}}可恢复异常。表示当前操作在重试后可能成功。典型场景包括HTTP 5xx服务端错误502 Bad Gateway、503 Service Unavailable等网络超时限流导致的临时失败2.2 NonTransientAiExceptionpublicclassNonTransientAiExceptionextendsRuntimeException{publicNonTransientAiException(Stringmessage){super(message);}publicNonTransientAiException(Stringmessage,Throwablecause){super(message,cause);}}不可恢复异常。表示重试不会改变结果必须修复根本原因。典型场景包括HTTP 401API Key无效HTTP 403权限不足HTTP 429配额超限2.3 RetryUtils — 默认配置工厂这是一个抽象工具类提供三组静态常量1DEFAULT_RESPONSE_ERROR_HANDLER内置的响应错误处理器分类逻辑为4xx 客户端错误→ 抛出NonTransientAiException不重试其他错误5xx 等→ 抛出TransientAiException重试2DEFAULT_RETRY_TEMPLATE默认重试模板参数如下参数值最大重试次数10重试的异常类型TransientAiException.class、ResourceAccessException.class退避策略指数退避初始间隔2000ms乘数因子5最大间隔180000ms3 分钟日志监听器每次重试时 WARN 级别打印Retry error. Retry count: N3SHORT_RETRY_TEMPLATE测试用重试模板差异点固定退避100ms不逐步增长日志精简不打印异常堆栈3. 自动配置SpringAiRetryAutoConfiguration类org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration触发条件ConditionalOnClass(RetryUtils.class)— 即classpath上存在spring-ai-retry模块。注册通过META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports自动加载。该配置类定义了两个核心Bean均带有ConditionalOnMissingBean允许用户覆盖Bean 1RetryTemplateBeanConditionalOnMissingBeanpublicRetryTemplateretryTemplate(SpringAiRetryPropertiesproperties)构建逻辑从SpringAiRetryProperties读取maxAttempts配置retryOn(TransientAiException.class)和retryOn(ResourceAccessException.class)配置指数退避参数initialInterval、multiplier、maxInterval动态检测WebFlux如果classpath存在WebClientRequestException也将其设为可重试异常添加日志监听器重试时打印Retry error. Retry count: N, Exception: ...Bean 2ResponseErrorHandlerBeanConditionalOnMissingBeanpublicResponseErrorHandlerresponseErrorHandler(SpringAiRetryPropertiesproperties)异常分类的决策流程有明确的优先级顺序HTTP 响应状态码为 Error │ ├── 1. 状态码在 onHttpCodes 列表中 │ └── YES → 抛 TransientAiException强制重试 │ ├── 2. onClientErrorsfalse 且 状态码为 4xx │ └── YES → 抛 NonTransientAiException默认不重试 │ ├── 3. 状态码在 excludeOnHttpCodes 列表中 │ └── YES → 抛 NonTransientAiException强制不重试 │ └── 4. 兜底 → 抛 TransientAiException默认重试4. 配置属性前缀spring.ai.retry4.1 配置一览属性类型默认值说明spring.ai.retry.max-attemptsint10最大重试次数spring.ai.retry.on-client-errorsbooleanfalse是否对 4xx 错误也进行重试spring.ai.retry.exclude-on-http-codesListInteger[]明确不重试的 HTTP 状态码列表spring.ai.retry.on-http-codesListInteger[]明确需要重试的 HTTP 状态码列表spring.ai.retry.backoff.initial-intervalDuration2000ms首次重试前的等待时间spring.ai.retry.backoff.multiplierint5指数退避的乘数因子spring.ai.retry.backoff.max-intervalDuration180000ms两次重试之间的最大等待时间4.2 配置示例spring:ai:retry:max-attempts:5# 最多重试 5 次on-client-errors:false# 4xx 不重试默认backoff:initial-interval:1s# 首次重试等待 1 秒multiplier:2# 指数乘数 2max-interval:30s# 最大间隔 30 秒exclude-on-http-codes:-429# 429 不重试配额超限重试无意义-503on-http-codes:-502# 502 明确重试4.3 退避间隔计算指数退避公式下一次间隔min(initialInterval × multiplier^(retryCount), maxInterval)以默认配置为例initialInterval2s,multiplier5,maxInterval180s重试次数等待间隔第 1 次2s第 2 次10s第 3 次50s第 4 次180s触及上限第 5 次及以后180s总耗时10次全失败约21050180×7≈22分钟后放弃。5. 重试生命周期完整请求流程以OpenAI Chat调用为例展示一次API请求从发起到最终响应的完整链路User Code │ ▼ OpenAiChatModel.call(Prompt) │ ▼ OpenAiChatModel.internalCall() │ ├── 1. 构造 ChatCompletionRequest │ ├── 2. Observation 埋点包装 │ └── 3. retryTemplate.execute(ctx - { return this.openAiApi.chatCompletionEntity(request, headers); }) │ ▼ OpenAiApi.chatCompletionEntity() │ ▼ RestClient (已注册 ResponseErrorHandler) │ ├── [成功] → 返回 ResponseEntityChatCompletion → 解析为 ChatResponse │ └── [失败] → ResponseErrorHandler 介入 │ ├── 抛 NonTransientAiException → RetryTemplate 不重试直接失败 │ └── 抛 TransientAiException → RetryTemplate 捕获 │ ├── 未达 maxAttempts → 指数退避等待 → 重试 │ └── 日志: Retry error. Retry count: 1, Exception: ... │ └── 已达 maxAttempts → 抛出最终异常代码证据在OpenAiChatModel.internalCall()中// 第 199-200 行核心调用被 retryTemplate.execute() 包裹ResponseEntityChatCompletioncompletionEntitythis.retryTemplate.execute(ctx-this.openAiApi.chatCompletionEntity(request,getAdditionalHttpHeaders(prompt)));在OpenAiChatModelBuilder中第806-809行retryTemplate默认值来自RetryUtils.DEFAULT_RETRY_TEMPLATE可通过OpenAiChatModel.builder().retryTemplate(...)覆盖。6. 涉及的模型范围所有模型自动配置均通过AutoConfiguration(after { ... , SpringAiRetryAutoConfiguration.class, ... })确保Retry Bean先于模型Bean创建然后同时注入RetryTemplate和ResponseErrorHandler。模块模型使用 RetryTemplate使用 ResponseErrorHandlerOpenAIChat / Embedding / Image / AudioSpeech / AudioTranscription / Moderation✓✓Azure OpenAI同一套 API 客户端✓✓DeepSeekChat✓✓智谱 AIChat / Embedding / Image✓✓Mistral AIChat / Embedding / Moderation✓✓MiniMaxChat / Embedding✓✓Vertex AI GeminiChat✓✓Vertex AIText Embedding✓✓Google GenAIChat / Text Embedding✓✓AnthropicChat✓✓OllamaChat✓✓ElevenLabsTextToSpeech✓✓实际上只要模型通过RestClient发起 HTTP 调用并接受RetryTemplate注入就会自动纳入这套机制。7. 流式 vs 非流式这是一个重要的设计细节调用方式重试行为同步调用call()retryTemplate.execute()包裹整个 HTTP 调用完整支持重试流式调用stream()不使用RetryTemplate不存在重试机制以OpenAiChatModel为例// 同步调用 — 第 199-200 行有 retryTemplate 包裹ResponseEntityChatCompletioncompletionEntitythis.retryTemplate.execute(ctx-this.openAiApi.chatCompletionEntity(request,...));// 流式调用 — 第 271-279 行直接调用 API无重试publicFluxChatResponseinternalStream(Promptprompt,...){returnFlux.deferContextual(contextView-{// 直接调用 openAiApi.chatCompletionStream()没有被 retryTemplate 包裹...});}这意味着同步调用遇到5xx会自动重试最多10次流式调用遇到错误直接失败不会自动重试。但初始连接建立时的4xx错误如API Key无效仍会被ResponseErrorHandler拦截为NonTransientAiException并快速失败8. 关键设计要点8.1 不使用 AOP 注解Spring AI不使用Retryable/EnableRetry注解。在整个代码库中零使用。重试是通过在模型方法中直接调用RetryTemplate.execute()实现的编程式重试。原因推测编程式重试允许在retryTemplate.execute()内部配合Observation可观测性埋点保持调用链路完整同时也便于在Builder模式中替换默认的RetryTemplate。8.2 不使用过滤器/拦截器重试不通过HTTP Filter或Spring Interceptor实现。异常分类在ResponseErrorHandlerRestClient层面重试执行在模型方法内部两者职责清晰分离。8.3 WebFlux 的动态支持SpringAiRetryAutoConfiguration通过反射动态检测WebClientRequestExceptiontry{Class?webClientRequestExClass.forName(org.springframework.web.reactive.function.client.WebClientRequestException);builder.retryOn((Class?extendsThrowable)webClientRequestEx);}catch(ClassNotFoundExceptionignore){// WebFlux 不在 classpath跳过}这使得WebFlux项目的预响应网络错误如DNS解析失败、连接被拒也能享受重试能力。8.4 独立使用也具备重试能力每个模型的Builder默认使用RetryUtils.DEFAULT_RETRY_TEMPLATE因此即使不依赖Spring Boot自动配置直接new出来的模型实例同样具备默认重试行为。8.5 异常体系RuntimeException ├── TransientAiException ← 可重试服务端错误、网络抖动 └── NonTransientAiException ← 不可重试认证失败、参数错误ResourceAccessExceptionSpring Web的网络I/O异常也被纳入重试范围与TransientAiException平级。9. 自定义与扩展9.1 通过配置文件调整spring:ai:retry:max-attempts:3on-client-errors:true# 对 4xx 也重试不推荐backoff:initial-interval:500msmultiplier:2max-interval:10son-http-codes:-429# 限流错误也重试9.2 覆盖 Bean — 自定义 RetryTemplate两类Bean均加有ConditionalOnMissingBean用户可自行定义覆盖ConfigurationpublicclassCustomRetryConfig{BeanpublicRetryTemplateretryTemplate(){returnRetryTemplate.builder().maxAttempts(3).retryOn(TransientAiException.class).fixedBackoff(Duration.ofSeconds(1))// 固定 1 秒退避.withListener(newRetryListener(){OverridepublicT,EextendsThrowablevoidonError(RetryContextctx,RetryCallbackT,Ecb,Throwablet){// 自定义重试监控如上报 Metricslog.error(AI API retry #{},ctx.getRetryCount(),t);}}).build();}}9.3 覆盖 Bean — 自定义 ResponseErrorHandlerBeanpublicResponseErrorHandlerresponseErrorHandler(){returnnewResponseErrorHandler(){OverridepublicbooleanhasError(ClientHttpResponseresponse)throwsIOException{returnresponse.getStatusCode().isError();}OverridepublicvoidhandleError(ClientHttpResponseresponse)throwsIOException{// 自定义错误分类所有 non-2xx 一律重试StringbodyStreamUtils.copyToString(response.getBody(),StandardCharsets.UTF_8);thrownewTransientAiException(String.format(HTTP %s - %s,response.getStatusCode().value(),body));}};}9.4 针对单个模型覆盖也可以不覆盖全局Bean而是在创建特定模型时传入自定义RetryTemplateRetryTemplateshortRetryRetryTemplate.builder().maxAttempts(3).exponentialBackoff(500,2,5000).build();OpenAiChatModelchatModelOpenAiChatModel.builder().openAiApi(openAiApi).defaultOptions(options).retryTemplate(shortRetry)// 仅对此 ChatModel 生效.build();总结Spring AI的Retry机制优雅地处理了AI API调用的不稳定性异常分类通过ResponseErrorHandler区分可恢复Transient与不可恢复NonTransient错误重试执行通过编程式RetryTemplate实现而非AOP注解保持了调用链路的完整性默认策略为指数退避2s→10s→50s→ … →180s最多重试10次全覆盖几乎所有AI模型的同步调用都透明地享受重试能力局限流式调用stream()不支持自动重试4xx客户端错误默认不重试高可定制通过配置文件、Bean覆盖或模型级参数均可灵活调整