{"record":{"id":"c2fd3270e268341d","repo":"alibaba/spring-ai-alibaba","slug":"model-call-failed-maximum-number-of-retries-reach","errorCode":null,"errorMessage":"Model call failed, maximum number of retries reached:${exceptionText}","messagePattern":"Model call failed, maximum number of retries reached:(.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/interceptor/modelretry/ModelRetryInterceptor.java","lineNumber":115,"sourceCode":"\t\t\t\t\t// Extract anomaly information from the text and determine whether a retry is possible.\n\t\t\t\t\tif (attempt < maxAttempts && isRetryableExceptionMessage(exceptionText)) {\n\t\t\t\t\t\tlastException = new RuntimeException(exceptionText);\n\t\t\t\t\t\t// Wait and try again\n\t\t\t\t\t\tif (currentDelay > 0) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tlog.info(\"Retry after {} ms\", currentDelay);\n\t\t\t\t\t\t\t\tThread.sleep(currentDelay);\n\t\t\t\t\t\t\t} catch (InterruptedException e) {\n\t\t\t\t\t\t\t\tThread.currentThread().interrupt();\n\t\t\t\t\t\t\t\tthrow new RuntimeException(\"Retry interrupted\", e);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Calculate the next delay time (exponential backoff)\n\t\t\t\t\t\tcurrentDelay = Math.min((long) (currentDelay * backoffMultiplier), maxDelay);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t} else if (attempt >= maxAttempts) {\n\t\t\t\t\t\tlog.error(\"The maximum number of retries has been reached {}, and the model call has failed.\", maxAttempts);\n\t\t\t\t\t\tthrow new RuntimeException(\"Model call failed, maximum number of retries reached:\" + exceptionText);\n\t\t\t\t\t}\n\n\t\t\t\t\t// For non-retryable exceptions, return immediately.\n\t\t\t\t\treturn modelResponse;\n\t\t\t\t}\n\n\t\t\t\t// Successful response\n\t\t\t\tif (attempt > 1) {\n\t\t\t\t\tlog.info(\"The model call succeeded after the {}th attempt.\", attempt);\n\t\t\t\t}\n\t\t\t\treturn modelResponse;\n\n\t\t\t} catch (Exception e) {\n\t\t\t\tlastException = e;\n\t\t\t\tlog.warn(\"Model call failed (attempted {}/{}): {}\", attempt, maxAttempts, e.getMessage());\n\n\t\t\t\tif (attempt >= maxAttempts) {\n\t\t\t\t\tlog.error(\"The maximum number of retries has been reached {}, and the model call has failed.\", maxAttempts);","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/interceptor/modelretry/ModelRetryInterceptor.java#L97-L133","documentation":"ModelRetryInterceptor detected an 'Exception:...' text payload in the model response (an error captured upstream as message text) and retried it until attempt >= maxAttempts without success. It throws RuntimeException('Model call failed, maximum number of retries reached:' + exceptionText) carrying the embedded error text. Note the original exception is not a cause here — the detail is embedded in the message string.","triggerScenarios":"interceptModel receiving a non-streaming response whose message text starts with 'Exception:', where isRetryableExceptionMessage(text) matched (contains connection/timeout/network/etc.), for all maxAttempts attempts. The last attempt takes the `attempt >= maxAttempts` branch at line 113-115.","commonSituations":"A persistent network problem to the LLM provider (proxy down, DNS failure, endpoint unreachable) that never recovers within the retry budget; retries too few/fast (maxAttempts=3 with 1s initial delay) for a longer outage; AgentLlmNode wrapping real exceptions as text so the cause chain is unavailable.","solutions":["Read the exceptionText after the colon in the message — it names the underlying provider/network error; fix that root cause.","Increase maxAttempts and/or delays via the builder so transient outages have time to clear.","Verify provider connectivity out-of-band (curl the endpoint) and check API key/quota status.","If the error is not actually transient (e.g. auth failure misreported with 'connection' in the text), tighten isRetryableExceptionMessage via retryableExceptionPredicate or fix the upstream text."],"exampleFix":"// before: too small a budget for flaky network\nModelRetryInterceptor.builder().maxAttempts(3).initialDelay(1000).build();\n// after\nModelRetryInterceptor.builder().maxAttempts(5).initialDelay(2000).maxDelay(30000).backoffMultiplier(2.0).build();","handlingStrategy":"retry","validationCode":"// Pre-flight connectivity check before the call\ntry (var s = new Socket()) { s.connect(new InetSocketAddress(host, 443), 3000); }\ncatch (IOException e) { throw new IllegalStateException(\"Provider endpoint unreachable: \" + host, e); }","typeGuard":null,"tryCatchPattern":"try {\n    return retryInterceptor.interceptModel(request, handler);\n} catch (RuntimeException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Model call failed, maximum number of retries reached:\")) {\n        String rootText = e.getMessage().substring(e.getMessage().indexOf(':') + 1);\n        log.error(\"Retry exhausted; embedded provider error: {}\", rootText);\n        throw new ProviderUnavailableException(rootText, e);\n    }\n    throw e;\n}","preventionTips":["Size maxAttempts/delays to exceed your provider's typical transient-outage duration.","Alert on retry-rate metrics rather than waiting for exhaustion errors.","Pair with ModelFallbackInterceptor so exhaustion falls through to another provider."],"tags":["retry-exhausted","network","llm","timeout"],"backgroundTag":"retry-limit-exceeded","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}