spring-projects/spring-ai · warning
No choices returned for prompt
Error message
No choices returned for prompt: ${prompt} What it means
OpenAiChatModel.internalCall sends a chat completion request to the OpenAI API and checks the returned ChatCompletion. When the API responds successfully but the choices list is empty, Spring AI logs a warning and returns an empty ChatResponse instead of throwing, because there are no generations to build. This usually signals the provider accepted the request but produced no candidate completions.
Solutions
- Check the response object / enable DEBUG logging to see the raw API response and why choices were empty
- If using a proxy or compatible endpoint, verify it returns a standard choices array in the OpenAI chat completion schema
- Rephrase the prompt if content filtering may have suppressed the output
- Handle the empty ChatResponse in your code: check ChatResponse.getResults() is non-empty before reading generations
Example fix
// before
ChatResponse response = chatModel.call(new Prompt("..."));
String text = response.getResult().getOutput().getText(); // NPE if empty
// after
ChatResponse response = chatModel.call(new Prompt("..."));
if (response == null || response.getResults().isEmpty()) {
throw new IllegalStateException("Model returned no completions");
}
String text = response.getResult().getOutput().getText(); Defensive patterns
Strategy: validation
Validate before calling
// after the call
ChatResponse resp = chatModel.call(prompt);
if (resp == null || resp.getResults() == null || resp.getResults().isEmpty()) {
throw new IllegalStateException("OpenAI returned no choices for prompt");
} Prevention
- Check ChatResponse.getResults() non-empty before reading getResult()
- Log raw API responses when using OpenAI-compatible proxies
- Rephrase prompts that may be content-filtered
When it happens
Trigger: Calling OpenAiChatModel.call(prompt) where the OpenAI API returns a ChatCompletion whose choices() list is empty — e.g. the request was filtered by content policy, the API returned a degenerate/empty response, or an unexpected response shape from a non-standard endpoint.
Common situations: Pointing spring.ai.openai.base-url at a non-OpenAI OpenAI-compatible proxy that omits the choices array; safety filtering of flagged prompts; API version changes that change response shape.
Understand the failure class
Background: "empty response", "returned no data", "empty embeddings": what HTTP 200-with-empty-body errors mean across libraries — this error's family across 36 libraries.
Related errors
- No speech response returned for prompt: + prompt
- Both maxTokens and maxCompletionTokens are set. OpenAI API…
- Both maxTokens and maxCompletionTokens are set. OpenAI API…
- Chat completion is not supported for this model:
- Conversion from JSON to
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/88c6579e4e9e6acc.
Report an issue: GitHub.
Appendix: source
Thrown at models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java:228
ChatCompletionCreateParams request = this.createRequest(prompt, false);
RequestOptions requestOptions = this.buildRequestOptions(prompt);
ChatModelObservationContext observationContext = ChatModelObservationContext.builder()
.prompt(prompt)
.provider(AiProvider.OPENAI.value())
.build();
ChatResponse response = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION
.observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext,
this.observationRegistry)
.observe(() -> {
ChatCompletion chatCompletion = this.openAiClient.chat().completions().create(request, requestOptions);
List<ChatCompletion.Choice> choices = chatCompletion.choices();
if (choices.isEmpty()) {
if (logger.isWarnEnabled()) {
logger.warn("No choices returned for prompt: " + prompt);
}
return new ChatResponse(List.of());
}
List<Generation> generations = choices.stream().map(choice -> {
Map<String, Object> metadata = Map.of("id", chatCompletion.id(), "role",
choice.message()._role().asString().isPresent() ? choice.message()._role().asStringOrThrow()
: "",
"index", choice.index(), "finishReason", choice.finishReason().value().toString(),
"refusal", choice.message().refusal().orElse(""), "annotations",
choice.message().annotations().orElse((List) List.of(Map.of())), REASONING_CONTENT,
getReasoningContent(choice));
return buildGeneration(choice, metadata, request);
}).toList();
// Current usage
CompletionUsage usage = chatCompletion.usage().orElse(null);
Usage currentChatResponseUsage = usage != null ? getDefaultUsage(usage) : new EmptyUsage();View on GitHub (pinned to 98a7beda4f)