spring-projects/spring-ai · error · IllegalStateException
No tool call requested by the chat model
Error message
No tool call requested by the chat model
What it means
DefaultToolCallingManager.executeToolCalls() expects the chat response to contain at least one Generation whose AssistantMessage has tool calls. If no generation carries tool calls, it throws IllegalStateException, because there is nothing for the tool execution step to execute.
Source
Thrown at spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java:188
List<ToolCallback> toolCallbacks = new ArrayList<>(
!CollectionUtils.isEmpty(chatOptions.getToolCallbacks()) ? chatOptions.getToolCallbacks() : List.of());
return toolCallbacks.stream().map(ToolCallback::getToolDefinition).toList();
}
@Override
public ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse) {
Assert.notNull(prompt, "prompt cannot be null");
Assert.notNull(chatResponse, "chatResponse cannot be null");
Optional<Generation> toolCallGeneration = chatResponse.getResults()
.stream()
.filter(g -> !CollectionUtils.isEmpty(g.getOutput().getToolCalls()))
.findFirst();
if (toolCallGeneration.isEmpty()) {
throw new IllegalStateException("No tool call requested by the chat model");
}
AssistantMessage assistantMessage = toolCallGeneration.get().getOutput();
ToolContext toolContext = buildToolContext(prompt, assistantMessage);
InternalToolExecutionResult internalToolExecutionResult = executeToolCall(prompt, assistantMessage,
toolContext);
List<Message> conversationHistory = buildConversationHistoryAfterToolExecution(prompt.getInstructions(),
assistantMessage, internalToolExecutionResult.toolResponseMessage());
return ToolExecutionResult.builder()
.conversationHistory(conversationHistory)
.returnDirect(internalToolExecutionResult.returnDirect())
.build();
}
View on GitHub (pinned to 98a7beda4f)
Solutions
- Only call executeToolCalls when the assistant message actually has tool calls; check generation.getOutput().getToolCalls() non-empty first
- If the model answered with plain text, treat the conversation as complete and return the text instead of invoking tool execution
- Inspect the model's finishReason / response content — a refusal or normal answer without tool calls is valid, not a bug; adjust the prompt or tool definitions if tool calls were expected
Example fix
// before
toolExecutionResult = toolCallingManager.executeToolCalls(promptOptions, chatResponse);
// after
boolean hasToolCalls = chatResponse.getResults().stream()
.anyMatch(g -> !CollectionUtils.isEmpty(g.getOutput().getToolCalls()));
if (hasToolCalls) {
toolExecutionResult = toolCallingManager.executeToolCalls(promptOptions, chatResponse);
} else {
return chatResponse; // plain answer, no tools to run
} Defensive patterns
Strategy: type-guard
Validate before calling
boolean modelRequestedToolCall(ChatResponse response) {
return response != null && response.getResults().stream()
.anyMatch(g -> g.getOutput() != null && !CollectionUtils.isEmpty(g.getOutput().getToolCalls()));
} Type guard
Optional<AssistantMessage> firstToolCallMessage(ChatResponse response) {
return response.getResults().stream()
.map(Generation::getOutput)
.filter(m -> !CollectionUtils.isEmpty(m.getToolCalls()))
.findFirst();
} Try / catch
try {
return toolCallingManager.executeToolCalls(promptOptions, chatResponse);
} catch (IllegalStateException e) {
if (e.getMessage().contains("No tool call requested")) {
return chatResponse; // no tools to execute
}
throw e;
} Prevention
- Always check getToolCalls() is non-empty before invoking executeToolCalls
- Handle the case where the model replies with plain text instead of a tool call
- Prefer Spring AI's built-in ToolCallingChatOptions auto tool execution over manual manager calls when possible
When it happens
Trigger: Calling executeToolCalls(prompt, chatResponse) with a chatResponse whose generations all have empty/null getOutput().getToolCalls() — e.g. passing a plain text response, a response from a model that chose not to call a tool, or an already-consumed/filtered response.
Common situations: Wiring ToolCallingManager manually in a loop without re-checking whether the model actually requested a tool; model returns a final text answer instead of a tool call but the pipeline assumes a tool call; streaming flows where an empty/blank tool-call response slips through.
Related errors
- Currently only one tool call is supported per message!
- No ToolCallback found for tool name: ${toolName}
- Sampling not supported by the client:
- Tool execution results are not supported for Bedrock models
- Currently only one tool call is supported per message!
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/9ccb41ae5c9cc3e3.
Report an issue: GitHub.