Please do a quick search on GitHub issues first, there might be already a duplicate issue for the one you are about to create. If the bug is trivial, just go ahead and create the issue. Otherwise, please take a few moments and fill in the following sections:

Bug description When I use DeepSeekChatModel to force a ToolCall call using tool_choice, if the tool definition has returnDirect=false, the completion of the push back will trigger ToolCall again, causing an infinite loop.

Environment Java Version: 17 Spring AI Version: 1.0.3

Steps to reproduce 1. Define a tool function and specify returnDirect=false. 2. Use the DeepSeekChatModel ChatClient to specify ToolChoice as this tool and perform completion.

Expected behavior Call the function only once and output the text normally

My Suggestion

  • Before

https://github.com/spring-projects/spring-ai/blob/3fc1ed6750d9521a50abe56a537319681d785d6b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java#L209-L225

  • After

Reset tool_choice to "auto" when push back to model

        if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response)) {
            var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response);
            if (toolExecutionResult.returnDirect()) {
                // Return tool execution result directly to the client.
                return ChatResponse.builder()
                    .from(response)
                    .generations(ToolExecutionResult.buildGenerations(toolExecutionResult))
                    .build();
            }
            else {
                            if (prompt.getOptions() instanceof DeepSeekChatOptions) {
                                DeepSeekChatOptions options = (DeepSeekChatOptions)prompt.getOptions();
                                options.setToolChoice(ChatCompletionRequest.ToolChoiceBuilder.AUTO);
                            }

                // Send the tool execution result back to the model.
                return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()),
                        response);
            }
        }

        return response;