{"record":{"id":"47768bf3f7456e10","repo":"alibaba/spring-ai-alibaba","slug":"model-call-failed-non-retryable-exception","errorCode":null,"errorMessage":"Model call failed (non-retryable exception)","messagePattern":"Model call failed \\(non-retryable exception\\)","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":140,"sourceCode":"\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);\n\t\t\t\t\tthrow new RuntimeException(\"Model call failed, maximum number of retries reached.\", lastException);\n\t\t\t\t}\n\n\t\t\t\t// Determine if an exception can be retried.\n\t\t\t\tif (!retryableExceptionPredicate.test(e)) {\n\t\t\t\t\tlog.warn(\"Exceptions cannot be retried and are thrown immediately: {}\", e.getMessage());\n\t\t\t\t\tthrow new RuntimeException(\"Model call failed (non-retryable exception)\", e);\n\t\t\t\t}\n\n\t\t\t\t// Wait and try again\n\t\t\t\tif (currentDelay > 0) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlog.info(\"Retry after {} ms\", currentDelay);\n\t\t\t\t\t\tThread.sleep(currentDelay);\n\t\t\t\t\t} catch (InterruptedException ie) {\n\t\t\t\t\t\tThread.currentThread().interrupt();\n\t\t\t\t\t\tthrow new RuntimeException(\"Retry interrupted\", ie);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Calculate the next delay time (exponential backoff)\n\t\t\t\tcurrentDelay = Math.min((long) (currentDelay * backoffMultiplier), maxDelay);\n\t\t\t}\n\t\t}\n","sourceCodeStart":122,"sourceCodeEnd":158,"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#L122-L158","documentation":"ModelRetryInterceptor caught an exception from the model call whose retryableExceptionPredicate returned false, meaning the failure is not transient (per the default: no I/O/connection/timeout/handshake/socket wording, no ResourceAccessException/WebClientRequestException, no IO/Socket/Connect/Timeout/SSL cause). It rethrows immediately as RuntimeException(\"Model call failed (non-retryable exception)\", e) instead of retrying.","triggerScenarios":"interceptModel where handler.call throws an exception that the configured predicate classifies as non-retryable — e.g. HTTP 400 invalid request, 401 authentication failure, context-length errors, or exceptions whose message/cause chain matches none of the retryable keywords.","commonSituations":"Expired or wrong API key (401); malformed prompt exceeding token limits (400); unknown model name; serializing unsupported message content; a custom retryableExceptionPredicate too narrow to match your provider's exception types.","solutions":["Read the cause — this is a deterministic error; fix the request (auth, model name, prompt size) rather than retrying.","If the error really is transient, extend the predicate via retryableExceptionPredicate(...) to match its type/message.","Verify credentials and request payload against the provider's API docs.","Confirm default retryability: ResourceAccessException/WebClientRequestException and IO/Socket/Connect/Timeout/SSL causes are retryable; everything else is not."],"exampleFix":"// before: default predicate misses provider-specific retryables\nModelRetryInterceptor.builder().build();\n// after\nModelRetryInterceptor.builder()\n    .retryableExceptionPredicate(e ->\n        e instanceof io.netty.channel.ConnectTimeoutException\n        || e.getMessage().contains(\"Too Many Requests\"))\n    .build();","handlingStrategy":"try-catch","validationCode":"// Validate request shape before invoking (avoids deterministic 4xx failures)\nObjects.requireNonNull(prompt, \"prompt\");\nif (tokenEstimator.count(prompt) > maxContextTokens) throw new IllegalArgumentException(\"Prompt exceeds model context window\");","typeGuard":null,"tryCatchPattern":"try {\n    return retryInterceptor.interceptModel(request, handler);\n} catch (RuntimeException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"non-retryable exception\")) {\n        Throwable root = e.getCause();\n        // e.g. 401 -> refresh credentials; 400 -> fix request; do not retry\n        throw new PermanentModelCallException(root);\n    }\n    throw e;\n}","preventionTips":["Tune retryableExceptionPredicate to your provider's actual exception taxonomy (Transient vs Permanent).","Validate prompts (token limits, message format) before the call to avoid deterministic 400s.","Rotate/refresh API keys proactively; auth errors are never retried."],"tags":["retry","non-retryable","llm","api-error"],"backgroundTag":"api-request-failed","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"}