alibaba/spring-ai-alibaba · warning

The model call returned an exception message: {}

Error message

The model call returned an exception message: {}

What it means

ModelRetryInterceptor.interceptModel inspects the model's returned message text and, if it starts with 'Exception:' (an exception previously captured and serialized by AgentLlmNode instead of thrown), logs this warning and treats it as a failure: if attempts remain and isRetryableExceptionMessage() accepts the text, it records a synthetic RuntimeException and retries after the current backoff delay. It exists because some paths deliver model exceptions as response text rather than thrown exceptions.

Source

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

		for (int attempt = 1; attempt <= maxAttempts; attempt++) {
			try {
				if (attempt > 1) {
					log.info("Retry model call, on the {}th attempt (out of {} attempts).", attempt, maxAttempts);
				}

				ModelResponse modelResponse = handler.call(request);
				Object messagePayload = modelResponse.getMessage();
				if (messagePayload instanceof Flux<?> responseFlux) {
					return ModelResponse.of(withStreamingRetry(request, handler, castChatResponseFlux(responseFlux), attempt, currentDelay));
				}
				if (!(messagePayload instanceof Message message)) {
					return modelResponse;
				}

				// Check if the response contains any exception information (exceptions captured from AgentLlmNode).
				if (message != null && message.getText() != null && message.getText().startsWith("Exception:")) {
					String exceptionText = message.getText();
					log.warn("The model call returned an exception message: {}", exceptionText);

					// Extract anomaly information from the text and determine whether a retry is possible.
					if (attempt < maxAttempts && isRetryableExceptionMessage(exceptionText)) {
						lastException = new RuntimeException(exceptionText);
						// Wait and try again
						if (currentDelay > 0) {
							try {
								log.info("Retry after {} ms", currentDelay);
								Thread.sleep(currentDelay);
							} catch (InterruptedException e) {
								Thread.currentThread().interrupt();
								throw new RuntimeException("Retry interrupted", e);
							}
						}
						// Calculate the next delay time (exponential backoff)
						currentDelay = Math.min((long) (currentDelay * backoffMultiplier), maxDelay);
						continue;
					} else if (attempt >= maxAttempts) {

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Inspect the logged exceptionText to identify the real upstream error and fix its root cause (auth, rate limit, context length)
  2. Customize the retryable predicate so only genuinely transient exception messages trigger retries
  3. Reduce total attempts/backoff if the underlying error is permanent (it will never succeed on retry)
  4. Ensure AgentLlmNode-side errors are surfaced as exceptions where possible so retry logic sees real failure signals

Example fix

// before
ModelRetryInterceptor.of(model); // default predicate retries on everything transient-looking
// after
ModelRetryInterceptor interceptor = ModelRetryInterceptor.builder()
    .chatModel(model)
    .maxAttempts(3)
    .retryablePredicate(msg -> msg.startsWith("Exception: rate limit")
        || msg.startsWith("Exception: timeout"))
    .build();
Defensive patterns

Strategy: retry

Validate before calling

String text = message.getText();
boolean embeddedException = text != null && text.startsWith("Exception:");
boolean retryable = embeddedException && isTransient(text);

Type guard

boolean isEmbeddedException(org.springframework.ai.chat.messages.AssistantMessage m) {
    return m != null && m.getText() != null && m.getText().startsWith("Exception:");
}

Try / catch

try { return interceptor.interceptModel(request, chain); }
catch (RuntimeException e) { throw new ModelInvocationException("Retries exhausted on embedded exception", e); }

Prevention

When it happens

Trigger: A model call 'succeeds' but its output text is an 'Exception: ...' string produced upstream (AgentLlmNode captured the exception), and the retry policy decides whether it is retryable; logs each time such an embedded exception is detected.

Common situations: Provider returning error payloads that get flattened into message text; rate-limit or timeout messages surfaced as text from a wrapped client; streaming responses containing serialized error bodies; prompts exceeding context limits reported as text.

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/b21278ff8dea477c. Report an issue: GitHub.