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

  1. Only call executeToolCalls when the assistant message actually has tool calls; check generation.getOutput().getToolCalls() non-empty first
  2. If the model answered with plain text, treat the conversation as complete and return the text instead of invoking tool execution
  3. 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

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


AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11). Data as JSON: /api/errors/9ccb41ae5c9cc3e3. Report an issue: GitHub.