alibaba/spring-ai-alibaba · error · RuntimeException

${message.getText()}

Error message

${message.getText()}

What it means

ModelFallbackInterceptor wraps the model call; if the returned message text contains the substring 'Exception:' it treats the response as an error-indicator payload and rethrows it as a RuntimeException with the full message text. This catches models/adapters that surface failures as textual output rather than thrown exceptions.

Source

Thrown at spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/interceptor/modelfallback/ModelFallbackInterceptor.java:70

		this.fallbackModels = new ArrayList<>(builder.fallbackModels);
	}

	public static Builder builder() {
		return new Builder();
	}

	@Override
	public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) {
		Exception lastException = null;

		// Try primary model first
		try {
			ModelResponse modelResponse = handler.call(request);
			Message message = (Message) modelResponse.getMessage();
			
			// Check if response contains error indicator
			if (message.getText() != null && message.getText().contains("Exception:")) {
				throw new RuntimeException(message.getText());
			}
			
			// Return successful response
			return modelResponse;
		}
		catch (Exception e) {
			log.warn("Primary model failed: {}", e.getMessage());
			lastException = e;
		}

		// Try fallback models in sequence
		for (int i = 0; i < fallbackModels.size(); i++) {
			ChatModel fallbackModel = fallbackModels.get(i);
			try {
				log.info("Trying fallback model {} of {}", i + 1, fallbackModels.size());

				// Call the fallback model directly
				Prompt prompt = new Prompt(request.getMessages(), request.getOptions());

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Inspect the full message text in the thrown RuntimeException to identify the underlying cause and fix it (quota, auth, invalid request).
  2. If this is a false positive (legitimate content containing 'Exception:'), rephrase content or adjust the interceptor's detection logic.
  3. Ensure your fallback model/adapter throws proper exceptions instead of returning error text as completions.

Example fix

// before (model returns error text)
// RuntimeException: java.util.concurrent.TimeoutException while calling tool
// after: handle it explicitly
catch (RuntimeException e) {
    logger.warn("Model fallback reported: {}", e.getMessage());
    return fallbackResponse;
}
Defensive patterns

Strategy: try-catch

Validate before calling

Message msg = modelResponse.getMessage();
String text = msg == null ? null : msg.getText();
boolean looksLikeError = text != null && text.contains("Exception:");
if (looksLikeError) { handleFallbackFailure(text); }

Type guard

boolean isCleanResponse(Message m) { return m != null && m.getText() != null && !m.getText().contains("Exception:"); }

Try / catch

try { return interceptor.interceptModel(request, handler); } catch (RuntimeException e) {
    log.error("Model/fallback error text: {}", e.getMessage());
    return errorResponse(request);
}

Prevention

When it happens

Trigger: The underlying model (or a fallback/mock adapter) returns a Message whose text contains 'Exception:', e.g. 'RuntimeException: quota exceeded' rendered as the completion; interceptModel then throws RuntimeException(message.getText()).

Common situations: Mock or test models that return error strings as legitimate text; a model echoing an exception from a tool result that happens to contain 'Exception:'; provider-side failure text passed through as a normal completion.

Related errors


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