alibaba/spring-ai-alibaba · error · RuntimeException

Model call failed (non-retryable exception)

Error message

Model call failed (non-retryable exception)

What it means

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.

Source

Thrown at spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/interceptor/modelretry/ModelRetryInterceptor.java:140

				// Successful response
				if (attempt > 1) {
					log.info("The model call succeeded after the {}th attempt.", attempt);
				}
				return modelResponse;

			} catch (Exception e) {
				lastException = e;
				log.warn("Model call failed (attempted {}/{}): {}", attempt, maxAttempts, e.getMessage());

				if (attempt >= maxAttempts) {
					log.error("The maximum number of retries has been reached {}, and the model call has failed.", maxAttempts);
					throw new RuntimeException("Model call failed, maximum number of retries reached.", lastException);
				}

				// Determine if an exception can be retried.
				if (!retryableExceptionPredicate.test(e)) {
					log.warn("Exceptions cannot be retried and are thrown immediately: {}", e.getMessage());
					throw new RuntimeException("Model call failed (non-retryable exception)", e);
				}

				// Wait and try again
				if (currentDelay > 0) {
					try {
						log.info("Retry after {} ms", currentDelay);
						Thread.sleep(currentDelay);
					} catch (InterruptedException ie) {
						Thread.currentThread().interrupt();
						throw new RuntimeException("Retry interrupted", ie);
					}
				}

				// Calculate the next delay time (exponential backoff)
				currentDelay = Math.min((long) (currentDelay * backoffMultiplier), maxDelay);
			}
		}

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Read the cause — this is a deterministic error; fix the request (auth, model name, prompt size) rather than retrying.
  2. If the error really is transient, extend the predicate via retryableExceptionPredicate(...) to match its type/message.
  3. Verify credentials and request payload against the provider's API docs.
  4. Confirm default retryability: ResourceAccessException/WebClientRequestException and IO/Socket/Connect/Timeout/SSL causes are retryable; everything else is not.

Example fix

// before: default predicate misses provider-specific retryables
ModelRetryInterceptor.builder().build();
// after
ModelRetryInterceptor.builder()
    .retryableExceptionPredicate(e ->
        e instanceof io.netty.channel.ConnectTimeoutException
        || e.getMessage().contains("Too Many Requests"))
    .build();
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate request shape before invoking (avoids deterministic 4xx failures)
Objects.requireNonNull(prompt, "prompt");
if (tokenEstimator.count(prompt) > maxContextTokens) throw new IllegalArgumentException("Prompt exceeds model context window");

Try / catch

try {
    return retryInterceptor.interceptModel(request, handler);
} catch (RuntimeException e) {
    if (e.getMessage() != null && e.getMessage().contains("non-retryable exception")) {
        Throwable root = e.getCause();
        // e.g. 401 -> refresh credentials; 400 -> fix request; do not retry
        throw new PermanentModelCallException(root);
    }
    throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.

Related errors


AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09). Data as JSON: /api/errors/47768bf3f7456e10. Report an issue: GitHub.