spring-projects/spring-ai · error · java.lang.IllegalStateException
Currently only one tool call is supported per message!
Error message
Currently only one tool call is supported per message!
What it means
DeepSeekStreamFunctionCallingHelper merges successive streaming chunks that carry partial tool-call deltas. Because DeepSeek streams one tool call per chunk, the merge logic assumes at most one tool call per message; receiving a chunk message containing more than one tool call breaks the incremental assembly and triggers this IllegalStateException.
Source
Thrown at models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/api/DeepSeekStreamFunctionCallingHelper.java:103
String name = (current.name() != null ? current.name() : (previous != null ? previous.name() : null));
String toolCallId = (current.toolCallId() != null ? current.toolCallId()
: (previous != null ? previous.toolCallId() : null));
Boolean prefix = (current.prefix() != null ? current.prefix() : (previous != null ? previous.prefix() : null));
String reasoningContent = (current.reasoningContent() != null ? current.reasoningContent()
: (previous != null ? previous.reasoningContent() : null));
List<ToolCall> toolCalls = new ArrayList<>();
ToolCall lastPreviousTooCall = null;
if (previous != null && !CollectionUtils.isEmpty(previous.toolCalls())) {
lastPreviousTooCall = previous.toolCalls().get(previous.toolCalls().size() - 1);
if (previous.toolCalls().size() > 1) {
toolCalls.addAll(previous.toolCalls().subList(0, previous.toolCalls().size() - 1));
}
}
if (!CollectionUtils.isEmpty(current.toolCalls())) {
if (current.toolCalls().size() > 1) {
throw new IllegalStateException("Currently only one tool call is supported per message!");
}
var currentToolCall = current.toolCalls().iterator().next();
if (StringUtils.hasText(currentToolCall.id())) {
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, toolCallId, toolCalls, prefix, reasoningContent);View on GitHub (pinned to 98a7beda4f)
Solutions
- Restrict the request to a single tool call per turn (set tool_choice to a specific function or parallel_tool_calls=false if supported)
- Upgrade spring-ai-deepseek to a version whose stream helper handles multiple tool-call deltas
- Aggregate tool calls manually from the raw chunks instead of relying on the merge helper
Example fix
// before
ChatCompletionRequest req = ChatCompletionRequest.builder().tools(tools).build(); // model may emit multiple calls
// after
ChatCompletionRequest req = ChatCompletionRequest.builder()
.tools(tools)
.toolChoice(ChatCompletionRequest.ToolChoice.of("my_function"))
.build(); Defensive patterns
Strategy: try-catch
Try / catch
try { return streamHelper.merge(previous, current); } catch (IllegalStateException e) { if (e.getMessage().contains("only one tool call")) { /* handle chunk with multiple tool calls separately */ } throw e; } Prevention
- Limit DeepSeek requests to one tool call per turn (specific tool_choice)
- Avoid prompts that encourage parallel tool invocations when streaming
- Keep spring-ai-deepseek updated for streaming format changes
When it happens
Trigger: Processing a DeepSeek chat-completion stream where a ChatCompletionChunk message contains toolCalls().size() > 1 — e.g. the model emits multiple parallel tool calls inside a single streamed message while the helper merges it with previous state.
Common situations: Prompts that request multiple tool/function invocations in one turn; server-side changes in DeepSeek streaming format emitting batched tool calls; non-DeepSeek-compatible proxies returning OpenAI-style multi-tool chunks.
Related errors
- Currently only one tool call is supported per message!
- No tool call requested by the chat model
- Tool call arguments are null or empty for MCP tool: <toolNam
- Tool call arguments are null or empty for MCP tool: <toolNam
- Tool call arguments are null or empty for tool: ${toolName}.
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/bf7a3756d7c9af52.
Report an issue: GitHub.