spring-projects/spring-ai · error · IllegalStateException

Currently only one tool call is supported per message!

Error message

Currently only one tool call is supported per message!

What it means

MistralAiStreamFunctionCallingHelper.merge() combines consecutive streaming chunks of a tool-calling response. It supports at most one tool call per streamed message; if an incoming chunk's toolCalls list contains more than one entry, IllegalStateException is thrown because incremental merging of multiple parallel tool calls is not implemented.

Source

Thrown at models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiStreamFunctionCallingHelper.java:131

	private ChatCompletionMessage merge(ChatCompletionMessage previous, ChatCompletionMessage current) {
		var content = (current.content() != null ? current.content()
				: (previous.content() != null) ? previous.content() : "");
		Role role = (current.role() != null ? current.role() : previous.role());
		role = (role != null ? role : Role.ASSISTANT); // default to ASSISTANT (if null
		String name = (current.name() != null ? current.name() : previous.name());

		List<ToolCall> toolCalls = new ArrayList<>();
		ToolCall lastPreviousTooCall = null;
		if (previous.toolCalls() != null) {
			lastPreviousTooCall = previous.toolCalls().get(previous.toolCalls().size() - 1);
			if (previous.toolCalls().size() > 1) {
				toolCalls.addAll(previous.toolCalls().subList(0, previous.toolCalls().size() - 1));
			}
		}
		if (current.toolCalls() != null) {
			if (current.toolCalls().size() > 1) {
				throw new IllegalStateException("Currently only one tool call is supported per message!");
			}
			var currentToolCall = current.toolCalls().iterator().next();
			if (currentToolCall.id() != null) {
				if (lastPreviousTooCall != null) {
					toolCalls.add(lastPreviousTooCall);
				}
				toolCalls.add(currentToolCall);
			}
			else {
				toolCalls.add(merge(lastPreviousTooCall, currentToolCall));
			}
		}
		else {
			if (lastPreviousTooCall != null) {
				toolCalls.add(lastPreviousTooCall);
			}
		}
		return new ChatCompletionMessage(content, role, name, toolCalls);

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Upgrade spring-ai-mistral-ai to a version supporting multiple parallel tool calls.
  2. Reduce exposed tools or adjust the prompt to discourage parallel tool calls.
  3. Handle tool calls non-streaming (use call() instead of stream()) where multi-tool-call responses are supported.
Defensive patterns

Strategy: try-catch

Validate before calling

long parallel = chunk.getChoices().stream()
    .flatMap(c -> c.getMessage().getToolCalls() == null ? Stream.empty() : c.getMessage().getToolCalls().stream())
    .count();
if (parallel > 1) { /* switch to non-streaming path */ }

Try / catch

try {
    return chatModel.stream(prompt);
} catch (IllegalStateException e) {
    if (e.getMessage().contains("only one tool call")) { return chatModel.call(prompt); } // non-streaming fallback
    throw e;
}

Prevention

When it happens

Trigger: Streaming a Mistral chat completion where the model emits a message with two or more parallel tool calls in one chunk (e.g. the model decides to call two functions simultaneously).

Common situations: Prompts that expose multiple tools to Mistral and the model responds with parallel tool invocation; newer Mistral models with multi tool-call output combined with an older Spring AI stream helper.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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