From 72bceabfee0db2ca11665efd844c71ff5aa553c9 Mon Sep 17 00:00:00 2001 From: lambochen Date: Thu, 29 May 2025 22:49:57 +0800 Subject: [PATCH 01/25] ToolCallingChatOptions support internalToolExecutionMaxIterations Signed-off-by: lambochen --- .../ai/anthropic/AnthropicChatOptions.java | 14 ++++++++ .../azure/openai/AzureOpenAiChatOptions.java | 28 ++++++++++++--- .../ai/deepseek/DeepSeekChatOptions.java | 13 +++++++ .../ai/minimax/MiniMaxChatOptions.java | 17 ++++++++++ .../ai/mistralai/MistralAiChatOptions.java | 24 ++++++++++++- .../ai/ollama/api/OllamaOptions.java | 22 +++++++++++- .../ai/openai/OpenAiChatOptions.java | 26 ++++++++++++-- .../gemini/VertexAiGeminiChatOptions.java | 22 +++++++++++- .../ai/zhipuai/ZhiPuAiChatOptions.java | 34 +++++++++++++++++++ .../tool/DefaultToolCallingChatOptions.java | 11 ++++++ .../ai/model/tool/ToolCallingChatOptions.java | 15 ++++++++ 11 files changed, 217 insertions(+), 9 deletions(-) diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java index dbfbee561c8..1cb3fffab02 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java @@ -44,6 +44,7 @@ * @author Thomas Vitale * @author Alexandros Pappas * @author Ilayaperumal Gopinathan + * @author lambochen * @since 1.0.0 */ @JsonInclude(Include.NON_NULL) @@ -79,6 +80,9 @@ public class AnthropicChatOptions implements ToolCallingChatOptions { @JsonIgnore private Boolean internalToolExecutionEnabled; + @JsonIgnore + private Integer internalToolExecutionMaxIterations; + @JsonIgnore private Map toolContext = new HashMap<>(); @@ -226,6 +230,16 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut this.internalToolExecutionEnabled = internalToolExecutionEnabled; } + @Override + public Integer getInternalToolExecutionMaxIterations() { + return this.internalToolExecutionMaxIterations; + } + + @Override + public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { + this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; + } + @Override @JsonIgnore public Double getFrequencyPenalty() { diff --git a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java index da442b4ad4d..3b56903e96b 100644 --- a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java +++ b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java @@ -48,6 +48,7 @@ * @author Ilayaperumal Gopinathan * @author Alexandros Pappas * @author Andres da Silva Santos + * @author lambochen */ @JsonInclude(Include.NON_NULL) public class AzureOpenAiChatOptions implements ToolCallingChatOptions { @@ -200,6 +201,9 @@ public class AzureOpenAiChatOptions implements ToolCallingChatOptions { @JsonIgnore private Boolean internalToolExecutionEnabled; + @JsonIgnore + private Integer internalToolExecutionMaxIterations; + /** * Whether to include token usage information in streaming chat completion responses. * Only applies to streaming responses. @@ -257,6 +261,16 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut this.internalToolExecutionEnabled = internalToolExecutionEnabled; } + @Override + public Integer getInternalToolExecutionMaxIterations() { + return this.internalToolExecutionMaxIterations; + } + + @Override + public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { + this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; + } + public static Builder builder() { return new Builder(); } @@ -504,6 +518,7 @@ public boolean equals(Object o) { && Objects.equals(this.toolCallbacks, that.toolCallbacks) && Objects.equals(this.toolNames, that.toolNames) && Objects.equals(this.internalToolExecutionEnabled, that.internalToolExecutionEnabled) + && Objects.equals(this.internalToolExecutionMaxIterations, that.internalToolExecutionMaxIterations) && Objects.equals(this.logprobs, that.logprobs) && Objects.equals(this.topLogProbs, that.topLogProbs) && Objects.equals(this.enhancements, that.enhancements) && Objects.equals(this.streamOptions, that.streamOptions) @@ -518,10 +533,10 @@ public boolean equals(Object o) { @Override public int hashCode() { return Objects.hash(this.logitBias, this.user, this.n, this.stop, this.deploymentName, this.responseFormat, - this.toolCallbacks, this.toolNames, this.internalToolExecutionEnabled, this.seed, this.logprobs, - this.topLogProbs, this.enhancements, this.streamOptions, this.reasoningEffort, this.enableStreamUsage, - this.toolContext, this.maxTokens, this.frequencyPenalty, this.presencePenalty, this.temperature, - this.topP); + this.toolCallbacks, this.toolNames, this.internalToolExecutionEnabled, + this.internalToolExecutionMaxIterations, this.seed, this.logprobs, this.topLogProbs, this.enhancements, + this.streamOptions, this.reasoningEffort, this.enableStreamUsage, this.toolContext, this.maxTokens, + this.frequencyPenalty, this.presencePenalty, this.temperature, this.topP); } public static class Builder { @@ -664,6 +679,11 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } + public Builder InternalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.options.setInternalToolExecutionMaxIterations(internalToolExecutionMaxIterations); + return this; + } + public AzureOpenAiChatOptions build() { return this.options; } diff --git a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java index 0731a1eb6cc..fbe97539dc9 100644 --- a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java +++ b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java @@ -43,6 +43,7 @@ * chat completion * * @author Geng Rong + * @author lambochen */ @JsonInclude(Include.NON_NULL) public class DeepSeekChatOptions implements ToolCallingChatOptions { @@ -289,6 +290,18 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut this.internalToolExecutionEnabled = internalToolExecutionEnabled; } + @Override + @JsonIgnore + public Integer getInternalToolExecutionMaxIterations() { + return 0; + } + + @Override + @JsonIgnore + public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { + + } + public Boolean getLogprobs() { return this.logprobs; } diff --git a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java index 9d2614396c5..4445401c183 100644 --- a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java +++ b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java @@ -45,6 +45,7 @@ * @author Geng Rong * @author Thomas Vitale * @author Ilayaperumal Gopinathan + * @author lambochen * @since 1.0.0 M1 */ @JsonInclude(Include.NON_NULL) @@ -153,6 +154,12 @@ public class MiniMaxChatOptions implements ToolCallingChatOptions { @JsonIgnore private Boolean internalToolExecutionEnabled; + /** + * The maximum number of iterations for tool execution. + */ + @JsonIgnore + private Integer internalToolExecutionMaxIterations; + // @formatter:on public static Builder builder() { @@ -349,6 +356,16 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut this.internalToolExecutionEnabled = internalToolExecutionEnabled; } + @Override + public Integer getInternalToolExecutionMaxIterations() { + return 0; + } + + @Override + public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { + + } + @Override public Map getToolContext() { return this.toolContext; diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java index 2b392d5176a..6d1bf000f82 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java @@ -45,6 +45,7 @@ * @author Christian Tzolov * @author Thomas Vitale * @author Alexandros Pappas + * @author lambochen * @since 0.8.1 */ @JsonInclude(JsonInclude.Include.NON_NULL) @@ -156,6 +157,9 @@ public class MistralAiChatOptions implements ToolCallingChatOptions { @JsonIgnore private Boolean internalToolExecutionEnabled; + @JsonIgnore + private Integer internalToolExecutionMaxIterations; + @JsonIgnore private Map toolContext = new HashMap<>(); @@ -180,6 +184,7 @@ public static MistralAiChatOptions fromOptions(MistralAiChatOptions fromOptions) .toolCallbacks(fromOptions.getToolCallbacks()) .toolNames(fromOptions.getToolNames()) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) + .internalToolExecutionMaxIterations(fromOptions.getInternalToolExecutionMaxIterations()) .toolContext(fromOptions.getToolContext()) .build(); } @@ -347,6 +352,16 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut this.internalToolExecutionEnabled = internalToolExecutionEnabled; } + @Override + public Integer getInternalToolExecutionMaxIterations() { + return this.internalToolExecutionMaxIterations; + } + + @Override + public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { + this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; + } + @Override @JsonIgnore public Integer getTopK() { @@ -374,7 +389,8 @@ public MistralAiChatOptions copy() { public int hashCode() { return Objects.hash(this.model, this.temperature, this.topP, this.maxTokens, this.safePrompt, this.randomSeed, this.responseFormat, this.stop, this.frequencyPenalty, this.presencePenalty, this.n, this.tools, - this.toolChoice, this.toolCallbacks, this.tools, this.internalToolExecutionEnabled, this.toolContext); + this.toolChoice, this.toolCallbacks, this.tools, this.internalToolExecutionEnabled, + this.internalToolExecutionMaxIterations, this.toolContext); } @Override @@ -400,6 +416,7 @@ public boolean equals(Object obj) { && Objects.equals(this.toolCallbacks, other.toolCallbacks) && Objects.equals(this.toolNames, other.toolNames) && Objects.equals(this.internalToolExecutionEnabled, other.internalToolExecutionEnabled) + && Objects.equals(this.internalToolExecutionMaxIterations, other.internalToolExecutionMaxIterations) && Objects.equals(this.toolContext, other.toolContext); } @@ -505,6 +522,11 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } + public Builder internalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.options.setInternalToolExecutionMaxIterations(internalToolExecutionMaxIterations); + return this; + } + public Builder toolContext(Map toolContext) { if (this.options.toolContext == null) { this.options.toolContext = toolContext; diff --git a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java index a71be1ce2b2..c67c5be2502 100644 --- a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java +++ b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java @@ -44,6 +44,7 @@ * @author Christian Tzolov * @author Thomas Vitale * @author Ilayaperumal Gopinathan + * @author lambochen * @since 0.8.0 * @see Ollama @@ -320,6 +321,8 @@ public class OllamaOptions implements ToolCallingChatOptions, EmbeddingOptions { @JsonIgnore private Boolean internalToolExecutionEnabled; + @JsonIgnore + private Integer internalToolExecutionMaxIterations; /** * Tool Function Callbacks to register with the ChatModel. @@ -397,6 +400,7 @@ public static OllamaOptions fromOptions(OllamaOptions fromOptions) { .stop(fromOptions.getStop()) .toolNames(fromOptions.getToolNames()) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) + .internalToolExecutionMaxIterations(fromOptions.getInternalToolExecutionMaxIterations()) .toolCallbacks(fromOptions.getToolCallbacks()) .toolContext(fromOptions.getToolContext()).build(); } @@ -746,6 +750,16 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut this.internalToolExecutionEnabled = internalToolExecutionEnabled; } + @Override + public Integer getInternalToolExecutionMaxIterations() { + return this.internalToolExecutionMaxIterations; + } + + @Override + public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { + this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; + } + @Override @JsonIgnore public Integer getDimensions() { @@ -809,6 +823,7 @@ public boolean equals(Object o) { && Objects.equals(this.penalizeNewline, that.penalizeNewline) && Objects.equals(this.stop, that.stop) && Objects.equals(this.toolCallbacks, that.toolCallbacks) && Objects.equals(this.internalToolExecutionEnabled, that.internalToolExecutionEnabled) + && Objects.equals(this.internalToolExecutionMaxIterations, that.internalToolExecutionMaxIterations) && Objects.equals(this.toolNames, that.toolNames) && Objects.equals(this.toolContext, that.toolContext); } @@ -820,7 +835,7 @@ public int hashCode() { this.topP, this.minP, this.tfsZ, this.typicalP, this.repeatLastN, this.temperature, this.repeatPenalty, this.presencePenalty, this.frequencyPenalty, this.mirostat, this.mirostatTau, this.mirostatEta, this.penalizeNewline, this.stop, this.toolCallbacks, this.toolNames, this.internalToolExecutionEnabled, - this.toolContext); + this.internalToolExecutionMaxIterations, this.toolContext); } public static class Builder { @@ -1029,6 +1044,11 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } + public Builder internalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.options.setInternalToolExecutionMaxIterations(internalToolExecutionMaxIterations); + return this; + } + public Builder toolContext(Map toolContext) { if (this.options.toolContext == null) { this.options.toolContext = toolContext; diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java index a1a9fede77e..97b95dcdd5a 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java @@ -49,6 +49,7 @@ * @author Mariusz Bernacki * @author Thomas Vitale * @author Ilayaperumal Gopinathan + * @author lambochen * @since 0.8.0 */ @JsonInclude(Include.NON_NULL) @@ -218,6 +219,9 @@ public class OpenAiChatOptions implements ToolCallingChatOptions { @JsonIgnore private Boolean internalToolExecutionEnabled; + @JsonIgnore + private Integer internalToolExecutionMaxIterations; + /** * Optional HTTP headers to be added to the chat completion request. */ @@ -262,6 +266,7 @@ public static OpenAiChatOptions fromOptions(OpenAiChatOptions fromOptions) { .toolNames(fromOptions.getToolNames() != null ? new HashSet<>(fromOptions.getToolNames()) : null) .httpHeaders(fromOptions.getHttpHeaders() != null ? new HashMap<>(fromOptions.getHttpHeaders()) : null) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) + .internalToolExecutionMaxIterations(fromOptions.getInternalToolExecutionMaxIterations()) .toolContext(fromOptions.getToolContext() != null ? new HashMap<>(fromOptions.getToolContext()) : null) .store(fromOptions.getStore()) .metadata(fromOptions.getMetadata()) @@ -504,6 +509,16 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut this.internalToolExecutionEnabled = internalToolExecutionEnabled; } + @Override + public Integer getInternalToolExecutionMaxIterations() { + return this.internalToolExecutionMaxIterations; + } + + @Override + public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { + this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; + } + public Map getHttpHeaders() { return this.httpHeaders; } @@ -573,8 +588,9 @@ public int hashCode() { this.maxTokens, this.maxCompletionTokens, this.n, this.presencePenalty, this.responseFormat, this.streamOptions, this.seed, this.stop, this.temperature, this.topP, this.tools, this.toolChoice, this.user, this.parallelToolCalls, this.toolCallbacks, this.toolNames, this.httpHeaders, - this.internalToolExecutionEnabled, this.toolContext, this.outputModalities, this.outputAudio, - this.store, this.metadata, this.reasoningEffort, this.webSearchOptions); + this.internalToolExecutionEnabled, this.internalToolExecutionMaxIterations, this.toolContext, + this.outputModalities, this.outputAudio, this.store, this.metadata, this.reasoningEffort, + this.webSearchOptions); } @Override @@ -603,6 +619,7 @@ public boolean equals(Object o) { && Objects.equals(this.httpHeaders, other.httpHeaders) && Objects.equals(this.toolContext, other.toolContext) && Objects.equals(this.internalToolExecutionEnabled, other.internalToolExecutionEnabled) + && Objects.equals(this.internalToolExecutionMaxIterations, other.internalToolExecutionMaxIterations) && Objects.equals(this.outputModalities, other.outputModalities) && Objects.equals(this.outputAudio, other.outputAudio) && Objects.equals(this.store, other.store) && Objects.equals(this.metadata, other.metadata) @@ -765,6 +782,11 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } + public Builder internalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.options.setInternalToolExecutionMaxIterations(internalToolExecutionMaxIterations); + return this; + } + public Builder httpHeaders(Map httpHeaders) { this.options.httpHeaders = httpHeaders; return this; diff --git a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java index 68ae24a92e2..1ca15b2d614 100644 --- a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java +++ b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java @@ -45,6 +45,7 @@ * @author Grogdunn * @author Ilayaperumal Gopinathan * @author Soby Chacko + * @author lambochen * @since 1.0.0 */ @JsonInclude(Include.NON_NULL) @@ -126,6 +127,9 @@ public class VertexAiGeminiChatOptions implements ToolCallingChatOptions { @JsonIgnore private Boolean internalToolExecutionEnabled; + @JsonIgnore + private Integer internalToolExecutionMaxIterations; + @JsonIgnore private Map toolContext = new HashMap<>(); @@ -281,6 +285,16 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut this.internalToolExecutionEnabled = internalToolExecutionEnabled; } + @Override + public Integer getInternalToolExecutionMaxIterations() { + return this.internalToolExecutionMaxIterations; + } + + @Override + public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { + this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; + } + @Override public Double getFrequencyPenalty() { return this.frequencyPenalty; @@ -346,6 +360,7 @@ public boolean equals(Object o) { && Objects.equals(this.toolNames, that.toolNames) && Objects.equals(this.safetySettings, that.safetySettings) && Objects.equals(this.internalToolExecutionEnabled, that.internalToolExecutionEnabled) + && Objects.equals(this.internalToolExecutionMaxIterations, that.internalToolExecutionMaxIterations) && Objects.equals(this.toolContext, that.toolContext); } @@ -354,7 +369,7 @@ public int hashCode() { return Objects.hash(this.stopSequences, this.temperature, this.topP, this.topK, this.candidateCount, this.frequencyPenalty, this.presencePenalty, this.maxOutputTokens, this.model, this.responseMimeType, this.toolCallbacks, this.toolNames, this.googleSearchRetrieval, this.safetySettings, - this.internalToolExecutionEnabled, this.toolContext); + this.internalToolExecutionEnabled, this.internalToolExecutionMaxIterations, this.toolContext); } @Override @@ -478,6 +493,11 @@ public Builder internalToolExecutionEnabled(boolean internalToolExecutionEnabled return this; } + public Builder internalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { + this.options.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; + return this; + } + public Builder toolContext(Map toolContext) { if (this.options.toolContext == null) { this.options.toolContext = toolContext; diff --git a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java index 8b8d3974413..1696e03dd23 100644 --- a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java +++ b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java @@ -42,6 +42,7 @@ * @author Geng Rong * @author Thomas Vitale * @author Ilayaperumal Gopinathan + * @author lambochen * @since 1.0.0 M1 */ @JsonInclude(Include.NON_NULL) @@ -125,6 +126,9 @@ public class ZhiPuAiChatOptions implements ToolCallingChatOptions { @JsonIgnore private Boolean internalToolExecutionEnabled; + @JsonIgnore + private Integer internalToolExecutionMaxIterations; + @JsonIgnore private Map toolContext = new HashMap<>(); // @formatter:on @@ -307,6 +311,16 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut this.internalToolExecutionEnabled = internalToolExecutionEnabled; } + @Override + public Integer getInternalToolExecutionMaxIterations() { + return this.internalToolExecutionMaxIterations; + } + + @Override + public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { + this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; + } + @Override public Map getToolContext() { return this.toolContext; @@ -331,6 +345,8 @@ public int hashCode() { result = prime * result + ((this.user == null) ? 0 : this.user.hashCode()); result = prime * result + ((this.internalToolExecutionEnabled == null) ? 0 : this.internalToolExecutionEnabled.hashCode()); + result = prime * result + ((this.internalToolExecutionMaxIterations == null) ? 0 + : this.internalToolExecutionMaxIterations.hashCode()); result = prime * result + ((this.toolCallbacks == null) ? 0 : this.toolCallbacks.hashCode()); result = prime * result + ((this.toolNames == null) ? 0 : this.toolNames.hashCode()); result = prime * result + ((this.toolContext == null) ? 0 : this.toolContext.hashCode()); @@ -437,6 +453,14 @@ else if (!this.doSample.equals(other.doSample)) { else if (!this.internalToolExecutionEnabled.equals(other.internalToolExecutionEnabled)) { return false; } + if (this.internalToolExecutionMaxIterations == null) { + if (other.internalToolExecutionMaxIterations != null) { + return false; + } + } + else if (!this.internalToolExecutionMaxIterations.equals(other.internalToolExecutionMaxIterations)) { + return false; + } if (this.toolContext == null) { if (other.toolContext != null) { return false; @@ -468,6 +492,10 @@ public ToolCallingChatOptions merge(ChatOptions options) { builder.internalToolExecutionEnabled(toolCallingChatOptions.getInternalToolExecutionEnabled() != null ? (toolCallingChatOptions).getInternalToolExecutionEnabled() : this.getInternalToolExecutionEnabled()); + builder.internalToolExecutionMaxIterations( + toolCallingChatOptions.getInternalToolExecutionMaxIterations() != null + ? toolCallingChatOptions.getInternalToolExecutionMaxIterations() + : this.getInternalToolExecutionMaxIterations()); Set toolNames = new HashSet<>(); if (this.toolNames != null) { @@ -498,6 +526,7 @@ public ToolCallingChatOptions merge(ChatOptions options) { } else { builder.internalToolExecutionEnabled(this.internalToolExecutionEnabled); + builder.internalToolExecutionMaxIterations(this.internalToolExecutionMaxIterations); builder.toolNames(this.toolNames != null ? new HashSet<>(this.toolNames) : null); builder.toolCallbacks(this.toolCallbacks != null ? new ArrayList<>(this.toolCallbacks) : null); builder.toolContext(this.toolContext != null ? new HashMap<>(this.toolContext) : null); @@ -603,6 +632,11 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } + public Builder internalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.options.setInternalToolExecutionMaxIterations(internalToolExecutionMaxIterations); + return this; + } + public Builder toolContext(Map toolContext) { if (this.options.toolContext == null) { this.options.toolContext = toolContext; diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java index 870db6931b9..1c3b6772c4f 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java @@ -33,6 +33,7 @@ * Default implementation of {@link ToolCallingChatOptions}. * * @author Thomas Vitale + * @author lambochen * @since 1.0.0 */ public class DefaultToolCallingChatOptions implements ToolCallingChatOptions { @@ -118,6 +119,16 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut this.internalToolExecutionEnabled = internalToolExecutionEnabled; } + @Override + public Integer getInternalToolExecutionMaxIterations() { + return 0; + } + + @Override + public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { + + } + @Override @Nullable public String getModel() { diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java index f06e71aa869..4281304d55f 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java @@ -37,6 +37,7 @@ * * @author Thomas Vitale * @author Ilayaperumal Gopinathan + * @author lambochen * @since 1.0.0 */ public interface ToolCallingChatOptions extends ChatOptions { @@ -76,6 +77,20 @@ public interface ToolCallingChatOptions extends ChatOptions { */ void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecutionEnabled); + /** + * Get the maximum number of iterations for tool execution. 0 or null means no limit. + * @return the maximum number of iterations. + * @see #getInternalToolExecutionEnabled() + */ + @Nullable + Integer getInternalToolExecutionMaxIterations(); + + /** + * Set the maximum number of iterations for tool execution. 0 or null means no limit. + * @param internalToolExecutionMaxIterations the maximum number of iterations. + */ + void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations); + /** * Get the configured tool context. * @return the tool context map. From 33847aad5776c4a0932c947e5474cac642378de8 Mon Sep 17 00:00:00 2001 From: lambochen Date: Thu, 29 May 2025 23:46:28 +0800 Subject: [PATCH 02/25] rename internalToolExecutionMaxAttempts Signed-off-by: lambochen --- .../ai/anthropic/AnthropicChatOptions.java | 10 +++--- .../azure/openai/AzureOpenAiChatOptions.java | 16 ++++----- .../ai/deepseek/DeepSeekChatOptions.java | 21 +++++++++--- .../ai/minimax/MiniMaxChatOptions.java | 30 +++++++++++----- .../ai/mistralai/MistralAiChatOptions.java | 20 +++++------ .../ai/ollama/api/OllamaOptions.java | 21 ++++++------ .../ai/openai/OpenAiChatOptions.java | 20 +++++------ .../gemini/VertexAiGeminiChatOptions.java | 18 +++++----- .../ai/zhipuai/ZhiPuAiChatOptions.java | 34 +++++++++---------- .../tool/DefaultToolCallingChatOptions.java | 18 +++++++--- .../ai/model/tool/ToolCallingChatOptions.java | 25 ++++++++++---- .../tool/ToolExecutionEligibilityChecker.java | 22 ++++++++++++ 12 files changed, 163 insertions(+), 92 deletions(-) diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java index 1cb3fffab02..13def6d537b 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java @@ -81,7 +81,7 @@ public class AnthropicChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxIterations; + private Integer internalToolExecutionMaxAttempts; @JsonIgnore private Map toolContext = new HashMap<>(); @@ -231,13 +231,13 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut } @Override - public Integer getInternalToolExecutionMaxIterations() { - return this.internalToolExecutionMaxIterations; + public Integer getInternalToolExecutionMaxAttempts() { + return this.internalToolExecutionMaxAttempts; } @Override - public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { - this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; + public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; } @Override diff --git a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java index 3b56903e96b..8ce46c64fb0 100644 --- a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java +++ b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java @@ -202,7 +202,7 @@ public class AzureOpenAiChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxIterations; + private Integer internalToolExecutionMaxAttempts; /** * Whether to include token usage information in streaming chat completion responses. @@ -262,13 +262,13 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut } @Override - public Integer getInternalToolExecutionMaxIterations() { - return this.internalToolExecutionMaxIterations; + public Integer getInternalToolExecutionMaxAttempts() { + return this.internalToolExecutionMaxAttempts; } @Override - public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { - this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; + public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; } public static Builder builder() { @@ -518,7 +518,7 @@ public boolean equals(Object o) { && Objects.equals(this.toolCallbacks, that.toolCallbacks) && Objects.equals(this.toolNames, that.toolNames) && Objects.equals(this.internalToolExecutionEnabled, that.internalToolExecutionEnabled) - && Objects.equals(this.internalToolExecutionMaxIterations, that.internalToolExecutionMaxIterations) + && Objects.equals(this.internalToolExecutionMaxAttempts, that.internalToolExecutionMaxAttempts) && Objects.equals(this.logprobs, that.logprobs) && Objects.equals(this.topLogProbs, that.topLogProbs) && Objects.equals(this.enhancements, that.enhancements) && Objects.equals(this.streamOptions, that.streamOptions) @@ -534,7 +534,7 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(this.logitBias, this.user, this.n, this.stop, this.deploymentName, this.responseFormat, this.toolCallbacks, this.toolNames, this.internalToolExecutionEnabled, - this.internalToolExecutionMaxIterations, this.seed, this.logprobs, this.topLogProbs, this.enhancements, + this.internalToolExecutionMaxAttempts, this.seed, this.logprobs, this.topLogProbs, this.enhancements, this.streamOptions, this.reasoningEffort, this.enableStreamUsage, this.toolContext, this.maxTokens, this.frequencyPenalty, this.presencePenalty, this.temperature, this.topP); } @@ -680,7 +680,7 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut } public Builder InternalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { - this.options.setInternalToolExecutionMaxIterations(internalToolExecutionMaxIterations); + this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxIterations); return this; } diff --git a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java index fbe97539dc9..576b7cedb79 100644 --- a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java +++ b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java @@ -123,6 +123,9 @@ public class DeepSeekChatOptions implements ToolCallingChatOptions { @JsonIgnore private Boolean internalToolExecutionEnabled; + @JsonIgnore + private Integer internalToolExecutionMaxAttempts; + /** * Tool Function Callbacks to register with the ChatModel. * For Prompt Options the toolCallbacks are automatically enabled for the duration of the prompt execution. @@ -292,14 +295,14 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut @Override @JsonIgnore - public Integer getInternalToolExecutionMaxIterations() { - return 0; + public Integer getInternalToolExecutionMaxAttempts() { + return this.internalToolExecutionMaxAttempts; } @Override @JsonIgnore - public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { - + public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; } public Boolean getLogprobs() { @@ -370,7 +373,9 @@ public boolean equals(Object o) { && Objects.equals(this.toolCallbacks, other.toolCallbacks) && Objects.equals(this.toolNames, other.toolNames) && Objects.equals(this.toolContext, other.toolContext) - && Objects.equals(this.internalToolExecutionEnabled, other.internalToolExecutionEnabled); + && Objects.equals(this.internalToolExecutionEnabled, other.internalToolExecutionEnabled) + && Objects.equals(this.internalToolExecutionMaxAttempts, other.internalToolExecutionMaxAttempts) + ; } public static DeepSeekChatOptions fromOptions(DeepSeekChatOptions fromOptions) { @@ -391,6 +396,7 @@ public static DeepSeekChatOptions fromOptions(DeepSeekChatOptions fromOptions) { fromOptions.getToolCallbacks() != null ? new ArrayList<>(fromOptions.getToolCallbacks()) : null) .toolNames(fromOptions.getToolNames() != null ? new HashSet<>(fromOptions.getToolNames()) : null) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) + .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) .toolContext(fromOptions.getToolContext() != null ? new HashMap<>(fromOptions.getToolContext()) : null) .build(); } @@ -500,6 +506,11 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } + public Builder internalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); + return this; + } + public Builder toolContext(Map toolContext) { if (this.options.toolContext == null) { this.options.toolContext = toolContext; diff --git a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java index 4445401c183..afd98b58ab1 100644 --- a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java +++ b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java @@ -154,11 +154,8 @@ public class MiniMaxChatOptions implements ToolCallingChatOptions { @JsonIgnore private Boolean internalToolExecutionEnabled; - /** - * The maximum number of iterations for tool execution. - */ @JsonIgnore - private Integer internalToolExecutionMaxIterations; + private Integer internalToolExecutionMaxAttempts; // @formatter:on @@ -183,6 +180,7 @@ public static MiniMaxChatOptions fromOptions(MiniMaxChatOptions fromOptions) { .toolCallbacks(fromOptions.getToolCallbacks()) .toolNames(fromOptions.getToolNames()) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) + .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) .toolContext(fromOptions.getToolContext()) .build(); } @@ -357,13 +355,13 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut } @Override - public Integer getInternalToolExecutionMaxIterations() { - return 0; + public Integer getInternalToolExecutionMaxAttempts() { + return this.internalToolExecutionMaxAttempts; } @Override - public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { - + public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; } @Override @@ -397,6 +395,8 @@ public int hashCode() { result = prime * result + ((this.toolNames == null) ? 0 : this.toolNames.hashCode()); result = prime * result + ((this.internalToolExecutionEnabled == null) ? 0 : this.internalToolExecutionEnabled.hashCode()); + result = prime * result + + ((this.internalToolExecutionMaxAttempts == null) ? 0 : this.internalToolExecutionMaxAttempts.hashCode()); result = prime * result + ((this.toolContext == null) ? 0 : this.toolContext.hashCode()); return result; } @@ -526,6 +526,15 @@ else if (!this.internalToolExecutionEnabled.equals(other.internalToolExecutionEn return false; } + if (this.internalToolExecutionMaxAttempts == null) { + if (other.internalToolExecutionMaxAttempts != null) { + return false; + } + } + else if (!this.internalToolExecutionMaxAttempts.equals(other.internalToolExecutionMaxAttempts)) { + return false; + } + if (this.toolNames == null) { if (other.toolNames != null) { return false; @@ -666,6 +675,11 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } + public Builder internalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); + return this; + } + public Builder toolContext(Map toolContext) { if (this.options.toolContext == null) { this.options.toolContext = toolContext; diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java index 6d1bf000f82..e87b29b3b2c 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java @@ -158,7 +158,7 @@ public class MistralAiChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxIterations; + private Integer internalToolExecutionMaxAttempts; @JsonIgnore private Map toolContext = new HashMap<>(); @@ -184,7 +184,7 @@ public static MistralAiChatOptions fromOptions(MistralAiChatOptions fromOptions) .toolCallbacks(fromOptions.getToolCallbacks()) .toolNames(fromOptions.getToolNames()) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) - .internalToolExecutionMaxIterations(fromOptions.getInternalToolExecutionMaxIterations()) + .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) .toolContext(fromOptions.getToolContext()) .build(); } @@ -353,13 +353,13 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut } @Override - public Integer getInternalToolExecutionMaxIterations() { - return this.internalToolExecutionMaxIterations; + public Integer getInternalToolExecutionMaxAttempts() { + return this.internalToolExecutionMaxAttempts; } @Override - public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { - this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; + public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; } @Override @@ -390,7 +390,7 @@ public int hashCode() { return Objects.hash(this.model, this.temperature, this.topP, this.maxTokens, this.safePrompt, this.randomSeed, this.responseFormat, this.stop, this.frequencyPenalty, this.presencePenalty, this.n, this.tools, this.toolChoice, this.toolCallbacks, this.tools, this.internalToolExecutionEnabled, - this.internalToolExecutionMaxIterations, this.toolContext); + this.internalToolExecutionMaxAttempts, this.toolContext); } @Override @@ -416,7 +416,7 @@ public boolean equals(Object obj) { && Objects.equals(this.toolCallbacks, other.toolCallbacks) && Objects.equals(this.toolNames, other.toolNames) && Objects.equals(this.internalToolExecutionEnabled, other.internalToolExecutionEnabled) - && Objects.equals(this.internalToolExecutionMaxIterations, other.internalToolExecutionMaxIterations) + && Objects.equals(this.internalToolExecutionMaxAttempts, other.internalToolExecutionMaxAttempts) && Objects.equals(this.toolContext, other.toolContext); } @@ -522,8 +522,8 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } - public Builder internalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { - this.options.setInternalToolExecutionMaxIterations(internalToolExecutionMaxIterations); + public Builder internalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { + this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); return this; } diff --git a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java index c67c5be2502..b6057f08bc6 100644 --- a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java +++ b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java @@ -321,8 +321,9 @@ public class OllamaOptions implements ToolCallingChatOptions, EmbeddingOptions { @JsonIgnore private Boolean internalToolExecutionEnabled; + @JsonIgnore - private Integer internalToolExecutionMaxIterations; + private Integer internalToolExecutionMaxAttempts; /** * Tool Function Callbacks to register with the ChatModel. @@ -400,7 +401,7 @@ public static OllamaOptions fromOptions(OllamaOptions fromOptions) { .stop(fromOptions.getStop()) .toolNames(fromOptions.getToolNames()) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) - .internalToolExecutionMaxIterations(fromOptions.getInternalToolExecutionMaxIterations()) + .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) .toolCallbacks(fromOptions.getToolCallbacks()) .toolContext(fromOptions.getToolContext()).build(); } @@ -751,13 +752,13 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut } @Override - public Integer getInternalToolExecutionMaxIterations() { - return this.internalToolExecutionMaxIterations; + public Integer getInternalToolExecutionMaxAttempts() { + return this.internalToolExecutionMaxAttempts; } @Override - public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { - this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; + public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; } @Override @@ -823,7 +824,7 @@ public boolean equals(Object o) { && Objects.equals(this.penalizeNewline, that.penalizeNewline) && Objects.equals(this.stop, that.stop) && Objects.equals(this.toolCallbacks, that.toolCallbacks) && Objects.equals(this.internalToolExecutionEnabled, that.internalToolExecutionEnabled) - && Objects.equals(this.internalToolExecutionMaxIterations, that.internalToolExecutionMaxIterations) + && Objects.equals(this.internalToolExecutionMaxAttempts, that.internalToolExecutionMaxAttempts) && Objects.equals(this.toolNames, that.toolNames) && Objects.equals(this.toolContext, that.toolContext); } @@ -835,7 +836,7 @@ public int hashCode() { this.topP, this.minP, this.tfsZ, this.typicalP, this.repeatLastN, this.temperature, this.repeatPenalty, this.presencePenalty, this.frequencyPenalty, this.mirostat, this.mirostatTau, this.mirostatEta, this.penalizeNewline, this.stop, this.toolCallbacks, this.toolNames, this.internalToolExecutionEnabled, - this.internalToolExecutionMaxIterations, this.toolContext); + this.internalToolExecutionMaxAttempts, this.toolContext); } public static class Builder { @@ -1044,8 +1045,8 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } - public Builder internalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { - this.options.setInternalToolExecutionMaxIterations(internalToolExecutionMaxIterations); + public Builder internalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { + this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); return this; } diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java index 97b95dcdd5a..0b1c2485e93 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java @@ -220,7 +220,7 @@ public class OpenAiChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxIterations; + private Integer internalToolExecutionMaxAttempts; /** * Optional HTTP headers to be added to the chat completion request. @@ -266,7 +266,7 @@ public static OpenAiChatOptions fromOptions(OpenAiChatOptions fromOptions) { .toolNames(fromOptions.getToolNames() != null ? new HashSet<>(fromOptions.getToolNames()) : null) .httpHeaders(fromOptions.getHttpHeaders() != null ? new HashMap<>(fromOptions.getHttpHeaders()) : null) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) - .internalToolExecutionMaxIterations(fromOptions.getInternalToolExecutionMaxIterations()) + .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) .toolContext(fromOptions.getToolContext() != null ? new HashMap<>(fromOptions.getToolContext()) : null) .store(fromOptions.getStore()) .metadata(fromOptions.getMetadata()) @@ -510,13 +510,13 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut } @Override - public Integer getInternalToolExecutionMaxIterations() { - return this.internalToolExecutionMaxIterations; + public Integer getInternalToolExecutionMaxAttempts() { + return this.internalToolExecutionMaxAttempts; } @Override - public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { - this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; + public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; } public Map getHttpHeaders() { @@ -588,7 +588,7 @@ public int hashCode() { this.maxTokens, this.maxCompletionTokens, this.n, this.presencePenalty, this.responseFormat, this.streamOptions, this.seed, this.stop, this.temperature, this.topP, this.tools, this.toolChoice, this.user, this.parallelToolCalls, this.toolCallbacks, this.toolNames, this.httpHeaders, - this.internalToolExecutionEnabled, this.internalToolExecutionMaxIterations, this.toolContext, + this.internalToolExecutionEnabled, this.internalToolExecutionMaxAttempts, this.toolContext, this.outputModalities, this.outputAudio, this.store, this.metadata, this.reasoningEffort, this.webSearchOptions); } @@ -619,7 +619,7 @@ public boolean equals(Object o) { && Objects.equals(this.httpHeaders, other.httpHeaders) && Objects.equals(this.toolContext, other.toolContext) && Objects.equals(this.internalToolExecutionEnabled, other.internalToolExecutionEnabled) - && Objects.equals(this.internalToolExecutionMaxIterations, other.internalToolExecutionMaxIterations) + && Objects.equals(this.internalToolExecutionMaxAttempts, other.internalToolExecutionMaxAttempts) && Objects.equals(this.outputModalities, other.outputModalities) && Objects.equals(this.outputAudio, other.outputAudio) && Objects.equals(this.store, other.store) && Objects.equals(this.metadata, other.metadata) @@ -782,8 +782,8 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } - public Builder internalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { - this.options.setInternalToolExecutionMaxIterations(internalToolExecutionMaxIterations); + public Builder internalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { + this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); return this; } diff --git a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java index 1ca15b2d614..4250c00fa13 100644 --- a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java +++ b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java @@ -128,7 +128,7 @@ public class VertexAiGeminiChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxIterations; + private Integer internalToolExecutionMaxAttempts; @JsonIgnore private Map toolContext = new HashMap<>(); @@ -286,13 +286,13 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut } @Override - public Integer getInternalToolExecutionMaxIterations() { - return this.internalToolExecutionMaxIterations; + public Integer getInternalToolExecutionMaxAttempts() { + return this.internalToolExecutionMaxAttempts; } @Override - public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { - this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; + public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; } @Override @@ -360,7 +360,7 @@ public boolean equals(Object o) { && Objects.equals(this.toolNames, that.toolNames) && Objects.equals(this.safetySettings, that.safetySettings) && Objects.equals(this.internalToolExecutionEnabled, that.internalToolExecutionEnabled) - && Objects.equals(this.internalToolExecutionMaxIterations, that.internalToolExecutionMaxIterations) + && Objects.equals(this.internalToolExecutionMaxAttempts, that.internalToolExecutionMaxAttempts) && Objects.equals(this.toolContext, that.toolContext); } @@ -369,7 +369,7 @@ public int hashCode() { return Objects.hash(this.stopSequences, this.temperature, this.topP, this.topK, this.candidateCount, this.frequencyPenalty, this.presencePenalty, this.maxOutputTokens, this.model, this.responseMimeType, this.toolCallbacks, this.toolNames, this.googleSearchRetrieval, this.safetySettings, - this.internalToolExecutionEnabled, this.internalToolExecutionMaxIterations, this.toolContext); + this.internalToolExecutionEnabled, this.internalToolExecutionMaxAttempts, this.toolContext); } @Override @@ -493,8 +493,8 @@ public Builder internalToolExecutionEnabled(boolean internalToolExecutionEnabled return this; } - public Builder internalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { - this.options.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; + public Builder internalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + this.options.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; return this; } diff --git a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java index 1696e03dd23..0381eb94656 100644 --- a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java +++ b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java @@ -127,7 +127,7 @@ public class ZhiPuAiChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxIterations; + private Integer internalToolExecutionMaxAttempts; @JsonIgnore private Map toolContext = new HashMap<>(); @@ -312,13 +312,13 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut } @Override - public Integer getInternalToolExecutionMaxIterations() { - return this.internalToolExecutionMaxIterations; + public Integer getInternalToolExecutionMaxAttempts() { + return this.internalToolExecutionMaxAttempts; } @Override - public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { - this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; + public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; } @Override @@ -345,8 +345,8 @@ public int hashCode() { result = prime * result + ((this.user == null) ? 0 : this.user.hashCode()); result = prime * result + ((this.internalToolExecutionEnabled == null) ? 0 : this.internalToolExecutionEnabled.hashCode()); - result = prime * result + ((this.internalToolExecutionMaxIterations == null) ? 0 - : this.internalToolExecutionMaxIterations.hashCode()); + result = prime * result + ((this.internalToolExecutionMaxAttempts == null) ? 0 + : this.internalToolExecutionMaxAttempts.hashCode()); result = prime * result + ((this.toolCallbacks == null) ? 0 : this.toolCallbacks.hashCode()); result = prime * result + ((this.toolNames == null) ? 0 : this.toolNames.hashCode()); result = prime * result + ((this.toolContext == null) ? 0 : this.toolContext.hashCode()); @@ -453,12 +453,12 @@ else if (!this.doSample.equals(other.doSample)) { else if (!this.internalToolExecutionEnabled.equals(other.internalToolExecutionEnabled)) { return false; } - if (this.internalToolExecutionMaxIterations == null) { - if (other.internalToolExecutionMaxIterations != null) { + if (this.internalToolExecutionMaxAttempts == null) { + if (other.internalToolExecutionMaxAttempts != null) { return false; } } - else if (!this.internalToolExecutionMaxIterations.equals(other.internalToolExecutionMaxIterations)) { + else if (!this.internalToolExecutionMaxAttempts.equals(other.internalToolExecutionMaxAttempts)) { return false; } if (this.toolContext == null) { @@ -492,10 +492,10 @@ public ToolCallingChatOptions merge(ChatOptions options) { builder.internalToolExecutionEnabled(toolCallingChatOptions.getInternalToolExecutionEnabled() != null ? (toolCallingChatOptions).getInternalToolExecutionEnabled() : this.getInternalToolExecutionEnabled()); - builder.internalToolExecutionMaxIterations( - toolCallingChatOptions.getInternalToolExecutionMaxIterations() != null - ? toolCallingChatOptions.getInternalToolExecutionMaxIterations() - : this.getInternalToolExecutionMaxIterations()); + builder.internalToolExecutionMaxAttempts( + toolCallingChatOptions.getInternalToolExecutionMaxAttempts() != null + ? toolCallingChatOptions.getInternalToolExecutionMaxAttempts() + : this.getInternalToolExecutionMaxAttempts()); Set toolNames = new HashSet<>(); if (this.toolNames != null) { @@ -526,7 +526,7 @@ public ToolCallingChatOptions merge(ChatOptions options) { } else { builder.internalToolExecutionEnabled(this.internalToolExecutionEnabled); - builder.internalToolExecutionMaxIterations(this.internalToolExecutionMaxIterations); + builder.internalToolExecutionMaxAttempts(this.internalToolExecutionMaxAttempts); builder.toolNames(this.toolNames != null ? new HashSet<>(this.toolNames) : null); builder.toolCallbacks(this.toolCallbacks != null ? new ArrayList<>(this.toolCallbacks) : null); builder.toolContext(this.toolContext != null ? new HashMap<>(this.toolContext) : null); @@ -632,8 +632,8 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } - public Builder internalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { - this.options.setInternalToolExecutionMaxIterations(internalToolExecutionMaxIterations); + public Builder internalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { + this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); return this; } diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java index 1c3b6772c4f..6d3a1fcfe4b 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java @@ -47,6 +47,9 @@ public class DefaultToolCallingChatOptions implements ToolCallingChatOptions { @Nullable private Boolean internalToolExecutionEnabled; + @Nullable + private Integer internalToolExecutionMaxAttempts; + @Nullable private String model; @@ -120,13 +123,13 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut } @Override - public Integer getInternalToolExecutionMaxIterations() { - return 0; + public Integer getInternalToolExecutionMaxAttempts() { + return this.internalToolExecutionMaxAttempts; } @Override - public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { - + public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; } @Override @@ -217,6 +220,7 @@ public T copy() { options.setToolNames(getToolNames()); options.setToolContext(getToolContext()); options.setInternalToolExecutionEnabled(getInternalToolExecutionEnabled()); + options.setInternalToolExecutionMaxAttempts(getInternalToolExecutionMaxAttempts()); options.setModel(getModel()); options.setFrequencyPenalty(getFrequencyPenalty()); options.setMaxTokens(getMaxTokens()); @@ -288,6 +292,12 @@ public ToolCallingChatOptions.Builder internalToolExecutionEnabled( return this; } + @Override + public ToolCallingChatOptions.Builder internalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); + return this; + } + @Override public ToolCallingChatOptions.Builder model(@Nullable String model) { this.options.setModel(model); diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java index 4281304d55f..4395992c549 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java @@ -44,6 +44,12 @@ public interface ToolCallingChatOptions extends ChatOptions { boolean DEFAULT_TOOL_EXECUTION_ENABLED = true; + /** + * No limit for tool execution attempts. + */ + int TOOL_EXECUTION_NO_LIMIT = Integer.MAX_VALUE; + int DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS = TOOL_EXECUTION_NO_LIMIT; + /** * ToolCallbacks to be registered with the ChatModel. */ @@ -78,18 +84,18 @@ public interface ToolCallingChatOptions extends ChatOptions { void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecutionEnabled); /** - * Get the maximum number of iterations for tool execution. 0 or null means no limit. - * @return the maximum number of iterations. + * Get the maximum number of attempts for tool execution. + * @return the maximum number of attempts. * @see #getInternalToolExecutionEnabled() */ @Nullable - Integer getInternalToolExecutionMaxIterations(); + Integer getInternalToolExecutionMaxAttempts(); /** - * Set the maximum number of iterations for tool execution. 0 or null means no limit. - * @param internalToolExecutionMaxIterations the maximum number of iterations. + * Set the maximum number of attempts for tool execution. + * @param internalToolExecutionMaxAttempts the maximum number of attempts. */ - void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations); + void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts); /** * Get the configured tool context. @@ -193,6 +199,13 @@ interface Builder extends ChatOptions.Builder { */ Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecutionEnabled); + /** + * the maximum number of attempts for tool execution. + * @param internalToolExecutionMaxAttempts the maximum number of attempts. + * @return the {@link ToolCallingChatOptions} Builder. + */ + Builder internalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts); + /** * Add a {@link Map} of context values into tool context. * @param context the map representing the tool context. diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java index 6ba92766929..f565ceaba61 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java @@ -74,4 +74,26 @@ default boolean isInternalToolExecutionEnabled(ChatOptions chatOptions) { return internalToolExecutionEnabled; } + /** + * Determines if tool execution should be performed by the Spring AI or by the client. + * @param chatOptions The options from the chat + * @param attempts The number of attempts to execute the tool + * @return true if tool execution should be performed by Spring AI, false if it should + * be performed by the client + */ + default boolean isInternalToolExecutionEnabled(ChatOptions chatOptions, int attempts) { + boolean internalToolExecutionEnabled = isInternalToolExecutionEnabled(chatOptions); + if (!internalToolExecutionEnabled) { + return false; + } + + if (chatOptions instanceof ToolCallingChatOptions toolCallingChatOptions) { + return toolCallingChatOptions.getInternalToolExecutionMaxAttempts() == null + || attempts <= toolCallingChatOptions.getInternalToolExecutionMaxAttempts(); + } else { + internalToolExecutionEnabled = true; + } + return internalToolExecutionEnabled; + } + } From e243d426eba4295d33c7aa88ba985736a487ed1e Mon Sep 17 00:00:00 2001 From: lambochen Date: Thu, 29 May 2025 23:57:06 +0800 Subject: [PATCH 03/25] ToolExecutionEligibilityChecker add logical for attempts Signed-off-by: lambochen --- .../tool/ToolExecutionEligibilityChecker.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java index f565ceaba61..1e3e0535222 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java @@ -27,6 +27,7 @@ * responses. * * @author Christian Tzolov + * @author lambochen */ public interface ToolExecutionEligibilityChecker extends Function { @@ -43,6 +44,19 @@ default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse return this.isInternalToolExecutionEnabled(promptOptions) && this.isToolCallResponse(chatResponse); } + /** + * Determines if tool execution should be performed based on the prompt options and chat response and attempts. + * @param promptOptions The options from the prompt + * @param chatResponse The response from the chat model + * @param attempts The number of attempts to execute the tool + * @return true if tool execution should be performed, false otherwise + */ + default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse chatResponse, int attempts) { + Assert.notNull(promptOptions, "promptOptions cannot be null"); + Assert.notNull(chatResponse, "chatResponse cannot be null"); + return this.isInternalToolExecutionEnabled(promptOptions, attempts) && this.isToolCallResponse(chatResponse); + } + /** * Determines if the response is a tool call message response. * @param chatResponse The response from the chat model call From 670d6913d0821c977de681029fa92daa5badad62 Mon Sep 17 00:00:00 2001 From: lambochen Date: Fri, 30 May 2025 00:09:17 +0800 Subject: [PATCH 04/25] OpenAiChatModel support internalToolExecutionMaxAttempts Signed-off-by: lambochen --- .../ai/minimax/MiniMaxChatOptions.java | 6 +++--- .../ai/openai/OpenAiChatModel.java | 16 ++++++++++++---- .../ai/zhipuai/ZhiPuAiChatOptions.java | 8 ++++---- .../tool/DefaultToolCallingChatOptions.java | 3 ++- .../ai/model/tool/ToolCallingChatOptions.java | 1 + .../tool/ToolExecutionEligibilityChecker.java | 6 ++++-- .../tool/ToolExecutionEligibilityPredicate.java | 16 ++++++++++++++++ 7 files changed, 42 insertions(+), 14 deletions(-) diff --git a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java index afd98b58ab1..be2c757c647 100644 --- a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java +++ b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java @@ -180,7 +180,7 @@ public static MiniMaxChatOptions fromOptions(MiniMaxChatOptions fromOptions) { .toolCallbacks(fromOptions.getToolCallbacks()) .toolNames(fromOptions.getToolNames()) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) - .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) + .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) .toolContext(fromOptions.getToolContext()) .build(); } @@ -395,8 +395,8 @@ public int hashCode() { result = prime * result + ((this.toolNames == null) ? 0 : this.toolNames.hashCode()); result = prime * result + ((this.internalToolExecutionEnabled == null) ? 0 : this.internalToolExecutionEnabled.hashCode()); - result = prime * result - + ((this.internalToolExecutionMaxAttempts == null) ? 0 : this.internalToolExecutionMaxAttempts.hashCode()); + result = prime * result + ((this.internalToolExecutionMaxAttempts == null) ? 0 + : this.internalToolExecutionMaxAttempts.hashCode()); result = prime * result + ((this.toolContext == null) ? 0 : this.toolContext.hashCode()); return result; } diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java index 7da34176c15..da9bb4c4a70 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java @@ -105,9 +105,11 @@ * @author Alexandros Pappas * @author Soby Chacko * @author Jonghoon Park + * @author lambochen * @see ChatModel * @see StreamingChatModel * @see OpenAiApi + * @see ToolCallingChatOptions */ public class OpenAiChatModel implements ChatModel { @@ -178,10 +180,10 @@ public ChatResponse call(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalCall(requestPrompt, null); + return this.internalCall(requestPrompt, null, 0); } - public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) { + public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { ChatCompletionRequest request = createRequest(prompt, false); @@ -240,7 +242,8 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons }); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response)) { + attempts++; + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. @@ -252,7 +255,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons else { // Send the tool execution result back to the model. return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response); + response, attempts); } } @@ -520,6 +523,9 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setInternalToolExecutionEnabled( ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); + requestOptions.setInternalToolExecutionMaxAttempts( + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), + this.defaultOptions.getInternalToolExecutionMaxAttempts())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -530,6 +536,8 @@ Prompt buildRequestPrompt(Prompt prompt) { else { requestOptions.setHttpHeaders(this.defaultOptions.getHttpHeaders()); requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); + requestOptions + .setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); diff --git a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java index 0381eb94656..5f054577cb3 100644 --- a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java +++ b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java @@ -492,10 +492,10 @@ public ToolCallingChatOptions merge(ChatOptions options) { builder.internalToolExecutionEnabled(toolCallingChatOptions.getInternalToolExecutionEnabled() != null ? (toolCallingChatOptions).getInternalToolExecutionEnabled() : this.getInternalToolExecutionEnabled()); - builder.internalToolExecutionMaxAttempts( - toolCallingChatOptions.getInternalToolExecutionMaxAttempts() != null - ? toolCallingChatOptions.getInternalToolExecutionMaxAttempts() - : this.getInternalToolExecutionMaxAttempts()); + builder + .internalToolExecutionMaxAttempts(toolCallingChatOptions.getInternalToolExecutionMaxAttempts() != null + ? toolCallingChatOptions.getInternalToolExecutionMaxAttempts() + : this.getInternalToolExecutionMaxAttempts()); Set toolNames = new HashSet<>(); if (this.toolNames != null) { diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java index 6d3a1fcfe4b..80720a8a281 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java @@ -293,7 +293,8 @@ public ToolCallingChatOptions.Builder internalToolExecutionEnabled( } @Override - public ToolCallingChatOptions.Builder internalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + public ToolCallingChatOptions.Builder internalToolExecutionMaxAttempts( + Integer internalToolExecutionMaxAttempts) { this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); return this; } diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java index 4395992c549..1c7f9337903 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java @@ -48,6 +48,7 @@ public interface ToolCallingChatOptions extends ChatOptions { * No limit for tool execution attempts. */ int TOOL_EXECUTION_NO_LIMIT = Integer.MAX_VALUE; + int DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS = TOOL_EXECUTION_NO_LIMIT; /** diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java index 1e3e0535222..b7d4ba3f119 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java @@ -45,7 +45,8 @@ default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse } /** - * Determines if tool execution should be performed based on the prompt options and chat response and attempts. + * Determines if tool execution should be performed based on the prompt options and + * chat response and attempts. * @param promptOptions The options from the prompt * @param chatResponse The response from the chat model * @param attempts The number of attempts to execute the tool @@ -104,7 +105,8 @@ default boolean isInternalToolExecutionEnabled(ChatOptions chatOptions, int atte if (chatOptions instanceof ToolCallingChatOptions toolCallingChatOptions) { return toolCallingChatOptions.getInternalToolExecutionMaxAttempts() == null || attempts <= toolCallingChatOptions.getInternalToolExecutionMaxAttempts(); - } else { + } + else { internalToolExecutionEnabled = true; } return internalToolExecutionEnabled; diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java index e3f048ebd41..20450e6611c 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java @@ -43,4 +43,20 @@ default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse return test(promptOptions, chatResponse); } + default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse chatResponse, int attempts) { + boolean isToolExecutionRequired = isToolExecutionRequired(promptOptions, chatResponse); + if (!isToolExecutionRequired) { + return true; + } + + if (promptOptions instanceof ToolCallingChatOptions toolCallingChatOptions) { + return toolCallingChatOptions.getInternalToolExecutionMaxAttempts() == null + || attempts <= toolCallingChatOptions.getInternalToolExecutionMaxAttempts(); + } + else { + isToolExecutionRequired = true; + } + return isToolExecutionRequired; + } + } From 092eb6c7f6812b8e4046b8aaed29c781bd1c9e54 Mon Sep 17 00:00:00 2001 From: lambochen Date: Fri, 30 May 2025 00:15:08 +0800 Subject: [PATCH 05/25] internalToolExecutionEnabled set default value is Integer.MAX_VALUE Signed-off-by: lambochen --- .../springframework/ai/anthropic/AnthropicChatOptions.java | 2 +- .../ai/azure/openai/AzureOpenAiChatOptions.java | 4 ++-- .../springframework/ai/deepseek/DeepSeekChatOptions.java | 6 +++--- .../org/springframework/ai/minimax/MiniMaxChatOptions.java | 5 +++-- .../springframework/ai/mistralai/MistralAiChatOptions.java | 4 ++-- .../org/springframework/ai/ollama/api/OllamaOptions.java | 4 ++-- .../org/springframework/ai/openai/OpenAiChatOptions.java | 4 ++-- .../ai/vertexai/gemini/VertexAiGeminiChatOptions.java | 4 ++-- .../org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java | 5 +++-- .../ai/model/tool/DefaultToolCallingChatOptions.java | 2 +- .../ai/model/tool/ToolCallingChatOptions.java | 2 +- 11 files changed, 22 insertions(+), 20 deletions(-) diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java index 13def6d537b..0596de620fd 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java @@ -81,7 +81,7 @@ public class AnthropicChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxAttempts; + private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; @JsonIgnore private Map toolContext = new HashMap<>(); diff --git a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java index 8ce46c64fb0..d077245cd3d 100644 --- a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java +++ b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java @@ -202,7 +202,7 @@ public class AzureOpenAiChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxAttempts; + private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; /** * Whether to include token usage information in streaming chat completion responses. @@ -679,7 +679,7 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } - public Builder InternalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + public Builder internalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxIterations); return this; } diff --git a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java index 576b7cedb79..e8916426dc0 100644 --- a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java +++ b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java @@ -124,7 +124,7 @@ public class DeepSeekChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxAttempts; + private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; /** * Tool Function Callbacks to register with the ChatModel. @@ -301,7 +301,7 @@ public Integer getInternalToolExecutionMaxAttempts() { @Override @JsonIgnore - public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + public void setInternalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; } @@ -506,7 +506,7 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } - public Builder internalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + public Builder internalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); return this; } diff --git a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java index be2c757c647..2449cfee3e8 100644 --- a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java +++ b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java @@ -155,7 +155,7 @@ public class MiniMaxChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxAttempts; + private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; // @formatter:on @@ -395,7 +395,8 @@ public int hashCode() { result = prime * result + ((this.toolNames == null) ? 0 : this.toolNames.hashCode()); result = prime * result + ((this.internalToolExecutionEnabled == null) ? 0 : this.internalToolExecutionEnabled.hashCode()); - result = prime * result + ((this.internalToolExecutionMaxAttempts == null) ? 0 + result = prime * result + ((this.internalToolExecutionMaxAttempts == null) + ? ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS : this.internalToolExecutionMaxAttempts.hashCode()); result = prime * result + ((this.toolContext == null) ? 0 : this.toolContext.hashCode()); return result; diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java index e87b29b3b2c..7a2cf9d2deb 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java @@ -158,7 +158,7 @@ public class MistralAiChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxAttempts; + private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; @JsonIgnore private Map toolContext = new HashMap<>(); @@ -358,7 +358,7 @@ public Integer getInternalToolExecutionMaxAttempts() { } @Override - public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + public void setInternalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; } diff --git a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java index b6057f08bc6..edacc96cc74 100644 --- a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java +++ b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java @@ -323,7 +323,7 @@ public class OllamaOptions implements ToolCallingChatOptions, EmbeddingOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxAttempts; + private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; /** * Tool Function Callbacks to register with the ChatModel. @@ -757,7 +757,7 @@ public Integer getInternalToolExecutionMaxAttempts() { } @Override - public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + public void setInternalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; } diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java index 0b1c2485e93..b0257016ab1 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java @@ -220,7 +220,7 @@ public class OpenAiChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxAttempts; + private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; /** * Optional HTTP headers to be added to the chat completion request. @@ -515,7 +515,7 @@ public Integer getInternalToolExecutionMaxAttempts() { } @Override - public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + public void setInternalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; } diff --git a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java index 4250c00fa13..b2142a41d1e 100644 --- a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java +++ b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java @@ -128,7 +128,7 @@ public class VertexAiGeminiChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxAttempts; + private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; @JsonIgnore private Map toolContext = new HashMap<>(); @@ -291,7 +291,7 @@ public Integer getInternalToolExecutionMaxAttempts() { } @Override - public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + public void setInternalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; } diff --git a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java index 5f054577cb3..1812f4e5d22 100644 --- a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java +++ b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java @@ -127,7 +127,7 @@ public class ZhiPuAiChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxAttempts; + private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; @JsonIgnore private Map toolContext = new HashMap<>(); @@ -152,6 +152,7 @@ public static ZhiPuAiChatOptions fromOptions(ZhiPuAiChatOptions fromOptions) { .toolCallbacks(fromOptions.getToolCallbacks()) .toolNames(fromOptions.getToolNames()) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) + .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) .toolContext(fromOptions.getToolContext()) .build(); } @@ -317,7 +318,7 @@ public Integer getInternalToolExecutionMaxAttempts() { } @Override - public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + public void setInternalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; } diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java index 80720a8a281..eea84ecdec1 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java @@ -48,7 +48,7 @@ public class DefaultToolCallingChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @Nullable - private Integer internalToolExecutionMaxAttempts; + private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; @Nullable private String model; diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java index 1c7f9337903..0302e4212c6 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java @@ -96,7 +96,7 @@ public interface ToolCallingChatOptions extends ChatOptions { * Set the maximum number of attempts for tool execution. * @param internalToolExecutionMaxAttempts the maximum number of attempts. */ - void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts); + void setInternalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts); /** * Get the configured tool context. From 2d128ba304dd3c36c582bb60600a24a300925487 Mon Sep 17 00:00:00 2001 From: lambochen Date: Fri, 30 May 2025 00:28:24 +0800 Subject: [PATCH 06/25] OpenAiChatModel support InternalToolExecutionMaxAttempts Signed-off-by: lambochen --- .../springframework/ai/openai/OpenAiChatModel.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java index da9bb4c4a70..5e6f8ddf1af 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java @@ -180,7 +180,7 @@ public ChatResponse call(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalCall(requestPrompt, null, 0); + return this.internalCall(requestPrompt, null, 1); } public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { @@ -242,7 +242,6 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons }); - attempts++; if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response); if (toolExecutionResult.returnDirect()) { @@ -255,7 +254,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons else { // Send the tool execution result back to the model. return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response, attempts); + response, attempts + 1); } } @@ -267,10 +266,10 @@ public Flux stream(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); - return internalStream(requestPrompt, null); + return internalStream(requestPrompt, null, 1); } - public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse) { + public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { return Flux.deferContextual(contextView -> { ChatCompletionRequest request = createRequest(prompt, true); @@ -365,7 +364,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha // @formatter:off Flux flux = chatResponse.flatMap(response -> { - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { return Flux.defer(() -> { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous @@ -379,7 +378,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha else { // Send the tool execution result back to the model. return this.internalStream(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response); + response, attempts + 1); } }).subscribeOn(Schedulers.boundedElastic()); } From 253d19ee277282e91952c732c8c96c9f403c47ed Mon Sep 17 00:00:00 2001 From: lambochen Date: Fri, 30 May 2025 00:42:36 +0800 Subject: [PATCH 07/25] AnthropicChatModel support InternalToolExecutionMaxAttempts Signed-off-by: lambochen --- .../ai/anthropic/AnthropicChatModel.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java index 270f3bef43d..f07a901a789 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java @@ -90,6 +90,7 @@ * @author Alexandros Pappas * @author Jonghoon Park * @author Soby Chacko + * @author lambochen * @since 1.0.0 */ public class AnthropicChatModel implements ChatModel { @@ -170,10 +171,10 @@ public ChatResponse call(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalCall(requestPrompt, null); + return this.internalCall(requestPrompt, null, 1); } - public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) { + public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { ChatCompletionRequest request = createRequest(prompt, false); ChatModelObservationContext observationContext = ChatModelObservationContext.builder() @@ -203,7 +204,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons return chatResponse; }); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. @@ -215,7 +216,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons else { // Send the tool execution result back to the model. return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response); + response, attempts + 1); } } @@ -232,10 +233,10 @@ public Flux stream(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalStream(requestPrompt, null); + return this.internalStream(requestPrompt, null, 1); } - public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse) { + public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { return Flux.deferContextual(contextView -> { ChatCompletionRequest request = createRequest(prompt, true); @@ -260,7 +261,8 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha Usage accumulatedUsage = UsageCalculator.getCumulativeUsage(currentChatResponseUsage, previousChatResponse); ChatResponse chatResponse = toChatResponse(chatCompletionResponse, accumulatedUsage); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse) && chatResponse.hasFinishReasons(Set.of("tool_use"))) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse, attempts) + && chatResponse.hasFinishReasons(Set.of("tool_use"))) { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous return Flux.defer(() -> { @@ -274,7 +276,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha else { // Send the tool execution result back to the model. return this.internalStream(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - chatResponse); + chatResponse, attempts + 1); } }).subscribeOn(Schedulers.boundedElastic()); } @@ -437,6 +439,11 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setInternalToolExecutionEnabled( ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); + requestOptions.setInternalToolExecutionMaxAttempts( + ModelOptionsUtils.mergeOption( + runtimeOptions.getInternalToolExecutionMaxAttempts(), + defaultOptions.getInternalToolExecutionMaxAttempts()) + ); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -447,6 +454,7 @@ Prompt buildRequestPrompt(Prompt prompt) { else { requestOptions.setHttpHeaders(this.defaultOptions.getHttpHeaders()); requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); + requestOptions.setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); From 93efce51012ab474ff64ed8b9367e4359409b5c2 Mon Sep 17 00:00:00 2001 From: lambochen Date: Fri, 30 May 2025 00:46:04 +0800 Subject: [PATCH 08/25] AzureOpenAiChatModel support InternalToolExecutionMaxAttempts Signed-off-by: lambochen --- .../ai/azure/openai/AzureOpenAiChatModel.java | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java index 1933f575300..106412bd6e4 100644 --- a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java +++ b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java @@ -122,6 +122,7 @@ * @author Berjan Jonker * @author Andres da Silva Santos * @author Bart Veenstra + * @author lambochen * @see ChatModel * @see com.azure.ai.openai.OpenAIClient * @since 1.0.0 @@ -247,10 +248,10 @@ public ChatResponse call(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalCall(requestPrompt, null); + return this.internalCall(requestPrompt, null, 1); } - public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) { + public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { ChatModelObservationContext observationContext = ChatModelObservationContext.builder() .prompt(prompt) @@ -270,7 +271,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons return chatResponse; }); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. @@ -282,7 +283,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons else { // Send the tool execution result back to the model. return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response); + response, attempts + 1); } } @@ -294,10 +295,10 @@ public Flux stream(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalStream(requestPrompt, null); + return this.internalStream(requestPrompt, null, 1); } - public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse) { + public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { return Flux.deferContextual(contextView -> { ChatCompletionsOptions options = toAzureChatCompletionsOptions(prompt); @@ -377,7 +378,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha }); return chatResponseFlux.flatMap(chatResponse -> { - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse, attempts)) { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous return Flux.defer(() -> { @@ -393,7 +394,8 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha // Send the tool execution result back to the model. return this.internalStream( new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - chatResponse); + chatResponse, + attempts + 1); } }).subscribeOn(Schedulers.boundedElastic()); } @@ -666,6 +668,12 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setInternalToolExecutionEnabled( ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); + runtimeOptions.setInternalToolExecutionMaxAttempts( + ModelOptionsUtils.mergeOption( + runtimeOptions.getInternalToolExecutionMaxAttempts(), + this.defaultOptions.getInternalToolExecutionMaxAttempts() + ) + ); requestOptions.setStreamUsage(ModelOptionsUtils.mergeOption(runtimeOptions.getStreamUsage(), this.defaultOptions.getStreamUsage())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), @@ -677,6 +685,7 @@ Prompt buildRequestPrompt(Prompt prompt) { } else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); + requestOptions.setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); requestOptions.setStreamUsage(this.defaultOptions.getStreamUsage()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); From d660152d543808c8e975c21b636aa81d3cda881c Mon Sep 17 00:00:00 2001 From: lambochen Date: Fri, 30 May 2025 00:50:03 +0800 Subject: [PATCH 09/25] BedrockProxyChatModel support internalToolExecutionMaxAttempts Signed-off-by: lambochen --- .../converse/BedrockProxyChatModel.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java b/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java index d30f2517756..668911b18af 100644 --- a/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java +++ b/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java @@ -133,6 +133,7 @@ * @author Alexandros Pappas * @author Jihoon Kim * @author Soby Chacko + * @author lambochen * @since 1.0.0 */ public class BedrockProxyChatModel implements ChatModel { @@ -213,10 +214,10 @@ private static ToolCallingChatOptions from(ChatOptions options) { @Override public ChatResponse call(Prompt prompt) { Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalCall(requestPrompt, null); + return this.internalCall(requestPrompt, null, 1); } - private ChatResponse internalCall(Prompt prompt, ChatResponse perviousChatResponse) { + private ChatResponse internalCall(Prompt prompt, ChatResponse perviousChatResponse, int attempts) { ConverseRequest converseRequest = this.createRequest(prompt); @@ -241,7 +242,7 @@ private ChatResponse internalCall(Prompt prompt, ChatResponse perviousChatRespon return response; }); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse) + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse, attempts) && chatResponse.hasFinishReasons(Set.of(StopReason.TOOL_USE.toString()))) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, chatResponse); if (toolExecutionResult.returnDirect()) { @@ -254,7 +255,7 @@ private ChatResponse internalCall(Prompt prompt, ChatResponse perviousChatRespon else { // Send the tool execution result back to the model. return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - chatResponse); + chatResponse, attempts + 1); } } return chatResponse; @@ -310,6 +311,11 @@ Prompt buildRequestPrompt(Prompt prompt) { .internalToolExecutionEnabled(runtimeOptions.getInternalToolExecutionEnabled() != null ? runtimeOptions.getInternalToolExecutionEnabled() : this.defaultOptions.getInternalToolExecutionEnabled()) + .internalToolExecutionMaxAttempts( + ModelOptionsUtils.mergeOption( + runtimeOptions.getInternalToolExecutionMaxAttempts(), + this.defaultOptions.getInternalToolExecutionMaxAttempts()) + ) .build(); } @@ -640,10 +646,10 @@ private ChatResponse toChatResponse(ConverseResponse response, ChatResponse perv @Override public Flux stream(Prompt prompt) { Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalStream(requestPrompt, null); + return this.internalStream(requestPrompt, null, 1); } - private Flux internalStream(Prompt prompt, ChatResponse perviousChatResponse) { + private Flux internalStream(Prompt prompt, ChatResponse perviousChatResponse, int attempts) { Assert.notNull(prompt, "'prompt' must not be null"); return Flux.deferContextual(contextView -> { @@ -676,7 +682,7 @@ private Flux internalStream(Prompt prompt, ChatResponse perviousCh Flux chatResponseFlux = chatResponses.switchMap(chatResponse -> { - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse) + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse, attempts) && chatResponse.hasFinishReasons(Set.of(StopReason.TOOL_USE.toString()))) { // FIXME: bounded elastic needs to be used since tool calling @@ -695,7 +701,8 @@ private Flux internalStream(Prompt prompt, ChatResponse perviousCh // Send the tool execution result back to the model. return this.internalStream( new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - chatResponse); + chatResponse, + attempts + 1); } }).subscribeOn(Schedulers.boundedElastic()); } From 2c7fc3dcf4c892050dc05b4f68dde0db540d7c92 Mon Sep 17 00:00:00 2001 From: lambochen Date: Fri, 30 May 2025 00:52:47 +0800 Subject: [PATCH 10/25] DeepSeekChatModel support internalToolExecutionMaxAttempts Signed-off-by: lambochen --- .../ai/deepseek/DeepSeekChatModel.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java index 4b7607c6e38..c750e31df84 100644 --- a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java +++ b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java @@ -75,6 +75,7 @@ * backed by {@link DeepSeekApi}. * * @author Geng Rong + * @author lambochen */ public class DeepSeekChatModel implements ChatModel { @@ -147,10 +148,10 @@ public DeepSeekChatModel(DeepSeekApi deepSeekApi, DeepSeekChatOptions defaultOpt @Override public ChatResponse call(Prompt prompt) { Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalCall(requestPrompt, null); + return this.internalCall(requestPrompt, null, 1); } - public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) { + public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { ChatCompletionRequest request = createRequest(prompt, false); @@ -205,7 +206,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons }); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. @@ -217,7 +218,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons else { // Send the tool execution result back to the model. return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response); + response, attempts + 1); } } @@ -227,10 +228,10 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons @Override public Flux stream(Prompt prompt) { Prompt requestPrompt = buildRequestPrompt(prompt); - return internalStream(requestPrompt, null); + return internalStream(requestPrompt, null, 1); } - public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse) { + public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { return Flux.deferContextual(contextView -> { ChatCompletionRequest request = createRequest(prompt, true); @@ -285,7 +286,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha // @formatter:off Flux flux = chatResponse.flatMap(response -> { - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { return Flux.defer(() -> { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous @@ -299,7 +300,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha else { // Send the tool execution result back to the model. return this.internalStream(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response); + response, attempts + 1); } }).subscribeOn(Schedulers.boundedElastic()); } @@ -397,6 +398,11 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setInternalToolExecutionEnabled( ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); + requestOptions.setInternalToolExecutionMaxAttempts( + ModelOptionsUtils.mergeOption( + runtimeOptions.getInternalToolExecutionMaxAttempts(), + this.defaultOptions.getInternalToolExecutionMaxAttempts()) + ); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -406,6 +412,7 @@ Prompt buildRequestPrompt(Prompt prompt) { } else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); + requestOptions.setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); From 1a11eca657a214f24d511dc96d008ae8e4fc86ff Mon Sep 17 00:00:00 2001 From: lambochen Date: Fri, 30 May 2025 00:58:03 +0800 Subject: [PATCH 11/25] MiniMaxChatModel support InternalToolExecutionMaxAttempts Signed-off-by: lambochen --- .../ai/minimax/MiniMaxChatModel.java | 104 ++++++++++-------- 1 file changed, 59 insertions(+), 45 deletions(-) diff --git a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatModel.java b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatModel.java index e5a774cacf9..c23de95ae3d 100644 --- a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatModel.java +++ b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatModel.java @@ -79,9 +79,11 @@ * @author Geng Rong * @author Alexandros Pappas * @author Ilayaperumal Gopinathan + * @author lambochen * @see ChatModel * @see StreamingChatModel * @see MiniMaxApi + * @see ToolCallingChatOptions * @since 1.0.0 M1 */ public class MiniMaxChatModel implements ChatModel { @@ -236,37 +238,41 @@ public ChatResponse call(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); + return internalCall(requestPrompt, 1); + } + + private ChatResponse internalCall(Prompt requestPrompt, int attempts) { ChatCompletionRequest request = createRequest(requestPrompt, false); ChatModelObservationContext observationContext = ChatModelObservationContext.builder() - .prompt(requestPrompt) - .provider(MiniMaxApiConstants.PROVIDER_NAME) - .build(); + .prompt(requestPrompt) + .provider(MiniMaxApiConstants.PROVIDER_NAME) + .build(); ChatResponse response = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION - .observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, - this.observationRegistry) - .observe(() -> { + .observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, + this.observationRegistry) + .observe(() -> { - ResponseEntity completionEntity = this.retryTemplate - .execute(ctx -> this.miniMaxApi.chatCompletionEntity(request)); + ResponseEntity completionEntity = this.retryTemplate + .execute(ctx -> this.miniMaxApi.chatCompletionEntity(request)); - var chatCompletion = completionEntity.getBody(); + var chatCompletion = completionEntity.getBody(); - if (chatCompletion == null) { - logger.warn("No chat completion returned for prompt: {}", requestPrompt); - return new ChatResponse(List.of()); - } + if (chatCompletion == null) { + logger.warn("No chat completion returned for prompt: {}", requestPrompt); + return new ChatResponse(List.of()); + } - List choices = chatCompletion.choices(); - if (choices == null) { - logger.warn("No choices returned for prompt: {}, because: {}}", requestPrompt, - chatCompletion.baseResponse().message()); - return new ChatResponse(List.of()); - } + List choices = chatCompletion.choices(); + if (choices == null) { + logger.warn("No choices returned for prompt: {}, because: {}}", requestPrompt, + chatCompletion.baseResponse().message()); + return new ChatResponse(List.of()); + } - List generations = choices.stream().map(choice -> { - // @formatter:off + List generations = choices.stream().map(choice -> { + // @formatter:off // if the choice is a web search tool call, return last message of choice.messages ChatCompletionMessage message = null; if (choice.message() != null) { @@ -282,28 +288,28 @@ else if (!CollectionUtils.isEmpty(choice.messages())) { "role", message != null && message.role() != null ? message.role().name() : "", "finishReason", choice.finishReason() != null ? choice.finishReason().name() : ""); // @formatter:on - return buildGeneration(message, choice.finishReason(), metadata); - }).toList(); + return buildGeneration(message, choice.finishReason(), metadata); + }).toList(); - ChatResponse chatResponse = new ChatResponse(generations, from(completionEntity.getBody())); + ChatResponse chatResponse = new ChatResponse(generations, from(completionEntity.getBody())); - observationContext.setResponse(chatResponse); + observationContext.setResponse(chatResponse); - return chatResponse; - }); + return chatResponse; + }); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(requestPrompt.getOptions(), response)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(requestPrompt.getOptions(), response, attempts)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(requestPrompt, response); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. return ChatResponse.builder() - .from(response) - .generations(ToolExecutionResult.buildGenerations(toolExecutionResult)) - .build(); + .from(response) + .generations(ToolExecutionResult.buildGenerations(toolExecutionResult)) + .build(); } else { // Send the tool execution result back to the model. - return this.call(new Prompt(toolExecutionResult.conversationHistory(), requestPrompt.getOptions())); + return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), requestPrompt.getOptions()), attempts + 1); } } @@ -320,20 +326,24 @@ public Flux stream(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); + return internalStream(requestPrompt, 1); + } + + private Flux internalStream(Prompt requestPrompt, int attempts) { return Flux.deferContextual(contextView -> { ChatCompletionRequest request = createRequest(requestPrompt, true); Flux completionChunks = this.retryTemplate - .execute(ctx -> this.miniMaxApi.chatCompletionStream(request)); + .execute(ctx -> this.miniMaxApi.chatCompletionStream(request)); // For chunked responses, only the first chunk contains the choice role. // The rest of the chunks with same ID share the same role. ConcurrentHashMap roleMap = new ConcurrentHashMap<>(); final ChatModelObservationContext observationContext = ChatModelObservationContext.builder() - .prompt(requestPrompt) - .provider(MiniMaxApiConstants.PROVIDER_NAME) - .build(); + .prompt(requestPrompt) + .provider(MiniMaxApiConstants.PROVIDER_NAME) + .build(); Observation observation = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION.observation( this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, @@ -344,12 +354,12 @@ public Flux stream(Prompt prompt) { // Convert the ChatCompletionChunk into a ChatCompletion to be able to reuse // the function call handling logic. Flux chatResponse = completionChunks.map(this::chunkToChatCompletion) - .switchMap(chatCompletion -> Mono.just(chatCompletion).map(chatCompletion2 -> { - try { - @SuppressWarnings("null") - String id = chatCompletion2.id(); + .switchMap(chatCompletion -> Mono.just(chatCompletion).map(chatCompletion2 -> { + try { + @SuppressWarnings("null") + String id = chatCompletion2.id(); - // @formatter:off + // @formatter:off List generations = chatCompletion2.choices().stream().map(choice -> { if (choice.message().role() != null) { roleMap.putIfAbsent(id, choice.message().role().name()); @@ -361,15 +371,15 @@ public Flux stream(Prompt prompt) { return buildGeneration(choice, metadata); }).toList(); return new ChatResponse(generations, from(chatCompletion2)); - } - catch (Exception e) { + } + catch (Exception e) { logger.error("Error processing chat completion", e); return new ChatResponse(List.of()); } })); Flux flux = chatResponse.flatMap(response -> { - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(requestPrompt.getOptions(), response)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(requestPrompt.getOptions(), response, attempts)) { return Flux.defer(() -> { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous @@ -382,7 +392,7 @@ public Flux stream(Prompt prompt) { } else { // Send the tool execution result back to the model. - return this.stream(new Prompt(toolExecutionResult.conversationHistory(), requestPrompt.getOptions())); + return this.internalStream(new Prompt(toolExecutionResult.conversationHistory(), requestPrompt.getOptions()), attempts + 1); } }).subscribeOn(Schedulers.boundedElastic()); } @@ -472,6 +482,9 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setInternalToolExecutionEnabled( ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); + requestOptions.setInternalToolExecutionMaxAttempts( + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), + this.defaultOptions.getInternalToolExecutionMaxAttempts())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -481,6 +494,7 @@ Prompt buildRequestPrompt(Prompt prompt) { } else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); + requestOptions.setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); From 69d0d5eaf874735ff1bdef49f9f6beeb8fb7dfce Mon Sep 17 00:00:00 2001 From: lambochen Date: Fri, 30 May 2025 01:08:53 +0800 Subject: [PATCH 12/25] MistralAiChatModel support InternalToolExecutionMaxAttempts Signed-off-by: lambochen --- .../ai/mistralai/MistralAiChatModel.java | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java index b9838dcedf1..6dbdf1ab3e7 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java @@ -83,7 +83,10 @@ * @author luocongqiu * @author Ilayaperumal Gopinathan * @author Alexandros Pappas + * @author lambochen * @since 1.0.0 + * + * @see ToolCallingChatOptions */ public class MistralAiChatModel implements ChatModel { @@ -177,10 +180,10 @@ public ChatResponse call(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalCall(requestPrompt, null); + return this.internalCall(requestPrompt, null, 1); } - public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) { + public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { MistralAiApi.ChatCompletionRequest request = createRequest(prompt, false); @@ -225,7 +228,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons return chatResponse; }); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. @@ -237,7 +240,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons else { // Send the tool execution result back to the model. return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response); + response, attempts + 1); } } @@ -249,10 +252,10 @@ public Flux stream(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalStream(requestPrompt, null); + return this.internalStream(requestPrompt, null, 1); } - public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse) { + public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { return Flux.deferContextual(contextView -> { var request = createRequest(prompt, true); @@ -313,7 +316,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha // @formatter:off Flux chatResponseFlux = chatResponse.flatMap(response -> { - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous return Flux.defer(() -> { @@ -327,7 +330,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha else { // Send the tool execution result back to the model. return this.internalStream(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response); + response, attempts + 1); } }).subscribeOn(Schedulers.boundedElastic()); } @@ -394,6 +397,12 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setInternalToolExecutionEnabled( ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); + requestOptions.setInternalToolExecutionMaxAttempts( + ModelOptionsUtils.mergeOption( + runtimeOptions.getInternalToolExecutionMaxAttempts(), + this.defaultOptions.getInternalToolExecutionMaxAttempts() + ) + ); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -403,6 +412,7 @@ Prompt buildRequestPrompt(Prompt prompt) { } else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); + requestOptions.setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); From f808731d227694165d1e5ecced2cd643efd66d5e Mon Sep 17 00:00:00 2001 From: lambochen Date: Fri, 30 May 2025 01:11:25 +0800 Subject: [PATCH 13/25] OllamaChatModel support InternalToolExecutionMaxAttempts Signed-off-by: lambochen --- .../ai/ollama/OllamaChatModel.java | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java index a75a274a797..33a0ef0a1aa 100644 --- a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java +++ b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java @@ -84,7 +84,10 @@ * @author Jihoon Kim * @author Alexandros Pappas * @author Ilayaperumal Gopinathan + * @author lambochen * @since 1.0.0 + * + * @see ToolCallingChatOptions */ public class OllamaChatModel implements ChatModel { @@ -216,10 +219,10 @@ public ChatResponse call(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalCall(requestPrompt, null); + return this.internalCall(requestPrompt, null, 1); } - private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) { + private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { OllamaApi.ChatRequest request = ollamaChatRequest(prompt, false); @@ -262,7 +265,7 @@ private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespon }); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. @@ -274,7 +277,7 @@ private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespon else { // Send the tool execution result back to the model. return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response); + response, attempts + 1); } } @@ -286,10 +289,10 @@ public Flux stream(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalStream(requestPrompt, null); + return this.internalStream(requestPrompt, null, 1); } - private Flux internalStream(Prompt prompt, ChatResponse previousChatResponse) { + private Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { return Flux.deferContextual(contextView -> { OllamaApi.ChatRequest request = ollamaChatRequest(prompt, true); @@ -334,7 +337,7 @@ private Flux internalStream(Prompt prompt, ChatResponse previousCh // @formatter:off Flux chatResponseFlux = chatResponse.flatMap(response -> { - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous return Flux.defer(() -> { @@ -348,7 +351,7 @@ private Flux internalStream(Prompt prompt, ChatResponse previousCh else { // Send the tool execution result back to the model. return this.internalStream(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response); + response, attempts + 1); } }).subscribeOn(Schedulers.boundedElastic()); } @@ -390,6 +393,11 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setInternalToolExecutionEnabled( ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); + requestOptions.setInternalToolExecutionMaxAttempts( + ModelOptionsUtils.mergeOption( + runtimeOptions.getInternalToolExecutionMaxAttempts(), + this.defaultOptions.getInternalToolExecutionMaxAttempts()) + ); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -399,6 +407,7 @@ Prompt buildRequestPrompt(Prompt prompt) { } else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); + requestOptions.setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); From bf9df7179e6ad2f606470825576b6650f0360700 Mon Sep 17 00:00:00 2001 From: lambochen Date: Fri, 30 May 2025 01:14:36 +0800 Subject: [PATCH 14/25] VertexAiGeminiChatModel support InternalToolExecutionMaxAttempts Signed-off-by: lambochen --- .../gemini/VertexAiGeminiChatModel.java | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java index 01ab8b96c02..afe8fd3f671 100644 --- a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java +++ b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java @@ -136,10 +136,12 @@ * @author Jihoon Kim * @author Alexandros Pappas * @author Ilayaperumal Gopinathan + * @author lambochen * @since 0.8.1 * @see VertexAiGeminiChatOptions * @see ToolCallingManager * @see ChatModel + * @see ToolCallingChatOptions */ public class VertexAiGeminiChatModel implements ChatModel, DisposableBean { @@ -389,10 +391,10 @@ private static Schema jsonToSchema(String json) { @Override public ChatResponse call(Prompt prompt) { var requestPrompt = this.buildRequestPrompt(prompt); - return this.internalCall(requestPrompt, null); + return this.internalCall(requestPrompt, null, 1); } - private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) { + private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { ChatModelObservationContext observationContext = ChatModelObservationContext.builder() .prompt(prompt) @@ -425,7 +427,7 @@ private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespon return chatResponse; })); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. @@ -437,7 +439,7 @@ private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespon else { // Send the tool execution result back to the model. return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response); + response, attempts + 1); } } @@ -469,6 +471,11 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setInternalToolExecutionEnabled( ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); + requestOptions.setInternalToolExecutionMaxAttempts( + ModelOptionsUtils.mergeOption( + runtimeOptions.getInternalToolExecutionMaxAttempts(), + this.defaultOptions.getInternalToolExecutionMaxAttempts()) + ); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -483,6 +490,7 @@ Prompt buildRequestPrompt(Prompt prompt) { } else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); + requestOptions.setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); @@ -499,10 +507,10 @@ Prompt buildRequestPrompt(Prompt prompt) { @Override public Flux stream(Prompt prompt) { var requestPrompt = this.buildRequestPrompt(prompt); - return this.internalStream(requestPrompt, null); + return this.internalStream(requestPrompt, null, 1); } - public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse) { + public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { return Flux.deferContextual(contextView -> { ChatModelObservationContext observationContext = ChatModelObservationContext.builder() @@ -538,7 +546,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha // @formatter:off Flux flux = chatResponseFlux.flatMap(response -> { - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous return Flux.defer(() -> { @@ -551,7 +559,10 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha } else { // Send the tool execution result back to the model. - return this.internalStream(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), response); + return this.internalStream( + new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), + response, + attempts + 1); } }).subscribeOn(Schedulers.boundedElastic()); } From 8dd816d9bc991f332ae0730872ac5b9476b677ab Mon Sep 17 00:00:00 2001 From: lambochen Date: Fri, 30 May 2025 01:18:43 +0800 Subject: [PATCH 15/25] ZhiPuAiChatModel support InternalToolExecutionMaxAttempts Signed-off-by: lambochen --- .../ai/anthropic/AnthropicChatModel.java | 9 +- .../ai/azure/openai/AzureOpenAiChatModel.java | 16 ++-- .../converse/BedrockProxyChatModel.java | 15 ++- .../ai/deepseek/DeepSeekChatModel.java | 9 +- .../ai/minimax/MiniMaxChatModel.java | 92 ++++++++++--------- .../ai/mistralai/MistralAiChatModel.java | 11 +-- .../ai/ollama/OllamaChatModel.java | 10 +- .../gemini/VertexAiGeminiChatModel.java | 9 +- .../ai/zhipuai/ZhiPuAiChatModel.java | 74 +++++++++------ .../ai/zhipuai/ZhiPuAiChatOptions.java | 2 +- 10 files changed, 129 insertions(+), 118 deletions(-) diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java index f07a901a789..f55b18a2b83 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java @@ -440,10 +440,8 @@ Prompt buildRequestPrompt(Prompt prompt) { ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); requestOptions.setInternalToolExecutionMaxAttempts( - ModelOptionsUtils.mergeOption( - runtimeOptions.getInternalToolExecutionMaxAttempts(), - defaultOptions.getInternalToolExecutionMaxAttempts()) - ); + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), + defaultOptions.getInternalToolExecutionMaxAttempts())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -454,7 +452,8 @@ Prompt buildRequestPrompt(Prompt prompt) { else { requestOptions.setHttpHeaders(this.defaultOptions.getHttpHeaders()); requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); - requestOptions.setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); + requestOptions + .setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); diff --git a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java index 106412bd6e4..83671201ed4 100644 --- a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java +++ b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java @@ -378,7 +378,8 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha }); return chatResponseFlux.flatMap(chatResponse -> { - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse, attempts)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse, + attempts)) { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous return Flux.defer(() -> { @@ -394,8 +395,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha // Send the tool execution result back to the model. return this.internalStream( new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - chatResponse, - attempts + 1); + chatResponse, attempts + 1); } }).subscribeOn(Schedulers.boundedElastic()); } @@ -669,11 +669,8 @@ Prompt buildRequestPrompt(Prompt prompt) { ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); runtimeOptions.setInternalToolExecutionMaxAttempts( - ModelOptionsUtils.mergeOption( - runtimeOptions.getInternalToolExecutionMaxAttempts(), - this.defaultOptions.getInternalToolExecutionMaxAttempts() - ) - ); + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), + this.defaultOptions.getInternalToolExecutionMaxAttempts())); requestOptions.setStreamUsage(ModelOptionsUtils.mergeOption(runtimeOptions.getStreamUsage(), this.defaultOptions.getStreamUsage())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), @@ -685,7 +682,8 @@ Prompt buildRequestPrompt(Prompt prompt) { } else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); - requestOptions.setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); + requestOptions + .setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); requestOptions.setStreamUsage(this.defaultOptions.getStreamUsage()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); diff --git a/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java b/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java index 668911b18af..36514b7cdf2 100644 --- a/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java +++ b/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java @@ -311,11 +311,9 @@ Prompt buildRequestPrompt(Prompt prompt) { .internalToolExecutionEnabled(runtimeOptions.getInternalToolExecutionEnabled() != null ? runtimeOptions.getInternalToolExecutionEnabled() : this.defaultOptions.getInternalToolExecutionEnabled()) - .internalToolExecutionMaxAttempts( - ModelOptionsUtils.mergeOption( - runtimeOptions.getInternalToolExecutionMaxAttempts(), - this.defaultOptions.getInternalToolExecutionMaxAttempts()) - ) + .internalToolExecutionMaxAttempts( + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), + this.defaultOptions.getInternalToolExecutionMaxAttempts())) .build(); } @@ -682,8 +680,8 @@ private Flux internalStream(Prompt prompt, ChatResponse perviousCh Flux chatResponseFlux = chatResponses.switchMap(chatResponse -> { - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse, attempts) - && chatResponse.hasFinishReasons(Set.of(StopReason.TOOL_USE.toString()))) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse, + attempts) && chatResponse.hasFinishReasons(Set.of(StopReason.TOOL_USE.toString()))) { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous @@ -701,8 +699,7 @@ private Flux internalStream(Prompt prompt, ChatResponse perviousCh // Send the tool execution result back to the model. return this.internalStream( new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - chatResponse, - attempts + 1); + chatResponse, attempts + 1); } }).subscribeOn(Schedulers.boundedElastic()); } diff --git a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java index c750e31df84..e8de1979ff6 100644 --- a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java +++ b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java @@ -399,10 +399,8 @@ Prompt buildRequestPrompt(Prompt prompt) { ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); requestOptions.setInternalToolExecutionMaxAttempts( - ModelOptionsUtils.mergeOption( - runtimeOptions.getInternalToolExecutionMaxAttempts(), - this.defaultOptions.getInternalToolExecutionMaxAttempts()) - ); + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), + this.defaultOptions.getInternalToolExecutionMaxAttempts())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -412,7 +410,8 @@ Prompt buildRequestPrompt(Prompt prompt) { } else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); - requestOptions.setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); + requestOptions + .setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); diff --git a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatModel.java b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatModel.java index c23de95ae3d..e70041fbed3 100644 --- a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatModel.java +++ b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatModel.java @@ -245,34 +245,34 @@ private ChatResponse internalCall(Prompt requestPrompt, int attempts) { ChatCompletionRequest request = createRequest(requestPrompt, false); ChatModelObservationContext observationContext = ChatModelObservationContext.builder() - .prompt(requestPrompt) - .provider(MiniMaxApiConstants.PROVIDER_NAME) - .build(); + .prompt(requestPrompt) + .provider(MiniMaxApiConstants.PROVIDER_NAME) + .build(); ChatResponse response = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION - .observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, - this.observationRegistry) - .observe(() -> { + .observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, + this.observationRegistry) + .observe(() -> { - ResponseEntity completionEntity = this.retryTemplate - .execute(ctx -> this.miniMaxApi.chatCompletionEntity(request)); + ResponseEntity completionEntity = this.retryTemplate + .execute(ctx -> this.miniMaxApi.chatCompletionEntity(request)); - var chatCompletion = completionEntity.getBody(); + var chatCompletion = completionEntity.getBody(); - if (chatCompletion == null) { - logger.warn("No chat completion returned for prompt: {}", requestPrompt); - return new ChatResponse(List.of()); - } + if (chatCompletion == null) { + logger.warn("No chat completion returned for prompt: {}", requestPrompt); + return new ChatResponse(List.of()); + } - List choices = chatCompletion.choices(); - if (choices == null) { - logger.warn("No choices returned for prompt: {}, because: {}}", requestPrompt, - chatCompletion.baseResponse().message()); - return new ChatResponse(List.of()); - } + List choices = chatCompletion.choices(); + if (choices == null) { + logger.warn("No choices returned for prompt: {}, because: {}}", requestPrompt, + chatCompletion.baseResponse().message()); + return new ChatResponse(List.of()); + } - List generations = choices.stream().map(choice -> { - // @formatter:off + List generations = choices.stream().map(choice -> { + // @formatter:off // if the choice is a web search tool call, return last message of choice.messages ChatCompletionMessage message = null; if (choice.message() != null) { @@ -288,28 +288,31 @@ else if (!CollectionUtils.isEmpty(choice.messages())) { "role", message != null && message.role() != null ? message.role().name() : "", "finishReason", choice.finishReason() != null ? choice.finishReason().name() : ""); // @formatter:on - return buildGeneration(message, choice.finishReason(), metadata); - }).toList(); + return buildGeneration(message, choice.finishReason(), metadata); + }).toList(); - ChatResponse chatResponse = new ChatResponse(generations, from(completionEntity.getBody())); + ChatResponse chatResponse = new ChatResponse(generations, from(completionEntity.getBody())); - observationContext.setResponse(chatResponse); + observationContext.setResponse(chatResponse); - return chatResponse; - }); + return chatResponse; + }); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(requestPrompt.getOptions(), response, attempts)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(requestPrompt.getOptions(), response, + attempts)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(requestPrompt, response); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. return ChatResponse.builder() - .from(response) - .generations(ToolExecutionResult.buildGenerations(toolExecutionResult)) - .build(); + .from(response) + .generations(ToolExecutionResult.buildGenerations(toolExecutionResult)) + .build(); } else { // Send the tool execution result back to the model. - return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), requestPrompt.getOptions()), attempts + 1); + return this.internalCall( + new Prompt(toolExecutionResult.conversationHistory(), requestPrompt.getOptions()), + attempts + 1); } } @@ -334,16 +337,16 @@ private Flux internalStream(Prompt requestPrompt, int attempts) { ChatCompletionRequest request = createRequest(requestPrompt, true); Flux completionChunks = this.retryTemplate - .execute(ctx -> this.miniMaxApi.chatCompletionStream(request)); + .execute(ctx -> this.miniMaxApi.chatCompletionStream(request)); // For chunked responses, only the first chunk contains the choice role. // The rest of the chunks with same ID share the same role. ConcurrentHashMap roleMap = new ConcurrentHashMap<>(); final ChatModelObservationContext observationContext = ChatModelObservationContext.builder() - .prompt(requestPrompt) - .provider(MiniMaxApiConstants.PROVIDER_NAME) - .build(); + .prompt(requestPrompt) + .provider(MiniMaxApiConstants.PROVIDER_NAME) + .build(); Observation observation = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION.observation( this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, @@ -354,12 +357,12 @@ private Flux internalStream(Prompt requestPrompt, int attempts) { // Convert the ChatCompletionChunk into a ChatCompletion to be able to reuse // the function call handling logic. Flux chatResponse = completionChunks.map(this::chunkToChatCompletion) - .switchMap(chatCompletion -> Mono.just(chatCompletion).map(chatCompletion2 -> { - try { - @SuppressWarnings("null") - String id = chatCompletion2.id(); + .switchMap(chatCompletion -> Mono.just(chatCompletion).map(chatCompletion2 -> { + try { + @SuppressWarnings("null") + String id = chatCompletion2.id(); - // @formatter:off + // @formatter:off List generations = chatCompletion2.choices().stream().map(choice -> { if (choice.message().role() != null) { roleMap.putIfAbsent(id, choice.message().role().name()); @@ -483,8 +486,8 @@ Prompt buildRequestPrompt(Prompt prompt) { ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); requestOptions.setInternalToolExecutionMaxAttempts( - ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), - this.defaultOptions.getInternalToolExecutionMaxAttempts())); + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), + this.defaultOptions.getInternalToolExecutionMaxAttempts())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -494,7 +497,8 @@ Prompt buildRequestPrompt(Prompt prompt) { } else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); - requestOptions.setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); + requestOptions + .setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java index 6dbdf1ab3e7..1a1b21c7835 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java @@ -85,7 +85,6 @@ * @author Alexandros Pappas * @author lambochen * @since 1.0.0 - * * @see ToolCallingChatOptions */ public class MistralAiChatModel implements ChatModel { @@ -398,11 +397,8 @@ Prompt buildRequestPrompt(Prompt prompt) { ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); requestOptions.setInternalToolExecutionMaxAttempts( - ModelOptionsUtils.mergeOption( - runtimeOptions.getInternalToolExecutionMaxAttempts(), - this.defaultOptions.getInternalToolExecutionMaxAttempts() - ) - ); + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), + this.defaultOptions.getInternalToolExecutionMaxAttempts())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -412,7 +408,8 @@ Prompt buildRequestPrompt(Prompt prompt) { } else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); - requestOptions.setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); + requestOptions + .setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); diff --git a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java index 33a0ef0a1aa..eea9d27d479 100644 --- a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java +++ b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java @@ -86,7 +86,6 @@ * @author Ilayaperumal Gopinathan * @author lambochen * @since 1.0.0 - * * @see ToolCallingChatOptions */ public class OllamaChatModel implements ChatModel { @@ -394,10 +393,8 @@ Prompt buildRequestPrompt(Prompt prompt) { ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); requestOptions.setInternalToolExecutionMaxAttempts( - ModelOptionsUtils.mergeOption( - runtimeOptions.getInternalToolExecutionMaxAttempts(), - this.defaultOptions.getInternalToolExecutionMaxAttempts()) - ); + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), + this.defaultOptions.getInternalToolExecutionMaxAttempts())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -407,7 +404,8 @@ Prompt buildRequestPrompt(Prompt prompt) { } else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); - requestOptions.setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); + requestOptions + .setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); diff --git a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java index afe8fd3f671..3bac3070ac7 100644 --- a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java +++ b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java @@ -472,10 +472,8 @@ Prompt buildRequestPrompt(Prompt prompt) { ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); requestOptions.setInternalToolExecutionMaxAttempts( - ModelOptionsUtils.mergeOption( - runtimeOptions.getInternalToolExecutionMaxAttempts(), - this.defaultOptions.getInternalToolExecutionMaxAttempts()) - ); + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), + this.defaultOptions.getInternalToolExecutionMaxAttempts())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -490,7 +488,8 @@ Prompt buildRequestPrompt(Prompt prompt) { } else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); - requestOptions.setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); + requestOptions + .setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); diff --git a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatModel.java b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatModel.java index 408666fdc34..fef9458808f 100644 --- a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatModel.java +++ b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatModel.java @@ -82,9 +82,11 @@ * @author Geng Rong * @author Alexandros Pappas * @author Ilayaperumal Gopinathan + * @author lambochen * @see ChatModel * @see StreamingChatModel * @see ZhiPuAiApi + * @see ToolCallingChatOptions * @since 1.0.0 M1 */ public class ZhiPuAiChatModel implements ChatModel { @@ -237,6 +239,10 @@ public ChatResponse call(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); + return internalCall(requestPrompt, 1); + } + + private ChatResponse internalCall(Prompt requestPrompt, int attempts) { ChatCompletionRequest request = createRequest(requestPrompt, false); ChatModelObservationContext observationContext = ChatModelObservationContext.builder() @@ -255,7 +261,7 @@ public ChatResponse call(Prompt prompt) { var chatCompletion = completionEntity.getBody(); if (chatCompletion == null) { - logger.warn("No chat completion returned for prompt: {}", prompt); + logger.warn("No chat completion returned for prompt: {}", requestPrompt); return new ChatResponse(List.of()); } @@ -263,12 +269,12 @@ public ChatResponse call(Prompt prompt) { List generations = choices.stream().map(choice -> { // @formatter:off - Map metadata = Map.of( - "id", chatCompletion.id(), - "role", choice.message().role() != null ? choice.message().role().name() : "", - "finishReason", choice.finishReason() != null ? choice.finishReason().name() : "" - ); - // @formatter:on + Map metadata = Map.of( + "id", chatCompletion.id(), + "role", choice.message().role() != null ? choice.message().role().name() : "", + "finishReason", choice.finishReason() != null ? choice.finishReason().name() : "" + ); + // @formatter:on return buildGeneration(choice, metadata); }).toList(); @@ -278,7 +284,8 @@ public ChatResponse call(Prompt prompt) { return chatResponse; }); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(requestPrompt.getOptions(), response)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(requestPrompt.getOptions(), response, + attempts)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(requestPrompt, response); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. @@ -289,7 +296,9 @@ public ChatResponse call(Prompt prompt) { } else { // Send the tool execution result back to the model. - return this.call(new Prompt(toolExecutionResult.conversationHistory(), requestPrompt.getOptions())); + return this.internalCall( + new Prompt(toolExecutionResult.conversationHistory(), requestPrompt.getOptions()), + attempts + 1); } } return response; @@ -302,6 +311,10 @@ public ChatOptions getDefaultOptions() { @Override public Flux stream(Prompt prompt) { + return internalStream(prompt, 1); + } + + private Flux internalStream(Prompt prompt, int attempts) { return Flux.deferContextual(contextView -> { // Before moving any further, build the final request Prompt, // merging runtime and default options. @@ -332,18 +345,18 @@ public Flux stream(Prompt prompt) { String id = chatCompletion2.id(); // @formatter:off - List generations = chatCompletion2.choices().stream().map(choice -> { - if (choice.message().role() != null) { - roleMap.putIfAbsent(id, choice.message().role().name()); - } - Map metadata = Map.of( - "id", chatCompletion2.id(), - "role", roleMap.getOrDefault(id, ""), - "finishReason", choice.finishReason() != null ? choice.finishReason().name() : "" - ); - return buildGeneration(choice, metadata); - }).toList(); - // @formatter:on + List generations = chatCompletion2.choices().stream().map(choice -> { + if (choice.message().role() != null) { + roleMap.putIfAbsent(id, choice.message().role().name()); + } + Map metadata = Map.of( + "id", chatCompletion2.id(), + "role", roleMap.getOrDefault(id, ""), + "finishReason", choice.finishReason() != null ? choice.finishReason().name() : "" + ); + return buildGeneration(choice, metadata); + }).toList(); + // @formatter:on return new ChatResponse(generations, from(chatCompletion2)); } @@ -356,7 +369,7 @@ public Flux stream(Prompt prompt) { // @formatter:off Flux flux = chatResponse.flatMap(response -> { - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(requestPrompt.getOptions(), response)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(requestPrompt.getOptions(), response, attempts)) { return Flux.defer(() -> { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous @@ -369,15 +382,17 @@ public Flux stream(Prompt prompt) { } else { // Send the tool execution result back to the model. - return this.stream(new Prompt(toolExecutionResult.conversationHistory(), requestPrompt.getOptions())); + return this.internalStream( + new Prompt(toolExecutionResult.conversationHistory(), requestPrompt.getOptions()), + attempts + 1); } }).subscribeOn(Schedulers.boundedElastic()); } return Flux.just(response); - }) - .doOnError(observation::error) - .doFinally(s -> observation.stop()) - .contextWrite(ctx -> ctx.put(ObservationThreadLocalAccessor.KEY, observation)); + }) + .doOnError(observation::error) + .doFinally(s -> observation.stop()) + .contextWrite(ctx -> ctx.put(ObservationThreadLocalAccessor.KEY, observation)); // @formatter:on return new MessageAggregator().aggregate(flux, observationContext::setResponse); @@ -449,6 +464,9 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setInternalToolExecutionEnabled( ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); + requestOptions.setInternalToolExecutionMaxAttempts( + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), + this.defaultOptions.getInternalToolExecutionMaxAttempts())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -458,6 +476,8 @@ Prompt buildRequestPrompt(Prompt prompt) { } else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); + requestOptions + .setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); diff --git a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java index 1812f4e5d22..cb15f4bb52a 100644 --- a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java +++ b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java @@ -152,7 +152,7 @@ public static ZhiPuAiChatOptions fromOptions(ZhiPuAiChatOptions fromOptions) { .toolCallbacks(fromOptions.getToolCallbacks()) .toolNames(fromOptions.getToolNames()) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) - .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) + .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) .toolContext(fromOptions.getToolContext()) .build(); } From 0ec9490737031c834a16500752d26673ce468f5d Mon Sep 17 00:00:00 2001 From: lambochen Date: Fri, 30 May 2025 01:35:46 +0800 Subject: [PATCH 16/25] fix: api compatability Signed-off-by: lambochen --- .../ai/anthropic/AnthropicChatModel.java | 12 ++++++++++-- .../ai/azure/openai/AzureOpenAiChatModel.java | 13 +++++++++++-- .../ai/bedrock/converse/BedrockProxyChatModel.java | 12 ++++++++++-- .../ai/deepseek/DeepSeekChatModel.java | 12 ++++++++++-- .../ai/mistralai/MistralAiChatModel.java | 12 ++++++++++-- .../springframework/ai/openai/OpenAiChatModel.java | 12 ++++++++++-- .../ai/vertexai/gemini/VertexAiGeminiChatModel.java | 12 ++++++++++-- 7 files changed, 71 insertions(+), 14 deletions(-) diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java index f55b18a2b83..02cb03ade87 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java @@ -171,7 +171,11 @@ public ChatResponse call(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalCall(requestPrompt, null, 1); + return this.internalCall(requestPrompt, null); + } + + public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) { + return this.internalCall(prompt, previousChatResponse, 1); } public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { @@ -233,7 +237,11 @@ public Flux stream(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalStream(requestPrompt, null, 1); + return this.internalStream(requestPrompt, null); + } + + public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse) { + return this.internalStream(prompt, previousChatResponse, 1); } public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { diff --git a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java index 83671201ed4..f2e8d99e97a 100644 --- a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java +++ b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java @@ -125,6 +125,7 @@ * @author lambochen * @see ChatModel * @see com.azure.ai.openai.OpenAIClient + * @see ToolCallingChatOptions * @since 1.0.0 */ public class AzureOpenAiChatModel implements ChatModel { @@ -248,7 +249,11 @@ public ChatResponse call(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalCall(requestPrompt, null, 1); + return this.internalCall(requestPrompt, null); + } + + public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) { + return internalCall(prompt, previousChatResponse, 1); } public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { @@ -295,7 +300,11 @@ public Flux stream(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalStream(requestPrompt, null, 1); + return this.internalStream(requestPrompt, null); + } + + public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse) { + return this.internalStream(prompt, previousChatResponse, 1); } public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { diff --git a/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java b/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java index 36514b7cdf2..317c83a7164 100644 --- a/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java +++ b/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java @@ -214,7 +214,11 @@ private static ToolCallingChatOptions from(ChatOptions options) { @Override public ChatResponse call(Prompt prompt) { Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalCall(requestPrompt, null, 1); + return this.internalCall(requestPrompt, null); + } + + private ChatResponse internalCall(Prompt prompt, ChatResponse perviousChatResponse) { + return this.internalCall(prompt, perviousChatResponse, 1); } private ChatResponse internalCall(Prompt prompt, ChatResponse perviousChatResponse, int attempts) { @@ -644,7 +648,11 @@ private ChatResponse toChatResponse(ConverseResponse response, ChatResponse perv @Override public Flux stream(Prompt prompt) { Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalStream(requestPrompt, null, 1); + return this.internalStream(requestPrompt, null); + } + + private Flux internalStream(Prompt prompt, ChatResponse perviousChatResponse) { + return this.internalStream(prompt, perviousChatResponse, 1); } private Flux internalStream(Prompt prompt, ChatResponse perviousChatResponse, int attempts) { diff --git a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java index e8de1979ff6..e0f91bc03e3 100644 --- a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java +++ b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java @@ -148,7 +148,11 @@ public DeepSeekChatModel(DeepSeekApi deepSeekApi, DeepSeekChatOptions defaultOpt @Override public ChatResponse call(Prompt prompt) { Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalCall(requestPrompt, null, 1); + return this.internalCall(requestPrompt, null); + } + + public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) { + return internalCall(prompt, previousChatResponse, 1); } public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { @@ -228,7 +232,11 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons @Override public Flux stream(Prompt prompt) { Prompt requestPrompt = buildRequestPrompt(prompt); - return internalStream(requestPrompt, null, 1); + return internalStream(requestPrompt, null); + } + + public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse) { + return internalStream(prompt, previousChatResponse, 1); } public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java index 1a1b21c7835..f9a2c224f58 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java @@ -179,7 +179,11 @@ public ChatResponse call(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalCall(requestPrompt, null, 1); + return this.internalCall(requestPrompt, null); + } + + public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) { + return internalCall(prompt, previousChatResponse, 1); } public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { @@ -251,7 +255,11 @@ public Flux stream(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalStream(requestPrompt, null, 1); + return this.internalStream(requestPrompt, null); + } + + public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse) { + return internalStream(prompt, previousChatResponse, 1); } public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java index 5e6f8ddf1af..fa2ba0bf4e6 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java @@ -180,7 +180,11 @@ public ChatResponse call(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); - return this.internalCall(requestPrompt, null, 1); + return this.internalCall(requestPrompt, null); + } + + public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) { + return internalCall(prompt, previousChatResponse, 1); } public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { @@ -266,7 +270,11 @@ public Flux stream(Prompt prompt) { // Before moving any further, build the final request Prompt, // merging runtime and default options. Prompt requestPrompt = buildRequestPrompt(prompt); - return internalStream(requestPrompt, null, 1); + return internalStream(requestPrompt, null); + } + + public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse) { + return internalStream(prompt, previousChatResponse, 1); } public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { diff --git a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java index 3bac3070ac7..146f2795971 100644 --- a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java +++ b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java @@ -391,7 +391,11 @@ private static Schema jsonToSchema(String json) { @Override public ChatResponse call(Prompt prompt) { var requestPrompt = this.buildRequestPrompt(prompt); - return this.internalCall(requestPrompt, null, 1); + return this.internalCall(requestPrompt, null); + } + + private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) { + return this.internalCall(prompt, previousChatResponse, 1); } private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { @@ -506,7 +510,11 @@ Prompt buildRequestPrompt(Prompt prompt) { @Override public Flux stream(Prompt prompt) { var requestPrompt = this.buildRequestPrompt(prompt); - return this.internalStream(requestPrompt, null, 1); + return this.internalStream(requestPrompt, null); + } + + public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse) { + return this.internalStream(prompt, previousChatResponse, 1); } public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { From 8aaf132cc430830faff7fdd20ad1a4ae91d5b74f Mon Sep 17 00:00:00 2001 From: lambochen Date: Fri, 30 May 2025 18:43:12 +0800 Subject: [PATCH 17/25] UT for IsToolExecutionRequiredWithAttempts Signed-off-by: lambochen --- .../tool/ToolExecutionEligibilityPredicate.java | 9 +++++++++ .../ToolExecutionEligibilityPredicateTests.java | 14 ++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java index 20450e6611c..2c97a554322 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java @@ -43,6 +43,15 @@ default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse return test(promptOptions, chatResponse); } + /** + * Determines if tool execution should be performed based on the prompt options and chat response and the number of attempts. + * @param promptOptions The options from the prompt + * @param chatResponse The response from the chat model + * @param attempts The number of attempts + * @return true if tool execution should be performed, false otherwise + * @see ToolCallingChatOptions#getInternalToolExecutionMaxAttempts() + * @see #isToolExecutionRequired(ChatOptions, ChatResponse) + */ default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse chatResponse, int attempts) { boolean isToolExecutionRequired = isToolExecutionRequired(promptOptions, chatResponse); if (!isToolExecutionRequired) { diff --git a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicateTests.java b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicateTests.java index d347f9190f1..4f40d6aa732 100644 --- a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicateTests.java +++ b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicateTests.java @@ -45,6 +45,20 @@ void whenIsToolExecutionRequiredWithNullPromptOptions() { .hasMessageContaining("promptOptions cannot be null"); } + @Test + void whenIsToolExecutionRequiredWithAttempts() { + ToolExecutionEligibilityPredicate predicate = new TestToolExecutionEligibilityPredicate(); + ToolCallingChatOptions promptOptions = ToolCallingChatOptions.builder().build(); + ChatResponse chatResponse = new ChatResponse(List.of(new Generation(new AssistantMessage("test")))); + promptOptions.setInternalToolExecutionMaxAttempts(2); + + assertThat(predicate.isToolExecutionRequired(promptOptions, chatResponse, 1)).isTrue(); + assertThat(predicate.isToolExecutionRequired(promptOptions, chatResponse, 2)).isTrue(); + + // attempts value is oversize + assertThat(predicate.isToolExecutionRequired(promptOptions, chatResponse, 3)).isFalse(); + } + @Test void whenIsToolExecutionRequiredWithNullChatResponse() { ToolExecutionEligibilityPredicate predicate = new TestToolExecutionEligibilityPredicate(); From 4269f3720fc33b8917211be23aa8be0990f5d162 Mon Sep 17 00:00:00 2001 From: lambochen Date: Fri, 30 May 2025 23:36:11 +0800 Subject: [PATCH 18/25] UT for ToolExecutionEligibilityChecker attempts Signed-off-by: lambochen --- .../ToolExecutionEligibilityCheckerTest.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityCheckerTest.java diff --git a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityCheckerTest.java b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityCheckerTest.java new file mode 100644 index 00000000000..1242d155570 --- /dev/null +++ b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityCheckerTest.java @@ -0,0 +1,53 @@ +package org.springframework.ai.model.tool; + +import org.junit.jupiter.api.Test; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.model.Generation; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class ToolExecutionEligibilityCheckerTest { + + @Test + void isToolExecutionRequired() { + ToolExecutionEligibilityChecker checker = new TestToolExecutionEligibilityChecker(); + + ToolCallingChatOptions promptOptions = ToolCallingChatOptions.builder().build(); + ChatResponse chatResponse = new ChatResponse(List.of(new Generation(new AssistantMessage("test")))); + promptOptions.setInternalToolExecutionMaxAttempts(2); + + assertThat(checker.isToolExecutionRequired(promptOptions, chatResponse, 1)).isTrue(); + assertThat(checker.isToolExecutionRequired(promptOptions, chatResponse, 2)).isTrue(); + + // attempts value is oversize + assertThat(checker.isToolExecutionRequired(promptOptions, chatResponse, 3)).isFalse(); + } + + @Test + void isInternalToolExecutionEnabled() { + + ToolExecutionEligibilityChecker checker = new TestToolExecutionEligibilityChecker(); + + ToolCallingChatOptions promptOptions = ToolCallingChatOptions.builder().build(); + promptOptions.setInternalToolExecutionMaxAttempts(2); + + assertThat(checker.isInternalToolExecutionEnabled(promptOptions, 1)).isTrue(); + assertThat(checker.isInternalToolExecutionEnabled(promptOptions, 2)).isTrue(); + + // attempts value is oversize + assertThat(checker.isInternalToolExecutionEnabled(promptOptions, 3)).isFalse(); + + } + + class TestToolExecutionEligibilityChecker implements ToolExecutionEligibilityChecker { + + @Override + public Boolean apply(ChatResponse chatResponse) { + return true; + } + } +} From fcf1a76133b37eb993ed2addc1a3562dea5d32f0 Mon Sep 17 00:00:00 2001 From: lambochen Date: Sat, 31 May 2025 16:51:53 +0800 Subject: [PATCH 19/25] UT config for openai Signed-off-by: lambochen --- .../ai/openai/OpenAiTestConfiguration.java | 50 +++++++++++++++++-- .../ToolExecutionEligibilityPredicate.java | 3 +- .../ToolExecutionEligibilityCheckerTest.java | 2 + 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiTestConfiguration.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiTestConfiguration.java index e7401d9d81b..e10dfdd2307 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiTestConfiguration.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiTestConfiguration.java @@ -32,22 +32,48 @@ public class OpenAiTestConfiguration { @Bean public OpenAiApi openAiApi() { - return OpenAiApi.builder().apiKey(getApiKey()).build(); + var builder = OpenAiApi.builder().apiKey(getApiKey()); + + String baseUrl = getBaseUrl(); + if (StringUtils.hasText(baseUrl)) { + builder.baseUrl(baseUrl); + } + String completionsPath = getCompletionsPath(); + if (StringUtils.hasText(completionsPath)) { + builder.completionsPath(completionsPath); + } + + return builder.build(); } @Bean public OpenAiImageApi openAiImageApi() { - return OpenAiImageApi.builder().apiKey(getApiKey()).build(); + var builder = OpenAiImageApi.builder().apiKey(getApiKey()); + String baseUrl = getBaseUrl(); + if (StringUtils.hasText(baseUrl)) { + builder.baseUrl(baseUrl); + } + return builder.build(); } @Bean public OpenAiAudioApi openAiAudioApi() { - return OpenAiAudioApi.builder().apiKey(getApiKey()).build(); + var builder = OpenAiAudioApi.builder().apiKey(getApiKey()); + String baseUrl = getBaseUrl(); + if (StringUtils.hasText(baseUrl)) { + builder.baseUrl(baseUrl); + } + return builder.build(); } @Bean public OpenAiModerationApi openAiModerationApi() { - return OpenAiModerationApi.builder().apiKey(getApiKey()).build(); + var builder = OpenAiModerationApi.builder().apiKey(getApiKey()); + String baseUrl = getBaseUrl(); + if (StringUtils.hasText(baseUrl)) { + builder.baseUrl(baseUrl); + } + return builder.build(); } private ApiKey getApiKey() { @@ -59,6 +85,22 @@ private ApiKey getApiKey() { return new SimpleApiKey(apiKey); } + private String getBaseUrl() { + String baseUrl = System.getenv("OPENAI_BASE_URL"); + if (StringUtils.hasText(baseUrl)) { + return baseUrl; + } + return null; + } + + private String getCompletionsPath() { + String path = System.getenv("OPENAI_COMPLETIONS_PATH"); + if (StringUtils.hasText(path)) { + return path; + } + return null; + } + @Bean public OpenAiChatModel openAiChatModel(OpenAiApi api) { return OpenAiChatModel.builder() diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java index 2c97a554322..de9d96394a2 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java @@ -44,7 +44,8 @@ default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse } /** - * Determines if tool execution should be performed based on the prompt options and chat response and the number of attempts. + * Determines if tool execution should be performed based on the prompt options and + * chat response and the number of attempts. * @param promptOptions The options from the prompt * @param chatResponse The response from the chat model * @param attempts The number of attempts diff --git a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityCheckerTest.java b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityCheckerTest.java index 1242d155570..190d51a092a 100644 --- a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityCheckerTest.java +++ b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityCheckerTest.java @@ -49,5 +49,7 @@ class TestToolExecutionEligibilityChecker implements ToolExecutionEligibilityChe public Boolean apply(ChatResponse chatResponse) { return true; } + } + } From ae8d1abbc821b17ceb5e7b688e6aa5857b253dfd Mon Sep 17 00:00:00 2001 From: lambochen Date: Sat, 31 May 2025 20:09:24 +0800 Subject: [PATCH 20/25] any UT for internalTooolExecutionMaxAttempts Signed-off-by: lambochen --- .../ai/anthropic/AnthropicChatOptions.java | 12 +++++- .../anthropic/AnthropicChatOptionsTests.java | 6 +++ .../azure/openai/AzureOpenAiChatOptions.java | 5 ++- .../MistralAiChatCompletionRequestTest.java | 4 ++ .../ai/ollama/OllamaChatRequestTests.java | 3 ++ .../ai/openai/ChatCompletionRequestTests.java | 4 ++ .../ai/openai/OpenAiChatOptionsTests.java | 11 ++++- .../gemini/VertexAiGeminiChatOptions.java | 1 + .../gemini/VertexAiGeminiChatOptionsTest.java | 42 +++++++++++++++++++ .../tool/DefaultToolCallingChatOptions.java | 4 +- .../ai/model/tool/ToolCallingChatOptions.java | 15 +++++++ .../DefaultToolCallingChatOptionsTests.java | 12 ++++++ .../tool/ToolCallingChatOptionsTests.java | 15 +++++++ 13 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptionsTest.java diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java index 0596de620fd..c20cd6c80ae 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java @@ -113,6 +113,7 @@ public static AnthropicChatOptions fromOptions(AnthropicChatOptions fromOptions) fromOptions.getToolCallbacks() != null ? new ArrayList<>(fromOptions.getToolCallbacks()) : null) .toolNames(fromOptions.getToolNames() != null ? new HashSet<>(fromOptions.getToolNames()) : null) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) + .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) .toolContext(fromOptions.getToolContext() != null ? new HashMap<>(fromOptions.getToolContext()) : null) .httpHeaders(fromOptions.getHttpHeaders() != null ? new HashMap<>(fromOptions.getHttpHeaders()) : null) .build(); @@ -236,7 +237,7 @@ public Integer getInternalToolExecutionMaxAttempts() { } @Override - public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + public void setInternalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; } @@ -295,6 +296,7 @@ public boolean equals(Object o) { && Objects.equals(this.toolCallbacks, that.toolCallbacks) && Objects.equals(this.toolNames, that.toolNames) && Objects.equals(this.internalToolExecutionEnabled, that.internalToolExecutionEnabled) + && Objects.equals(this.internalToolExecutionMaxAttempts, that.internalToolExecutionMaxAttempts) && Objects.equals(this.toolContext, that.toolContext) && Objects.equals(this.httpHeaders, that.httpHeaders); } @@ -302,7 +304,8 @@ public boolean equals(Object o) { @Override public int hashCode() { return Objects.hash(this.model, this.maxTokens, this.metadata, this.stopSequences, this.temperature, this.topP, - this.topK, this.thinking, this.toolCallbacks, this.toolNames, this.internalToolExecutionEnabled, + this.topK, this.thinking, this.toolCallbacks, this.toolNames, + this.internalToolExecutionEnabled, this.internalToolExecutionMaxAttempts, this.toolContext, this.httpHeaders); } @@ -388,6 +391,11 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } + public Builder internalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { + this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); + return this; + } + public Builder toolContext(Map toolContext) { if (this.options.toolContext == null) { this.options.toolContext = toolContext; diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java index 62d97b459e4..24506652e6e 100644 --- a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java +++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test; import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionRequest.Metadata; +import org.springframework.ai.model.tool.ToolCallingChatOptions; import static org.assertj.core.api.Assertions.assertThat; @@ -29,6 +30,7 @@ * Tests for {@link AnthropicChatOptions}. * * @author Alexandros Pappas + * @author lambochen */ class AnthropicChatOptionsTests { @@ -59,6 +61,7 @@ void testCopy() { .topK(50) .metadata(new Metadata("userId_123")) .toolContext(Map.of("key1", "value1")) + .internalToolExecutionMaxAttempts(3) .build(); AnthropicChatOptions copied = original.copy(); @@ -67,6 +70,8 @@ void testCopy() { // Ensure deep copy assertThat(copied.getStopSequences()).isNotSameAs(original.getStopSequences()); assertThat(copied.getToolContext()).isNotSameAs(original.getToolContext()); + + assertThat(copied.getInternalToolExecutionMaxAttempts()).isEqualTo(3); } @Test @@ -99,6 +104,7 @@ void testDefaultValues() { assertThat(options.getTopP()).isNull(); assertThat(options.getStopSequences()).isNull(); assertThat(options.getMetadata()).isNull(); + assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); } } diff --git a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java index d077245cd3d..0a7b921d35b 100644 --- a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java +++ b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java @@ -298,6 +298,7 @@ public static AzureOpenAiChatOptions fromOptions(AzureOpenAiChatOptions fromOpti .enhancements(fromOptions.getEnhancements()) .toolContext(fromOptions.getToolContext() != null ? new HashMap<>(fromOptions.getToolContext()) : null) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) + .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) .streamOptions(fromOptions.getStreamOptions()) .toolCallbacks( fromOptions.getToolCallbacks() != null ? new ArrayList<>(fromOptions.getToolCallbacks()) : null) @@ -679,8 +680,8 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } - public Builder internalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { - this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxIterations); + public Builder internalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { + this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); return this; } diff --git a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatCompletionRequestTest.java b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatCompletionRequestTest.java index e6bf2490cc0..e37fa4b152b 100644 --- a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatCompletionRequestTest.java +++ b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatCompletionRequestTest.java @@ -35,6 +35,7 @@ * @author Ricken Bazolo * @author Alexandros Pappas * @author Thomas Vitale + * @author lambochen * @since 0.8.1 */ @SpringBootTest(classes = MistralAiTestConfiguration.class) @@ -73,6 +74,7 @@ void whenToolRuntimeOptionsThenMergeWithDefaults() { MistralAiChatOptions defaultOptions = MistralAiChatOptions.builder() .model("DEFAULT_MODEL") .internalToolExecutionEnabled(true) + .internalToolExecutionMaxAttempts(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS) .toolCallbacks(new TestToolCallback("tool1"), new TestToolCallback("tool2")) .toolNames("tool1", "tool2") .toolContext(Map.of("key1", "value1", "key2", "valueA")) @@ -85,6 +87,7 @@ void whenToolRuntimeOptionsThenMergeWithDefaults() { MistralAiChatOptions runtimeOptions = MistralAiChatOptions.builder() .internalToolExecutionEnabled(false) + .internalToolExecutionMaxAttempts(3) .toolCallbacks(new TestToolCallback("tool3"), new TestToolCallback("tool4")) .toolNames("tool3") .toolContext(Map.of("key2", "valueB")) @@ -93,6 +96,7 @@ void whenToolRuntimeOptionsThenMergeWithDefaults() { assertThat(((ToolCallingChatOptions) prompt.getOptions())).isNotNull(); assertThat(((ToolCallingChatOptions) prompt.getOptions()).getInternalToolExecutionEnabled()).isFalse(); + assertThat(((ToolCallingChatOptions) prompt.getOptions()).getInternalToolExecutionMaxAttempts()).isEqualTo(3); assertThat(((ToolCallingChatOptions) prompt.getOptions()).getToolCallbacks()).hasSize(2); assertThat(((ToolCallingChatOptions) prompt.getOptions()).getToolCallbacks() .stream() diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatRequestTests.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatRequestTests.java index 59baa37bec2..04fd3c97d72 100644 --- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatRequestTests.java +++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatRequestTests.java @@ -47,6 +47,7 @@ void whenToolRuntimeOptionsThenMergeWithDefaults() { OllamaOptions defaultOptions = OllamaOptions.builder() .model("MODEL_NAME") .internalToolExecutionEnabled(true) + .internalToolExecutionMaxAttempts(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS) .toolCallbacks(new TestToolCallback("tool1"), new TestToolCallback("tool2")) .toolNames("tool1", "tool2") .toolContext(Map.of("key1", "value1", "key2", "valueA")) @@ -58,6 +59,7 @@ void whenToolRuntimeOptionsThenMergeWithDefaults() { OllamaOptions runtimeOptions = OllamaOptions.builder() .internalToolExecutionEnabled(false) + .internalToolExecutionMaxAttempts(3) .toolCallbacks(new TestToolCallback("tool3"), new TestToolCallback("tool4")) .toolNames("tool3") .toolContext(Map.of("key2", "valueB")) @@ -66,6 +68,7 @@ void whenToolRuntimeOptionsThenMergeWithDefaults() { assertThat(((ToolCallingChatOptions) prompt.getOptions())).isNotNull(); assertThat(((ToolCallingChatOptions) prompt.getOptions()).getInternalToolExecutionEnabled()).isFalse(); + assertThat(((ToolCallingChatOptions) prompt.getOptions()).getInternalToolExecutionMaxAttempts()).isEqualTo(3); assertThat(((ToolCallingChatOptions) prompt.getOptions()).getToolCallbacks()).hasSize(2); assertThat(((ToolCallingChatOptions) prompt.getOptions()).getToolCallbacks() .stream() diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/ChatCompletionRequestTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/ChatCompletionRequestTests.java index 3d7623c96f4..559e7f7e4fe 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/ChatCompletionRequestTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/ChatCompletionRequestTests.java @@ -36,6 +36,7 @@ /** * @author Christian Tzolov * @author Thomas Vitale + * @author lambochen */ class ChatCompletionRequestTests { @@ -44,6 +45,7 @@ void whenToolRuntimeOptionsThenMergeWithDefaults() { OpenAiChatOptions defaultOptions = OpenAiChatOptions.builder() .model("DEFAULT_MODEL") .internalToolExecutionEnabled(true) + .internalToolExecutionMaxAttempts(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS) .toolCallbacks(new TestToolCallback("tool1"), new TestToolCallback("tool2")) .toolNames("tool1", "tool2") .toolContext(Map.of("key1", "value1", "key2", "valueA")) @@ -56,6 +58,7 @@ void whenToolRuntimeOptionsThenMergeWithDefaults() { OpenAiChatOptions runtimeOptions = OpenAiChatOptions.builder() .internalToolExecutionEnabled(false) + .internalToolExecutionMaxAttempts(10) .toolCallbacks(new TestToolCallback("tool3"), new TestToolCallback("tool4")) .toolNames("tool3") .toolContext(Map.of("key2", "valueB")) @@ -64,6 +67,7 @@ void whenToolRuntimeOptionsThenMergeWithDefaults() { assertThat(((ToolCallingChatOptions) prompt.getOptions())).isNotNull(); assertThat(((ToolCallingChatOptions) prompt.getOptions()).getInternalToolExecutionEnabled()).isFalse(); + assertThat(((ToolCallingChatOptions) prompt.getOptions()).getInternalToolExecutionMaxAttempts()).isEqualTo(10); assertThat(((ToolCallingChatOptions) prompt.getOptions()).getToolCallbacks()).hasSize(2); assertThat(((ToolCallingChatOptions) prompt.getOptions()).getToolCallbacks() .stream() diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiChatOptionsTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiChatOptionsTests.java index d09808f1a31..a9582375af7 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiChatOptionsTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiChatOptionsTests.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test; +import org.springframework.ai.model.tool.ToolCallingChatOptions; import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.ai.openai.api.OpenAiApi.ChatCompletionRequest.AudioParameters; import org.springframework.ai.openai.api.OpenAiApi.ChatCompletionRequest.StreamOptions; @@ -80,6 +81,7 @@ void testBuilderWithAllFields() { .metadata(metadata) .reasoningEffort("medium") .internalToolExecutionEnabled(false) + .internalToolExecutionMaxAttempts(10) .httpHeaders(Map.of("header1", "value1")) .toolContext(toolContext) .build(); @@ -88,11 +90,12 @@ void testBuilderWithAllFields() { .extracting("model", "frequencyPenalty", "logitBias", "logprobs", "topLogprobs", "maxTokens", "maxCompletionTokens", "n", "outputModalities", "outputAudio", "presencePenalty", "responseFormat", "streamOptions", "seed", "stop", "temperature", "topP", "tools", "toolChoice", "user", - "parallelToolCalls", "store", "metadata", "reasoningEffort", "internalToolExecutionEnabled", + "parallelToolCalls", "store", "metadata", "reasoningEffort", + "internalToolExecutionEnabled", "internalToolExecutionMaxAttempts", "httpHeaders", "toolContext") .containsExactly("test-model", 0.5, logitBias, true, 5, 100, 50, 2, outputModalities, outputAudio, 0.8, responseFormat, streamOptions, 12345, stopSequences, 0.7, 0.9, tools, toolChoice, "test-user", true, - false, metadata, "medium", false, Map.of("header1", "value1"), toolContext); + false, metadata, "medium", false, 10, Map.of("header1", "value1"), toolContext); assertThat(options.getStreamUsage()).isTrue(); assertThat(options.getStreamOptions()).isEqualTo(StreamOptions.INCLUDE_USAGE); @@ -139,6 +142,7 @@ void testCopy() { .metadata(metadata) .reasoningEffort("low") .internalToolExecutionEnabled(true) + .internalToolExecutionMaxAttempts(3) .httpHeaders(Map.of("header1", "value1")) .build(); @@ -187,6 +191,7 @@ void testSetters() { options.setMetadata(metadata); options.setReasoningEffort("high"); options.setInternalToolExecutionEnabled(false); + options.setInternalToolExecutionMaxAttempts(3); options.setHttpHeaders(Map.of("header2", "value2")); assertThat(options.getModel()).isEqualTo("test-model"); @@ -214,6 +219,7 @@ void testSetters() { assertThat(options.getMetadata()).isEqualTo(metadata); assertThat(options.getReasoningEffort()).isEqualTo("high"); assertThat(options.getInternalToolExecutionEnabled()).isFalse(); + assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); assertThat(options.getHttpHeaders()).isEqualTo(Map.of("header2", "value2")); assertThat(options.getStreamUsage()).isTrue(); options.setStreamUsage(false); @@ -253,6 +259,7 @@ void testDefaultValues() { assertThat(options.getReasoningEffort()).isNull(); assertThat(options.getToolCallbacks()).isNotNull().isEmpty(); assertThat(options.getInternalToolExecutionEnabled()).isNull(); + assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); assertThat(options.getHttpHeaders()).isNotNull().isEmpty(); assertThat(options.getToolContext()).isEqualTo(new HashMap<>()); assertThat(options.getStreamUsage()).isFalse(); diff --git a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java index b2142a41d1e..0de968ffec0 100644 --- a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java +++ b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java @@ -165,6 +165,7 @@ public static VertexAiGeminiChatOptions fromOptions(VertexAiGeminiChatOptions fr options.setGoogleSearchRetrieval(fromOptions.getGoogleSearchRetrieval()); options.setSafetySettings(fromOptions.getSafetySettings()); options.setInternalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()); + options.setInternalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()); options.setToolContext(fromOptions.getToolContext()); return options; } diff --git a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptionsTest.java b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptionsTest.java new file mode 100644 index 00000000000..3b41a3005fb --- /dev/null +++ b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptionsTest.java @@ -0,0 +1,42 @@ +package org.springframework.ai.vertexai.gemini; + +import org.junit.jupiter.api.Test; +import org.springframework.ai.model.tool.ToolCallingChatOptions; + +import static org.junit.jupiter.api.Assertions.*; + +class VertexAiGeminiChatOptionsTest { + + @Test + void optionsDefault() { + var options = new VertexAiGeminiChatOptions(); + + assertEquals(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS, options.getInternalToolExecutionMaxAttempts()); + } + + @Test + void builderDefault() { + var options = VertexAiGeminiChatOptions.builder().build(); + + assertEquals(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS, options.getInternalToolExecutionMaxAttempts()); + } + + @Test + void testBuilder() { + var options = VertexAiGeminiChatOptions.builder() + .internalToolExecutionMaxAttempts(3) + .build(); + + assertEquals(3, options.getInternalToolExecutionMaxAttempts()); + } + + @Test + void fromOptions() { + var original = new VertexAiGeminiChatOptions(); + original.setInternalToolExecutionMaxAttempts(3); + + var copied = VertexAiGeminiChatOptions.fromOptions(original); + + assertEquals(original.getInternalToolExecutionMaxAttempts(), copied.getInternalToolExecutionMaxAttempts()); + } +} diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java index eea84ecdec1..c41e5528911 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java @@ -128,7 +128,7 @@ public Integer getInternalToolExecutionMaxAttempts() { } @Override - public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { + public void setInternalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; } @@ -294,7 +294,7 @@ public ToolCallingChatOptions.Builder internalToolExecutionEnabled( @Override public ToolCallingChatOptions.Builder internalToolExecutionMaxAttempts( - Integer internalToolExecutionMaxAttempts) { + @Nullable Integer internalToolExecutionMaxAttempts) { this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); return this; } diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java index 0302e4212c6..34d5a2b57a3 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java @@ -131,6 +131,21 @@ static boolean isInternalToolExecutionEnabled(ChatOptions chatOptions) { return internalToolExecutionEnabled; } + static boolean isInternalToolExecutionEnabled(ChatOptions chatOptions, int attempts) { + boolean isInternalToolExecutionEnabled = isInternalToolExecutionEnabled(chatOptions); + if (!isInternalToolExecutionEnabled) { + return false; + } + + if (chatOptions instanceof ToolCallingChatOptions toolCallingChatOptions + && toolCallingChatOptions.getInternalToolExecutionMaxAttempts() != null) { + int maxAttempts = toolCallingChatOptions.getInternalToolExecutionMaxAttempts(); + return attempts <= maxAttempts; + } + + return DEFAULT_TOOL_EXECUTION_ENABLED; + } + static Set mergeToolNames(Set runtimeToolNames, Set defaultToolNames) { Assert.notNull(runtimeToolNames, "runtimeToolNames cannot be null"); Assert.notNull(defaultToolNames, "defaultToolNames cannot be null"); diff --git a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptionsTests.java b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptionsTests.java index 45557f23a6d..d32e0824773 100644 --- a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptionsTests.java +++ b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptionsTests.java @@ -33,6 +33,7 @@ * Unit tests for {@link DefaultToolCallingChatOptions}. * * @author Thomas Vitale + * @author lambochen */ class DefaultToolCallingChatOptionsTests { @@ -140,6 +141,7 @@ void copyShouldCreateNewInstanceWithSameValues() { original.setToolNames(Set.of("tool1")); original.setToolContext(Map.of("key", "value")); original.setInternalToolExecutionEnabled(true); + original.setInternalToolExecutionMaxAttempts(ToolCallingChatOptions.TOOL_EXECUTION_NO_LIMIT); original.setModel("gpt-4"); original.setTemperature(0.7); @@ -150,6 +152,7 @@ void copyShouldCreateNewInstanceWithSameValues() { assertThat(c.getToolNames()).isEqualTo(original.getToolNames()); assertThat(c.getToolContext()).isEqualTo(original.getToolContext()); assertThat(c.getInternalToolExecutionEnabled()).isEqualTo(original.getInternalToolExecutionEnabled()); + assertThat(c.getInternalToolExecutionMaxAttempts()).isEqualTo(original.getInternalToolExecutionMaxAttempts()); assertThat(c.getModel()).isEqualTo(original.getModel()); assertThat(c.getTemperature()).isEqualTo(original.getTemperature()); }); @@ -180,6 +183,7 @@ void builderShouldCreateOptionsWithAllProperties() { .toolNames(Set.of("tool1")) .toolContext(context) .internalToolExecutionEnabled(true) + .internalToolExecutionMaxAttempts(3) .model("gpt-4") .temperature(0.7) .maxTokens(100) @@ -195,6 +199,7 @@ void builderShouldCreateOptionsWithAllProperties() { assertThat(o.getToolNames()).containsExactly("tool1"); assertThat(o.getToolContext()).isEqualTo(context); assertThat(o.getInternalToolExecutionEnabled()).isTrue(); + assertThat(o.getInternalToolExecutionMaxAttempts()).isEqualTo(3); assertThat(o.getModel()).isEqualTo("gpt-4"); assertThat(o.getTemperature()).isEqualTo(0.7); assertThat(o.getMaxTokens()).isEqualTo(100); @@ -233,6 +238,13 @@ void deprecatedMethodsShouldWorkCorrectly() { options.setInternalToolExecutionEnabled(true); assertThat(options.getInternalToolExecutionEnabled()).isTrue(); + + // default value check + assertThat(options.getInternalToolExecutionMaxAttempts()) + .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); + + options.setInternalToolExecutionMaxAttempts(3); + assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); } } diff --git a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolCallingChatOptionsTests.java b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolCallingChatOptionsTests.java index 6d5d599dccd..0f82700e516 100644 --- a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolCallingChatOptionsTests.java +++ b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolCallingChatOptionsTests.java @@ -33,6 +33,7 @@ * Unit tests for {@link ToolCallingChatOptions}. * * @author Thomas Vitale + * @author lambochen */ class ToolCallingChatOptionsTests { @@ -50,6 +51,20 @@ void whenToolCallingChatOptionsAndExecutionEnabledFalse() { assertThat(ToolCallingChatOptions.isInternalToolExecutionEnabled(options)).isFalse(); } + @Test + void whenToolCallingChatOptionsAndMaxAttemptsOver() { + ToolCallingChatOptions options = new DefaultToolCallingChatOptions(); + options.setInternalToolExecutionMaxAttempts(1); + // 3 > 1 + assertThat(ToolCallingChatOptions.isInternalToolExecutionEnabled(options, 3)).isFalse(); + } + + @Test + void whenToolCallingChatOptionsAndMaxAttemptsDefault() { + ToolCallingChatOptions options = new DefaultToolCallingChatOptions(); + assertThat(ToolCallingChatOptions.isInternalToolExecutionEnabled(options, 1)).isTrue(); + } + @Test void whenToolCallingChatOptionsAndExecutionEnabledDefault() { ToolCallingChatOptions options = new DefaultToolCallingChatOptions(); From 3b1162e5e9de55db4170d06fe71372245bad7e7e Mon Sep 17 00:00:00 2001 From: lambochen Date: Sat, 31 May 2025 21:08:12 +0800 Subject: [PATCH 21/25] add UT for internalToolCallingExecutionMaxAttempts in some options Signed-off-by: lambochen --- .../anthropic/AnthropicChatOptionsTests.java | 8 +++- .../openai/AzureOpenAiChatOptionsTests.java | 14 ++++++- .../ai/deepseek/DeepSeekChatOptions.java | 4 +- .../ai/deepseek/DeepSeekChatOptionsTest.java | 38 +++++++++++++++++++ .../minimax/chat/MiniMaxChatOptionsTests.java | 24 ++++++++++++ .../ai/mistralai/MistralAiChatOptions.java | 5 ++- .../mistralai/MistralAiChatOptionsTests.java | 37 ++++++++++++++++++ .../zhipuai/chat/ZhiPuAiChatOptionsTests.java | 36 ++++++++++++++++++ 8 files changed, 159 insertions(+), 7 deletions(-) create mode 100644 models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/DeepSeekChatOptionsTest.java create mode 100644 models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatOptionsTests.java create mode 100644 models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/chat/ZhiPuAiChatOptionsTests.java diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java index 24506652e6e..924e87d4d88 100644 --- a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java +++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java @@ -44,10 +44,12 @@ void testBuilderWithAllFields() { .topP(0.8) .topK(50) .metadata(new Metadata("userId_123")) + .internalToolExecutionMaxAttempts(3) .build(); - assertThat(options).extracting("model", "maxTokens", "stopSequences", "temperature", "topP", "topK", "metadata") - .containsExactly("test-model", 100, List.of("stop1", "stop2"), 0.7, 0.8, 50, new Metadata("userId_123")); + assertThat(options).extracting("model", "maxTokens", "stopSequences", "temperature", "topP", "topK", "metadata", + "internalToolExecutionMaxAttempts") + .containsExactly("test-model", 100, List.of("stop1", "stop2"), 0.7, 0.8, 50, new Metadata("userId_123"), 3); } @Test @@ -84,6 +86,7 @@ void testSetters() { options.setTopP(0.8); options.setStopSequences(List.of("stop1", "stop2")); options.setMetadata(new Metadata("userId_123")); + options.setInternalToolExecutionMaxAttempts(3); assertThat(options.getModel()).isEqualTo("test-model"); assertThat(options.getMaxTokens()).isEqualTo(100); @@ -92,6 +95,7 @@ void testSetters() { assertThat(options.getTopP()).isEqualTo(0.8); assertThat(options.getStopSequences()).isEqualTo(List.of("stop1", "stop2")); assertThat(options.getMetadata()).isEqualTo(new Metadata("userId_123")); + assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); } @Test diff --git a/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptionsTests.java b/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptionsTests.java index 789635d358e..d22b948cd0e 100644 --- a/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptionsTests.java +++ b/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptionsTests.java @@ -24,6 +24,7 @@ import com.azure.ai.openai.models.AzureChatOCREnhancementConfiguration; import com.azure.ai.openai.models.ChatCompletionStreamOptions; import org.junit.jupiter.api.Test; +import org.springframework.ai.model.tool.ToolCallingChatOptions; import static org.assertj.core.api.Assertions.assertThat; @@ -31,6 +32,7 @@ * Tests for {@link AzureOpenAiChatOptions}. * * @author Alexandros Pappas + * @author lambochen */ class AzureOpenAiChatOptionsTests { @@ -65,15 +67,17 @@ void testBuilderWithAllFields() { .topLogprobs(5) .enhancements(enhancements) .streamOptions(streamOptions) + .internalToolExecutionMaxAttempts(3) .build(); assertThat(options) .extracting("deploymentName", "frequencyPenalty", "logitBias", "maxTokens", "n", "presencePenalty", "stop", "temperature", "topP", "user", "responseFormat", "streamUsage", "reasoningEffort", "seed", - "logprobs", "topLogProbs", "enhancements", "streamOptions") + "logprobs", "topLogProbs", "enhancements", "streamOptions", + "internalToolExecutionMaxAttempts") .containsExactly("test-deployment", 0.5, Map.of("token1", 1, "token2", -1), 200, 2, 0.8, List.of("stop1", "stop2"), 0.7, 0.9, "test-user", responseFormat, true, "low", 12345L, true, 5, - enhancements, streamOptions); + enhancements, streamOptions, 3); } @Test @@ -107,6 +111,7 @@ void testCopy() { .topLogprobs(5) .enhancements(enhancements) .streamOptions(streamOptions) + .internalToolExecutionMaxAttempts(3) .build(); AzureOpenAiChatOptions copiedOptions = originalOptions.copy(); @@ -115,6 +120,8 @@ void testCopy() { // Ensure deep copy assertThat(copiedOptions.getStop()).isNotSameAs(originalOptions.getStop()); assertThat(copiedOptions.getToolContext()).isNotSameAs(originalOptions.getToolContext()); + + assertThat(copiedOptions.getInternalToolExecutionMaxAttempts()).isEqualTo(3); } @Test @@ -145,6 +152,7 @@ void testSetters() { options.setTopLogProbs(5); options.setEnhancements(enhancements); options.setStreamOptions(streamOptions); + options.setInternalToolExecutionMaxAttempts(3); assertThat(options.getDeploymentName()).isEqualTo("test-deployment"); options.setModel("test-model"); @@ -168,6 +176,7 @@ void testSetters() { assertThat(options.getEnhancements()).isEqualTo(enhancements); assertThat(options.getStreamOptions()).isEqualTo(streamOptions); assertThat(options.getModel()).isEqualTo("test-model"); + assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); } @Test @@ -193,6 +202,7 @@ void testDefaultValues() { assertThat(options.getEnhancements()).isNull(); assertThat(options.getStreamOptions()).isNull(); assertThat(options.getModel()).isNull(); + assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); } } diff --git a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java index e8916426dc0..5ad71c54c97 100644 --- a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java +++ b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java @@ -348,7 +348,9 @@ public int hashCode() { return Objects.hash(this.model, this.frequencyPenalty, this.logprobs, this.topLogprobs, this.maxTokens, this.presencePenalty, this.responseFormat, this.stop, this.temperature, this.topP, this.tools, this.toolChoice, - this.toolCallbacks, this.toolNames, this.internalToolExecutionEnabled, this.toolContext); + this.toolCallbacks, this.toolNames, + this.internalToolExecutionEnabled, this.internalToolExecutionMaxAttempts, + this.toolContext); } diff --git a/models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/DeepSeekChatOptionsTest.java b/models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/DeepSeekChatOptionsTest.java new file mode 100644 index 00000000000..9b405b47219 --- /dev/null +++ b/models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/DeepSeekChatOptionsTest.java @@ -0,0 +1,38 @@ +package org.springframework.ai.deepseek; + +import org.junit.jupiter.api.Test; +import org.springframework.ai.model.tool.ToolCallingChatOptions; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author lambochen + */ +class DeepSeekChatOptionsTest { + + @Test + void fromOptions() { + var original = new DeepSeekChatOptions(); + original.setInternalToolExecutionMaxAttempts(3); + + var copy = DeepSeekChatOptions.fromOptions(original); + assertNotSame(original, copy); + assertSame(original.getInternalToolExecutionMaxAttempts(), copy.getInternalToolExecutionMaxAttempts()); + } + + @Test + void optionsDefault() { + var options = new DeepSeekChatOptions(); + + assertEquals(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS, options.getInternalToolExecutionMaxAttempts()); + } + + @Test + void optionsBuilder() { + var options = DeepSeekChatOptions.builder() + .internalToolExecutionMaxAttempts(3) + .build(); + + assertEquals(3, options.getInternalToolExecutionMaxAttempts()); + } +} diff --git a/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/chat/MiniMaxChatOptionsTests.java b/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/chat/MiniMaxChatOptionsTests.java index fa3a0489409..9db34b46b83 100644 --- a/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/chat/MiniMaxChatOptionsTests.java +++ b/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/chat/MiniMaxChatOptionsTests.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.ai.model.tool.ToolCallingChatOptions; import reactor.core.publisher.Flux; import org.springframework.ai.chat.messages.AssistantMessage; @@ -116,4 +117,27 @@ void testToolCallingStream() { assertThat(content).contains("15"); } + @Test + void testOptionsDefaultValue() { + var options = new MiniMaxChatOptions(); + + assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); + } + + @Test + void testOptionsSetter() { + var options = new MiniMaxChatOptions(); + options.setInternalToolExecutionMaxAttempts(3); + assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); + } + + @Test + void testOptionsBuilder() { + var options = MiniMaxChatOptions.builder() + .internalToolExecutionMaxAttempts(3) + .build(); + + assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); + } + } diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java index 7a2cf9d2deb..8678e1189b2 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java @@ -389,8 +389,9 @@ public MistralAiChatOptions copy() { public int hashCode() { return Objects.hash(this.model, this.temperature, this.topP, this.maxTokens, this.safePrompt, this.randomSeed, this.responseFormat, this.stop, this.frequencyPenalty, this.presencePenalty, this.n, this.tools, - this.toolChoice, this.toolCallbacks, this.tools, this.internalToolExecutionEnabled, - this.internalToolExecutionMaxAttempts, this.toolContext); + this.toolChoice, this.toolCallbacks, this.tools, + this.internalToolExecutionEnabled, this.internalToolExecutionMaxAttempts, + this.toolContext); } @Override diff --git a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatOptionsTests.java b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatOptionsTests.java new file mode 100644 index 00000000000..2175f1e3a09 --- /dev/null +++ b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatOptionsTests.java @@ -0,0 +1,37 @@ +package org.springframework.ai.mistralai; + +import org.junit.jupiter.api.Test; +import org.springframework.ai.model.tool.ToolCallingChatOptions; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author lambochen + */ +class MistralAiChatOptionsTests { + + @Test + void testOptionsDefault() { + var options = new MistralAiChatOptions(); + + assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); + } + + @Test + void testOptionsCustom() { + var options = new MistralAiChatOptions(); + + options.setInternalToolExecutionMaxAttempts(3); + + assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); + } + + @Test + void testBuilder() { + var options = MistralAiChatOptions.builder() + .internalToolExecutionMaxAttempts(3) + .build(); + + assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); + } +} diff --git a/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/chat/ZhiPuAiChatOptionsTests.java b/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/chat/ZhiPuAiChatOptionsTests.java new file mode 100644 index 00000000000..b6bad99d464 --- /dev/null +++ b/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/chat/ZhiPuAiChatOptionsTests.java @@ -0,0 +1,36 @@ +package org.springframework.ai.zhipuai.chat; + +import org.junit.jupiter.api.Test; +import org.springframework.ai.model.tool.ToolCallingChatOptions; +import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +/** + * @author lambochen + */ +class ZhiPuAiChatOptionsTests { + + @Test + void testDefaultValue() { + var options = new ZhiPuAiChatOptions(); + + assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); + } + + @Test + void testSetter() { + var options = new ZhiPuAiChatOptions(); + options.setInternalToolExecutionMaxAttempts(3); + assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); + } + + @Test + void testBuilder() { + var options = ZhiPuAiChatOptions.builder() + .internalToolExecutionMaxAttempts(3) + .build(); + + assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); + } +} From e894e67ce73a192b44926bba6f63adcec6c035e9 Mon Sep 17 00:00:00 2001 From: lambochen Date: Sat, 31 May 2025 21:15:16 +0800 Subject: [PATCH 22/25] code format by spring-javaformat plugin Signed-off-by: lambochen --- .../ai/anthropic/AnthropicChatOptions.java | 5 ++--- .../ai/anthropic/AnthropicChatOptionsTests.java | 8 +++++--- .../ai/azure/openai/AzureOpenAiChatOptionsTests.java | 6 +++--- .../ai/deepseek/DeepSeekChatOptionsTest.java | 8 ++++---- .../ai/minimax/chat/MiniMaxChatOptionsTests.java | 7 +++---- .../ai/mistralai/MistralAiChatOptions.java | 5 ++--- .../ai/mistralai/MistralAiChatOptionsTests.java | 8 ++++---- .../ai/openai/OpenAiChatOptionsTests.java | 8 ++++---- .../gemini/VertexAiGeminiChatOptionsTest.java | 11 ++++++----- .../ai/zhipuai/chat/ZhiPuAiChatOptionsTests.java | 8 ++++---- .../tool/DefaultToolCallingChatOptionsTests.java | 5 +++-- 11 files changed, 40 insertions(+), 39 deletions(-) diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java index c20cd6c80ae..bd9baf2893f 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java @@ -304,9 +304,8 @@ public boolean equals(Object o) { @Override public int hashCode() { return Objects.hash(this.model, this.maxTokens, this.metadata, this.stopSequences, this.temperature, this.topP, - this.topK, this.thinking, this.toolCallbacks, this.toolNames, - this.internalToolExecutionEnabled, this.internalToolExecutionMaxAttempts, - this.toolContext, this.httpHeaders); + this.topK, this.thinking, this.toolCallbacks, this.toolNames, this.internalToolExecutionEnabled, + this.internalToolExecutionMaxAttempts, this.toolContext, this.httpHeaders); } public static class Builder { diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java index 924e87d4d88..400926e6b93 100644 --- a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java +++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java @@ -47,8 +47,9 @@ void testBuilderWithAllFields() { .internalToolExecutionMaxAttempts(3) .build(); - assertThat(options).extracting("model", "maxTokens", "stopSequences", "temperature", "topP", "topK", "metadata", - "internalToolExecutionMaxAttempts") + assertThat(options) + .extracting("model", "maxTokens", "stopSequences", "temperature", "topP", "topK", "metadata", + "internalToolExecutionMaxAttempts") .containsExactly("test-model", 100, List.of("stop1", "stop2"), 0.7, 0.8, 50, new Metadata("userId_123"), 3); } @@ -108,7 +109,8 @@ void testDefaultValues() { assertThat(options.getTopP()).isNull(); assertThat(options.getStopSequences()).isNull(); assertThat(options.getMetadata()).isNull(); - assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); + assertThat(options.getInternalToolExecutionMaxAttempts()) + .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); } } diff --git a/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptionsTests.java b/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptionsTests.java index d22b948cd0e..968d40ecf36 100644 --- a/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptionsTests.java +++ b/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptionsTests.java @@ -73,8 +73,7 @@ void testBuilderWithAllFields() { assertThat(options) .extracting("deploymentName", "frequencyPenalty", "logitBias", "maxTokens", "n", "presencePenalty", "stop", "temperature", "topP", "user", "responseFormat", "streamUsage", "reasoningEffort", "seed", - "logprobs", "topLogProbs", "enhancements", "streamOptions", - "internalToolExecutionMaxAttempts") + "logprobs", "topLogProbs", "enhancements", "streamOptions", "internalToolExecutionMaxAttempts") .containsExactly("test-deployment", 0.5, Map.of("token1", 1, "token2", -1), 200, 2, 0.8, List.of("stop1", "stop2"), 0.7, 0.9, "test-user", responseFormat, true, "low", 12345L, true, 5, enhancements, streamOptions, 3); @@ -202,7 +201,8 @@ void testDefaultValues() { assertThat(options.getEnhancements()).isNull(); assertThat(options.getStreamOptions()).isNull(); assertThat(options.getModel()).isNull(); - assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); + assertThat(options.getInternalToolExecutionMaxAttempts()) + .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); } } diff --git a/models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/DeepSeekChatOptionsTest.java b/models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/DeepSeekChatOptionsTest.java index 9b405b47219..f313f9f8ee2 100644 --- a/models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/DeepSeekChatOptionsTest.java +++ b/models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/DeepSeekChatOptionsTest.java @@ -24,15 +24,15 @@ void fromOptions() { void optionsDefault() { var options = new DeepSeekChatOptions(); - assertEquals(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS, options.getInternalToolExecutionMaxAttempts()); + assertEquals(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS, + options.getInternalToolExecutionMaxAttempts()); } @Test void optionsBuilder() { - var options = DeepSeekChatOptions.builder() - .internalToolExecutionMaxAttempts(3) - .build(); + var options = DeepSeekChatOptions.builder().internalToolExecutionMaxAttempts(3).build(); assertEquals(3, options.getInternalToolExecutionMaxAttempts()); } + } diff --git a/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/chat/MiniMaxChatOptionsTests.java b/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/chat/MiniMaxChatOptionsTests.java index 9db34b46b83..37487583521 100644 --- a/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/chat/MiniMaxChatOptionsTests.java +++ b/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/chat/MiniMaxChatOptionsTests.java @@ -121,7 +121,8 @@ void testToolCallingStream() { void testOptionsDefaultValue() { var options = new MiniMaxChatOptions(); - assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); + assertThat(options.getInternalToolExecutionMaxAttempts()) + .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); } @Test @@ -133,9 +134,7 @@ void testOptionsSetter() { @Test void testOptionsBuilder() { - var options = MiniMaxChatOptions.builder() - .internalToolExecutionMaxAttempts(3) - .build(); + var options = MiniMaxChatOptions.builder().internalToolExecutionMaxAttempts(3).build(); assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); } diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java index 8678e1189b2..7a2cf9d2deb 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java @@ -389,9 +389,8 @@ public MistralAiChatOptions copy() { public int hashCode() { return Objects.hash(this.model, this.temperature, this.topP, this.maxTokens, this.safePrompt, this.randomSeed, this.responseFormat, this.stop, this.frequencyPenalty, this.presencePenalty, this.n, this.tools, - this.toolChoice, this.toolCallbacks, this.tools, - this.internalToolExecutionEnabled, this.internalToolExecutionMaxAttempts, - this.toolContext); + this.toolChoice, this.toolCallbacks, this.tools, this.internalToolExecutionEnabled, + this.internalToolExecutionMaxAttempts, this.toolContext); } @Override diff --git a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatOptionsTests.java b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatOptionsTests.java index 2175f1e3a09..e94554695eb 100644 --- a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatOptionsTests.java +++ b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatOptionsTests.java @@ -14,7 +14,8 @@ class MistralAiChatOptionsTests { void testOptionsDefault() { var options = new MistralAiChatOptions(); - assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); + assertThat(options.getInternalToolExecutionMaxAttempts()) + .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); } @Test @@ -28,10 +29,9 @@ void testOptionsCustom() { @Test void testBuilder() { - var options = MistralAiChatOptions.builder() - .internalToolExecutionMaxAttempts(3) - .build(); + var options = MistralAiChatOptions.builder().internalToolExecutionMaxAttempts(3).build(); assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); } + } diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiChatOptionsTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiChatOptionsTests.java index a9582375af7..d73a2a09603 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiChatOptionsTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiChatOptionsTests.java @@ -90,9 +90,8 @@ void testBuilderWithAllFields() { .extracting("model", "frequencyPenalty", "logitBias", "logprobs", "topLogprobs", "maxTokens", "maxCompletionTokens", "n", "outputModalities", "outputAudio", "presencePenalty", "responseFormat", "streamOptions", "seed", "stop", "temperature", "topP", "tools", "toolChoice", "user", - "parallelToolCalls", "store", "metadata", "reasoningEffort", - "internalToolExecutionEnabled", "internalToolExecutionMaxAttempts", - "httpHeaders", "toolContext") + "parallelToolCalls", "store", "metadata", "reasoningEffort", "internalToolExecutionEnabled", + "internalToolExecutionMaxAttempts", "httpHeaders", "toolContext") .containsExactly("test-model", 0.5, logitBias, true, 5, 100, 50, 2, outputModalities, outputAudio, 0.8, responseFormat, streamOptions, 12345, stopSequences, 0.7, 0.9, tools, toolChoice, "test-user", true, false, metadata, "medium", false, 10, Map.of("header1", "value1"), toolContext); @@ -259,7 +258,8 @@ void testDefaultValues() { assertThat(options.getReasoningEffort()).isNull(); assertThat(options.getToolCallbacks()).isNotNull().isEmpty(); assertThat(options.getInternalToolExecutionEnabled()).isNull(); - assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); + assertThat(options.getInternalToolExecutionMaxAttempts()) + .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); assertThat(options.getHttpHeaders()).isNotNull().isEmpty(); assertThat(options.getToolContext()).isEqualTo(new HashMap<>()); assertThat(options.getStreamUsage()).isFalse(); diff --git a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptionsTest.java b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptionsTest.java index 3b41a3005fb..4406eaba005 100644 --- a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptionsTest.java +++ b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptionsTest.java @@ -11,21 +11,21 @@ class VertexAiGeminiChatOptionsTest { void optionsDefault() { var options = new VertexAiGeminiChatOptions(); - assertEquals(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS, options.getInternalToolExecutionMaxAttempts()); + assertEquals(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS, + options.getInternalToolExecutionMaxAttempts()); } @Test void builderDefault() { var options = VertexAiGeminiChatOptions.builder().build(); - assertEquals(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS, options.getInternalToolExecutionMaxAttempts()); + assertEquals(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS, + options.getInternalToolExecutionMaxAttempts()); } @Test void testBuilder() { - var options = VertexAiGeminiChatOptions.builder() - .internalToolExecutionMaxAttempts(3) - .build(); + var options = VertexAiGeminiChatOptions.builder().internalToolExecutionMaxAttempts(3).build(); assertEquals(3, options.getInternalToolExecutionMaxAttempts()); } @@ -39,4 +39,5 @@ void fromOptions() { assertEquals(original.getInternalToolExecutionMaxAttempts(), copied.getInternalToolExecutionMaxAttempts()); } + } diff --git a/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/chat/ZhiPuAiChatOptionsTests.java b/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/chat/ZhiPuAiChatOptionsTests.java index b6bad99d464..29b38e42e18 100644 --- a/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/chat/ZhiPuAiChatOptionsTests.java +++ b/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/chat/ZhiPuAiChatOptionsTests.java @@ -15,7 +15,8 @@ class ZhiPuAiChatOptionsTests { void testDefaultValue() { var options = new ZhiPuAiChatOptions(); - assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); + assertThat(options.getInternalToolExecutionMaxAttempts()) + .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); } @Test @@ -27,10 +28,9 @@ void testSetter() { @Test void testBuilder() { - var options = ZhiPuAiChatOptions.builder() - .internalToolExecutionMaxAttempts(3) - .build(); + var options = ZhiPuAiChatOptions.builder().internalToolExecutionMaxAttempts(3).build(); assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); } + } diff --git a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptionsTests.java b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptionsTests.java index d32e0824773..d15de26bb63 100644 --- a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptionsTests.java +++ b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptionsTests.java @@ -152,7 +152,8 @@ void copyShouldCreateNewInstanceWithSameValues() { assertThat(c.getToolNames()).isEqualTo(original.getToolNames()); assertThat(c.getToolContext()).isEqualTo(original.getToolContext()); assertThat(c.getInternalToolExecutionEnabled()).isEqualTo(original.getInternalToolExecutionEnabled()); - assertThat(c.getInternalToolExecutionMaxAttempts()).isEqualTo(original.getInternalToolExecutionMaxAttempts()); + assertThat(c.getInternalToolExecutionMaxAttempts()) + .isEqualTo(original.getInternalToolExecutionMaxAttempts()); assertThat(c.getModel()).isEqualTo(original.getModel()); assertThat(c.getTemperature()).isEqualTo(original.getTemperature()); }); @@ -241,7 +242,7 @@ void deprecatedMethodsShouldWorkCorrectly() { // default value check assertThat(options.getInternalToolExecutionMaxAttempts()) - .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); + .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); options.setInternalToolExecutionMaxAttempts(3); assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); From c63341bf87484fe3907bcc843aa941e2c4fe53c7 Mon Sep 17 00:00:00 2001 From: lambochen Date: Sat, 31 May 2025 21:32:08 +0800 Subject: [PATCH 23/25] fix attempts check logical Signed-off-by: lambochen --- .../ai/model/tool/ToolExecutionEligibilityChecker.java | 5 +---- .../ai/model/tool/ToolExecutionEligibilityPredicate.java | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java index b7d4ba3f119..891f1d9dbc7 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java @@ -99,16 +99,13 @@ default boolean isInternalToolExecutionEnabled(ChatOptions chatOptions) { default boolean isInternalToolExecutionEnabled(ChatOptions chatOptions, int attempts) { boolean internalToolExecutionEnabled = isInternalToolExecutionEnabled(chatOptions); if (!internalToolExecutionEnabled) { - return false; + return internalToolExecutionEnabled; } if (chatOptions instanceof ToolCallingChatOptions toolCallingChatOptions) { return toolCallingChatOptions.getInternalToolExecutionMaxAttempts() == null || attempts <= toolCallingChatOptions.getInternalToolExecutionMaxAttempts(); } - else { - internalToolExecutionEnabled = true; - } return internalToolExecutionEnabled; } diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java index de9d96394a2..a3d9aba4f91 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java @@ -56,16 +56,13 @@ default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse chatResponse, int attempts) { boolean isToolExecutionRequired = isToolExecutionRequired(promptOptions, chatResponse); if (!isToolExecutionRequired) { - return true; + return isToolExecutionRequired; } if (promptOptions instanceof ToolCallingChatOptions toolCallingChatOptions) { return toolCallingChatOptions.getInternalToolExecutionMaxAttempts() == null || attempts <= toolCallingChatOptions.getInternalToolExecutionMaxAttempts(); } - else { - isToolExecutionRequired = true; - } return isToolExecutionRequired; } From 56fba7cb08c55699d030b1269439824e6666a39a Mon Sep 17 00:00:00 2001 From: lambochen Date: Sun, 1 Jun 2025 22:52:17 +0800 Subject: [PATCH 24/25] ToolCallingChatOptions: rename attempts to iterations for tool execution Signed-off-by: lambochen --- .../ai/anthropic/AnthropicChatModel.java | 20 +++++------ .../ai/anthropic/AnthropicChatOptions.java | 20 +++++------ .../anthropic/AnthropicChatOptionsTests.java | 16 ++++----- .../ai/azure/openai/AzureOpenAiChatModel.java | 20 +++++------ .../azure/openai/AzureOpenAiChatOptions.java | 20 +++++------ .../openai/AzureOpenAiChatOptionsTests.java | 16 ++++----- .../converse/BedrockProxyChatModel.java | 20 +++++------ .../ai/deepseek/DeepSeekChatModel.java | 20 +++++------ .../ai/deepseek/DeepSeekChatOptions.java | 20 +++++------ .../ai/deepseek/DeepSeekChatOptionsTest.java | 12 +++---- .../ai/minimax/MiniMaxChatModel.java | 20 +++++------ .../ai/minimax/MiniMaxChatOptions.java | 28 +++++++-------- .../minimax/chat/MiniMaxChatOptionsTests.java | 12 +++---- .../ai/mistralai/MistralAiChatModel.java | 20 +++++------ .../ai/mistralai/MistralAiChatOptions.java | 20 +++++------ .../MistralAiChatCompletionRequestTest.java | 6 ++-- .../mistralai/MistralAiChatOptionsTests.java | 12 +++---- .../ai/ollama/OllamaChatModel.java | 20 +++++------ .../ai/ollama/api/OllamaOptions.java | 20 +++++------ .../ai/ollama/OllamaChatRequestTests.java | 6 ++-- .../ai/openai/OpenAiChatModel.java | 20 +++++------ .../ai/openai/OpenAiChatOptions.java | 20 +++++------ .../ai/openai/ChatCompletionRequestTests.java | 7 ++-- .../ai/openai/OpenAiChatOptionsTests.java | 14 ++++---- .../gemini/VertexAiGeminiChatModel.java | 20 +++++------ .../gemini/VertexAiGeminiChatOptions.java | 20 +++++------ .../gemini/VertexAiGeminiChatOptionsTest.java | 16 ++++----- .../ai/zhipuai/ZhiPuAiChatModel.java | 20 +++++------ .../ai/zhipuai/ZhiPuAiChatOptions.java | 36 +++++++++---------- .../zhipuai/chat/ZhiPuAiChatOptionsTests.java | 12 +++---- .../tool/DefaultToolCallingChatOptions.java | 18 +++++----- .../ai/model/tool/ToolCallingChatOptions.java | 26 +++++++------- .../tool/ToolExecutionEligibilityChecker.java | 4 +-- .../ToolExecutionEligibilityPredicate.java | 6 ++-- .../DefaultToolCallingChatOptionsTests.java | 18 +++++----- .../tool/ToolCallingChatOptionsTests.java | 6 ++-- .../ToolExecutionEligibilityCheckerTest.java | 5 ++- ...oolExecutionEligibilityPredicateTests.java | 2 +- 38 files changed, 309 insertions(+), 309 deletions(-) diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java index 02cb03ade87..c97f0aae085 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java @@ -178,7 +178,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons return this.internalCall(prompt, previousChatResponse, 1); } - public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { + public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int iterations) { ChatCompletionRequest request = createRequest(prompt, false); ChatModelObservationContext observationContext = ChatModelObservationContext.builder() @@ -208,7 +208,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons return chatResponse; }); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, iterations)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. @@ -220,7 +220,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons else { // Send the tool execution result back to the model. return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response, attempts + 1); + response, iterations + 1); } } @@ -244,7 +244,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha return this.internalStream(prompt, previousChatResponse, 1); } - public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { + public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int iterations) { return Flux.deferContextual(contextView -> { ChatCompletionRequest request = createRequest(prompt, true); @@ -269,7 +269,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha Usage accumulatedUsage = UsageCalculator.getCumulativeUsage(currentChatResponseUsage, previousChatResponse); ChatResponse chatResponse = toChatResponse(chatCompletionResponse, accumulatedUsage); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse, attempts) + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse, iterations) && chatResponse.hasFinishReasons(Set.of("tool_use"))) { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous @@ -284,7 +284,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha else { // Send the tool execution result back to the model. return this.internalStream(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - chatResponse, attempts + 1); + chatResponse, iterations + 1); } }).subscribeOn(Schedulers.boundedElastic()); } @@ -447,9 +447,9 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setInternalToolExecutionEnabled( ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); - requestOptions.setInternalToolExecutionMaxAttempts( - ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), - defaultOptions.getInternalToolExecutionMaxAttempts())); + requestOptions.setInternalToolExecutionMaxIterations( + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxIterations(), + defaultOptions.getInternalToolExecutionMaxIterations())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -461,7 +461,7 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setHttpHeaders(this.defaultOptions.getHttpHeaders()); requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); requestOptions - .setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); + .setInternalToolExecutionMaxIterations(this.defaultOptions.getInternalToolExecutionMaxIterations()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java index bd9baf2893f..030ba12f977 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java @@ -81,7 +81,7 @@ public class AnthropicChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; + private Integer internalToolExecutionMaxIterations = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS; @JsonIgnore private Map toolContext = new HashMap<>(); @@ -113,7 +113,7 @@ public static AnthropicChatOptions fromOptions(AnthropicChatOptions fromOptions) fromOptions.getToolCallbacks() != null ? new ArrayList<>(fromOptions.getToolCallbacks()) : null) .toolNames(fromOptions.getToolNames() != null ? new HashSet<>(fromOptions.getToolNames()) : null) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) - .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) + .internalToolExecutionMaxIterations(fromOptions.getInternalToolExecutionMaxIterations()) .toolContext(fromOptions.getToolContext() != null ? new HashMap<>(fromOptions.getToolContext()) : null) .httpHeaders(fromOptions.getHttpHeaders() != null ? new HashMap<>(fromOptions.getHttpHeaders()) : null) .build(); @@ -232,13 +232,13 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut } @Override - public Integer getInternalToolExecutionMaxAttempts() { - return this.internalToolExecutionMaxAttempts; + public Integer getInternalToolExecutionMaxIterations() { + return this.internalToolExecutionMaxIterations; } @Override - public void setInternalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { - this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; + public void setInternalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; } @Override @@ -296,7 +296,7 @@ public boolean equals(Object o) { && Objects.equals(this.toolCallbacks, that.toolCallbacks) && Objects.equals(this.toolNames, that.toolNames) && Objects.equals(this.internalToolExecutionEnabled, that.internalToolExecutionEnabled) - && Objects.equals(this.internalToolExecutionMaxAttempts, that.internalToolExecutionMaxAttempts) + && Objects.equals(this.internalToolExecutionMaxIterations, that.internalToolExecutionMaxIterations) && Objects.equals(this.toolContext, that.toolContext) && Objects.equals(this.httpHeaders, that.httpHeaders); } @@ -305,7 +305,7 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(this.model, this.maxTokens, this.metadata, this.stopSequences, this.temperature, this.topP, this.topK, this.thinking, this.toolCallbacks, this.toolNames, this.internalToolExecutionEnabled, - this.internalToolExecutionMaxAttempts, this.toolContext, this.httpHeaders); + this.internalToolExecutionMaxIterations, this.toolContext, this.httpHeaders); } public static class Builder { @@ -390,8 +390,8 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } - public Builder internalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { - this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); + public Builder internalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.options.setInternalToolExecutionMaxIterations(internalToolExecutionMaxIterations); return this; } diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java index 400926e6b93..2364e864f64 100644 --- a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java +++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java @@ -44,12 +44,12 @@ void testBuilderWithAllFields() { .topP(0.8) .topK(50) .metadata(new Metadata("userId_123")) - .internalToolExecutionMaxAttempts(3) + .internalToolExecutionMaxIterations(3) .build(); assertThat(options) .extracting("model", "maxTokens", "stopSequences", "temperature", "topP", "topK", "metadata", - "internalToolExecutionMaxAttempts") + "internalToolExecutionMaxIterations") .containsExactly("test-model", 100, List.of("stop1", "stop2"), 0.7, 0.8, 50, new Metadata("userId_123"), 3); } @@ -64,7 +64,7 @@ void testCopy() { .topK(50) .metadata(new Metadata("userId_123")) .toolContext(Map.of("key1", "value1")) - .internalToolExecutionMaxAttempts(3) + .internalToolExecutionMaxIterations(3) .build(); AnthropicChatOptions copied = original.copy(); @@ -74,7 +74,7 @@ void testCopy() { assertThat(copied.getStopSequences()).isNotSameAs(original.getStopSequences()); assertThat(copied.getToolContext()).isNotSameAs(original.getToolContext()); - assertThat(copied.getInternalToolExecutionMaxAttempts()).isEqualTo(3); + assertThat(copied.getInternalToolExecutionMaxIterations()).isEqualTo(3); } @Test @@ -87,7 +87,7 @@ void testSetters() { options.setTopP(0.8); options.setStopSequences(List.of("stop1", "stop2")); options.setMetadata(new Metadata("userId_123")); - options.setInternalToolExecutionMaxAttempts(3); + options.setInternalToolExecutionMaxIterations(3); assertThat(options.getModel()).isEqualTo("test-model"); assertThat(options.getMaxTokens()).isEqualTo(100); @@ -96,7 +96,7 @@ void testSetters() { assertThat(options.getTopP()).isEqualTo(0.8); assertThat(options.getStopSequences()).isEqualTo(List.of("stop1", "stop2")); assertThat(options.getMetadata()).isEqualTo(new Metadata("userId_123")); - assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); + assertThat(options.getInternalToolExecutionMaxIterations()).isEqualTo(3); } @Test @@ -109,8 +109,8 @@ void testDefaultValues() { assertThat(options.getTopP()).isNull(); assertThat(options.getStopSequences()).isNull(); assertThat(options.getMetadata()).isNull(); - assertThat(options.getInternalToolExecutionMaxAttempts()) - .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); + assertThat(options.getInternalToolExecutionMaxIterations()) + .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS); } } diff --git a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java index f2e8d99e97a..21c0d36a0e1 100644 --- a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java +++ b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatModel.java @@ -256,7 +256,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons return internalCall(prompt, previousChatResponse, 1); } - public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { + public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int iterations) { ChatModelObservationContext observationContext = ChatModelObservationContext.builder() .prompt(prompt) @@ -276,7 +276,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons return chatResponse; }); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, iterations)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. @@ -288,7 +288,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons else { // Send the tool execution result back to the model. return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response, attempts + 1); + response, iterations + 1); } } @@ -307,7 +307,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha return this.internalStream(prompt, previousChatResponse, 1); } - public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { + public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int iterations) { return Flux.deferContextual(contextView -> { ChatCompletionsOptions options = toAzureChatCompletionsOptions(prompt); @@ -388,7 +388,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha return chatResponseFlux.flatMap(chatResponse -> { if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse, - attempts)) { + iterations)) { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous return Flux.defer(() -> { @@ -404,7 +404,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha // Send the tool execution result back to the model. return this.internalStream( new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - chatResponse, attempts + 1); + chatResponse, iterations + 1); } }).subscribeOn(Schedulers.boundedElastic()); } @@ -677,9 +677,9 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setInternalToolExecutionEnabled( ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); - runtimeOptions.setInternalToolExecutionMaxAttempts( - ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), - this.defaultOptions.getInternalToolExecutionMaxAttempts())); + runtimeOptions.setInternalToolExecutionMaxIterations( + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxIterations(), + this.defaultOptions.getInternalToolExecutionMaxIterations())); requestOptions.setStreamUsage(ModelOptionsUtils.mergeOption(runtimeOptions.getStreamUsage(), this.defaultOptions.getStreamUsage())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), @@ -692,7 +692,7 @@ Prompt buildRequestPrompt(Prompt prompt) { else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); requestOptions - .setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); + .setInternalToolExecutionMaxIterations(this.defaultOptions.getInternalToolExecutionMaxIterations()); requestOptions.setStreamUsage(this.defaultOptions.getStreamUsage()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); diff --git a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java index 0a7b921d35b..9f130b47e5d 100644 --- a/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java +++ b/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java @@ -202,7 +202,7 @@ public class AzureOpenAiChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; + private Integer internalToolExecutionMaxIterations = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS; /** * Whether to include token usage information in streaming chat completion responses. @@ -262,13 +262,13 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut } @Override - public Integer getInternalToolExecutionMaxAttempts() { - return this.internalToolExecutionMaxAttempts; + public Integer getInternalToolExecutionMaxIterations() { + return this.internalToolExecutionMaxIterations; } @Override - public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { - this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; + public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { + this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; } public static Builder builder() { @@ -298,7 +298,7 @@ public static AzureOpenAiChatOptions fromOptions(AzureOpenAiChatOptions fromOpti .enhancements(fromOptions.getEnhancements()) .toolContext(fromOptions.getToolContext() != null ? new HashMap<>(fromOptions.getToolContext()) : null) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) - .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) + .internalToolExecutionMaxIterations(fromOptions.getInternalToolExecutionMaxIterations()) .streamOptions(fromOptions.getStreamOptions()) .toolCallbacks( fromOptions.getToolCallbacks() != null ? new ArrayList<>(fromOptions.getToolCallbacks()) : null) @@ -519,7 +519,7 @@ public boolean equals(Object o) { && Objects.equals(this.toolCallbacks, that.toolCallbacks) && Objects.equals(this.toolNames, that.toolNames) && Objects.equals(this.internalToolExecutionEnabled, that.internalToolExecutionEnabled) - && Objects.equals(this.internalToolExecutionMaxAttempts, that.internalToolExecutionMaxAttempts) + && Objects.equals(this.internalToolExecutionMaxIterations, that.internalToolExecutionMaxIterations) && Objects.equals(this.logprobs, that.logprobs) && Objects.equals(this.topLogProbs, that.topLogProbs) && Objects.equals(this.enhancements, that.enhancements) && Objects.equals(this.streamOptions, that.streamOptions) @@ -535,7 +535,7 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(this.logitBias, this.user, this.n, this.stop, this.deploymentName, this.responseFormat, this.toolCallbacks, this.toolNames, this.internalToolExecutionEnabled, - this.internalToolExecutionMaxAttempts, this.seed, this.logprobs, this.topLogProbs, this.enhancements, + this.internalToolExecutionMaxIterations, this.seed, this.logprobs, this.topLogProbs, this.enhancements, this.streamOptions, this.reasoningEffort, this.enableStreamUsage, this.toolContext, this.maxTokens, this.frequencyPenalty, this.presencePenalty, this.temperature, this.topP); } @@ -680,8 +680,8 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } - public Builder internalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { - this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); + public Builder internalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.options.setInternalToolExecutionMaxIterations(internalToolExecutionMaxIterations); return this; } diff --git a/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptionsTests.java b/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptionsTests.java index 968d40ecf36..82be30a85d6 100644 --- a/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptionsTests.java +++ b/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptionsTests.java @@ -67,13 +67,13 @@ void testBuilderWithAllFields() { .topLogprobs(5) .enhancements(enhancements) .streamOptions(streamOptions) - .internalToolExecutionMaxAttempts(3) + .internalToolExecutionMaxIterations(3) .build(); assertThat(options) .extracting("deploymentName", "frequencyPenalty", "logitBias", "maxTokens", "n", "presencePenalty", "stop", "temperature", "topP", "user", "responseFormat", "streamUsage", "reasoningEffort", "seed", - "logprobs", "topLogProbs", "enhancements", "streamOptions", "internalToolExecutionMaxAttempts") + "logprobs", "topLogProbs", "enhancements", "streamOptions", "internalToolExecutionMaxIterations") .containsExactly("test-deployment", 0.5, Map.of("token1", 1, "token2", -1), 200, 2, 0.8, List.of("stop1", "stop2"), 0.7, 0.9, "test-user", responseFormat, true, "low", 12345L, true, 5, enhancements, streamOptions, 3); @@ -110,7 +110,7 @@ void testCopy() { .topLogprobs(5) .enhancements(enhancements) .streamOptions(streamOptions) - .internalToolExecutionMaxAttempts(3) + .internalToolExecutionMaxIterations(3) .build(); AzureOpenAiChatOptions copiedOptions = originalOptions.copy(); @@ -120,7 +120,7 @@ void testCopy() { assertThat(copiedOptions.getStop()).isNotSameAs(originalOptions.getStop()); assertThat(copiedOptions.getToolContext()).isNotSameAs(originalOptions.getToolContext()); - assertThat(copiedOptions.getInternalToolExecutionMaxAttempts()).isEqualTo(3); + assertThat(copiedOptions.getInternalToolExecutionMaxIterations()).isEqualTo(3); } @Test @@ -151,7 +151,7 @@ void testSetters() { options.setTopLogProbs(5); options.setEnhancements(enhancements); options.setStreamOptions(streamOptions); - options.setInternalToolExecutionMaxAttempts(3); + options.setInternalToolExecutionMaxIterations(3); assertThat(options.getDeploymentName()).isEqualTo("test-deployment"); options.setModel("test-model"); @@ -175,7 +175,7 @@ void testSetters() { assertThat(options.getEnhancements()).isEqualTo(enhancements); assertThat(options.getStreamOptions()).isEqualTo(streamOptions); assertThat(options.getModel()).isEqualTo("test-model"); - assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); + assertThat(options.getInternalToolExecutionMaxIterations()).isEqualTo(3); } @Test @@ -201,8 +201,8 @@ void testDefaultValues() { assertThat(options.getEnhancements()).isNull(); assertThat(options.getStreamOptions()).isNull(); assertThat(options.getModel()).isNull(); - assertThat(options.getInternalToolExecutionMaxAttempts()) - .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); + assertThat(options.getInternalToolExecutionMaxIterations()) + .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS); } } diff --git a/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java b/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java index 317c83a7164..c17e04b565c 100644 --- a/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java +++ b/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java @@ -221,7 +221,7 @@ private ChatResponse internalCall(Prompt prompt, ChatResponse perviousChatRespon return this.internalCall(prompt, perviousChatResponse, 1); } - private ChatResponse internalCall(Prompt prompt, ChatResponse perviousChatResponse, int attempts) { + private ChatResponse internalCall(Prompt prompt, ChatResponse perviousChatResponse, int iterations) { ConverseRequest converseRequest = this.createRequest(prompt); @@ -246,8 +246,8 @@ private ChatResponse internalCall(Prompt prompt, ChatResponse perviousChatRespon return response; }); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse, attempts) - && chatResponse.hasFinishReasons(Set.of(StopReason.TOOL_USE.toString()))) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse, + iterations) && chatResponse.hasFinishReasons(Set.of(StopReason.TOOL_USE.toString()))) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, chatResponse); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. @@ -259,7 +259,7 @@ private ChatResponse internalCall(Prompt prompt, ChatResponse perviousChatRespon else { // Send the tool execution result back to the model. return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - chatResponse, attempts + 1); + chatResponse, iterations + 1); } } return chatResponse; @@ -315,9 +315,9 @@ Prompt buildRequestPrompt(Prompt prompt) { .internalToolExecutionEnabled(runtimeOptions.getInternalToolExecutionEnabled() != null ? runtimeOptions.getInternalToolExecutionEnabled() : this.defaultOptions.getInternalToolExecutionEnabled()) - .internalToolExecutionMaxAttempts( - ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), - this.defaultOptions.getInternalToolExecutionMaxAttempts())) + .internalToolExecutionMaxIterations( + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxIterations(), + this.defaultOptions.getInternalToolExecutionMaxIterations())) .build(); } @@ -655,7 +655,7 @@ private Flux internalStream(Prompt prompt, ChatResponse perviousCh return this.internalStream(prompt, perviousChatResponse, 1); } - private Flux internalStream(Prompt prompt, ChatResponse perviousChatResponse, int attempts) { + private Flux internalStream(Prompt prompt, ChatResponse perviousChatResponse, int iterations) { Assert.notNull(prompt, "'prompt' must not be null"); return Flux.deferContextual(contextView -> { @@ -689,7 +689,7 @@ private Flux internalStream(Prompt prompt, ChatResponse perviousCh Flux chatResponseFlux = chatResponses.switchMap(chatResponse -> { if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse, - attempts) && chatResponse.hasFinishReasons(Set.of(StopReason.TOOL_USE.toString()))) { + iterations) && chatResponse.hasFinishReasons(Set.of(StopReason.TOOL_USE.toString()))) { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous @@ -707,7 +707,7 @@ private Flux internalStream(Prompt prompt, ChatResponse perviousCh // Send the tool execution result back to the model. return this.internalStream( new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - chatResponse, attempts + 1); + chatResponse, iterations + 1); } }).subscribeOn(Schedulers.boundedElastic()); } diff --git a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java index e0f91bc03e3..5dd16312dda 100644 --- a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java +++ b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java @@ -155,7 +155,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons return internalCall(prompt, previousChatResponse, 1); } - public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { + public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int iterations) { ChatCompletionRequest request = createRequest(prompt, false); @@ -210,7 +210,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons }); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, iterations)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. @@ -222,7 +222,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons else { // Send the tool execution result back to the model. return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response, attempts + 1); + response, iterations + 1); } } @@ -239,7 +239,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha return internalStream(prompt, previousChatResponse, 1); } - public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { + public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int iterations) { return Flux.deferContextual(contextView -> { ChatCompletionRequest request = createRequest(prompt, true); @@ -294,7 +294,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha // @formatter:off Flux flux = chatResponse.flatMap(response -> { - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, iterations)) { return Flux.defer(() -> { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous @@ -308,7 +308,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha else { // Send the tool execution result back to the model. return this.internalStream(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response, attempts + 1); + response, iterations + 1); } }).subscribeOn(Schedulers.boundedElastic()); } @@ -406,9 +406,9 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setInternalToolExecutionEnabled( ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); - requestOptions.setInternalToolExecutionMaxAttempts( - ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), - this.defaultOptions.getInternalToolExecutionMaxAttempts())); + requestOptions.setInternalToolExecutionMaxIterations( + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxIterations(), + this.defaultOptions.getInternalToolExecutionMaxIterations())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -419,7 +419,7 @@ Prompt buildRequestPrompt(Prompt prompt) { else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); requestOptions - .setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); + .setInternalToolExecutionMaxIterations(this.defaultOptions.getInternalToolExecutionMaxIterations()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); diff --git a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java index 5ad71c54c97..7728a595092 100644 --- a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java +++ b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java @@ -124,7 +124,7 @@ public class DeepSeekChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; + private Integer internalToolExecutionMaxIterations = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS; /** * Tool Function Callbacks to register with the ChatModel. @@ -295,14 +295,14 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut @Override @JsonIgnore - public Integer getInternalToolExecutionMaxAttempts() { - return this.internalToolExecutionMaxAttempts; + public Integer getInternalToolExecutionMaxIterations() { + return this.internalToolExecutionMaxIterations; } @Override @JsonIgnore - public void setInternalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { - this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; + public void setInternalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; } public Boolean getLogprobs() { @@ -349,7 +349,7 @@ public int hashCode() { this.maxTokens, this.presencePenalty, this.responseFormat, this.stop, this.temperature, this.topP, this.tools, this.toolChoice, this.toolCallbacks, this.toolNames, - this.internalToolExecutionEnabled, this.internalToolExecutionMaxAttempts, + this.internalToolExecutionEnabled, this.internalToolExecutionMaxIterations, this.toolContext); } @@ -376,7 +376,7 @@ public boolean equals(Object o) { && Objects.equals(this.toolNames, other.toolNames) && Objects.equals(this.toolContext, other.toolContext) && Objects.equals(this.internalToolExecutionEnabled, other.internalToolExecutionEnabled) - && Objects.equals(this.internalToolExecutionMaxAttempts, other.internalToolExecutionMaxAttempts) + && Objects.equals(this.internalToolExecutionMaxIterations, other.internalToolExecutionMaxIterations) ; } @@ -398,7 +398,7 @@ public static DeepSeekChatOptions fromOptions(DeepSeekChatOptions fromOptions) { fromOptions.getToolCallbacks() != null ? new ArrayList<>(fromOptions.getToolCallbacks()) : null) .toolNames(fromOptions.getToolNames() != null ? new HashSet<>(fromOptions.getToolNames()) : null) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) - .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) + .internalToolExecutionMaxIterations(fromOptions.getInternalToolExecutionMaxIterations()) .toolContext(fromOptions.getToolContext() != null ? new HashMap<>(fromOptions.getToolContext()) : null) .build(); } @@ -508,8 +508,8 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } - public Builder internalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { - this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); + public Builder internalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.options.setInternalToolExecutionMaxIterations(internalToolExecutionMaxIterations); return this; } diff --git a/models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/DeepSeekChatOptionsTest.java b/models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/DeepSeekChatOptionsTest.java index f313f9f8ee2..cd6e8854806 100644 --- a/models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/DeepSeekChatOptionsTest.java +++ b/models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/DeepSeekChatOptionsTest.java @@ -13,26 +13,26 @@ class DeepSeekChatOptionsTest { @Test void fromOptions() { var original = new DeepSeekChatOptions(); - original.setInternalToolExecutionMaxAttempts(3); + original.setInternalToolExecutionMaxIterations(3); var copy = DeepSeekChatOptions.fromOptions(original); assertNotSame(original, copy); - assertSame(original.getInternalToolExecutionMaxAttempts(), copy.getInternalToolExecutionMaxAttempts()); + assertSame(original.getInternalToolExecutionMaxIterations(), copy.getInternalToolExecutionMaxIterations()); } @Test void optionsDefault() { var options = new DeepSeekChatOptions(); - assertEquals(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS, - options.getInternalToolExecutionMaxAttempts()); + assertEquals(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS, + options.getInternalToolExecutionMaxIterations()); } @Test void optionsBuilder() { - var options = DeepSeekChatOptions.builder().internalToolExecutionMaxAttempts(3).build(); + var options = DeepSeekChatOptions.builder().internalToolExecutionMaxIterations(3).build(); - assertEquals(3, options.getInternalToolExecutionMaxAttempts()); + assertEquals(3, options.getInternalToolExecutionMaxIterations()); } } diff --git a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatModel.java b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatModel.java index e70041fbed3..c9d4acaff5c 100644 --- a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatModel.java +++ b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatModel.java @@ -241,7 +241,7 @@ public ChatResponse call(Prompt prompt) { return internalCall(requestPrompt, 1); } - private ChatResponse internalCall(Prompt requestPrompt, int attempts) { + private ChatResponse internalCall(Prompt requestPrompt, int iterations) { ChatCompletionRequest request = createRequest(requestPrompt, false); ChatModelObservationContext observationContext = ChatModelObservationContext.builder() @@ -299,7 +299,7 @@ else if (!CollectionUtils.isEmpty(choice.messages())) { }); if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(requestPrompt.getOptions(), response, - attempts)) { + iterations)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(requestPrompt, response); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. @@ -312,7 +312,7 @@ else if (!CollectionUtils.isEmpty(choice.messages())) { // Send the tool execution result back to the model. return this.internalCall( new Prompt(toolExecutionResult.conversationHistory(), requestPrompt.getOptions()), - attempts + 1); + iterations + 1); } } @@ -332,7 +332,7 @@ public Flux stream(Prompt prompt) { return internalStream(requestPrompt, 1); } - private Flux internalStream(Prompt requestPrompt, int attempts) { + private Flux internalStream(Prompt requestPrompt, int iterations) { return Flux.deferContextual(contextView -> { ChatCompletionRequest request = createRequest(requestPrompt, true); @@ -382,7 +382,7 @@ private Flux internalStream(Prompt requestPrompt, int attempts) { })); Flux flux = chatResponse.flatMap(response -> { - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(requestPrompt.getOptions(), response, attempts)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(requestPrompt.getOptions(), response, iterations)) { return Flux.defer(() -> { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous @@ -395,7 +395,7 @@ private Flux internalStream(Prompt requestPrompt, int attempts) { } else { // Send the tool execution result back to the model. - return this.internalStream(new Prompt(toolExecutionResult.conversationHistory(), requestPrompt.getOptions()), attempts + 1); + return this.internalStream(new Prompt(toolExecutionResult.conversationHistory(), requestPrompt.getOptions()), iterations + 1); } }).subscribeOn(Schedulers.boundedElastic()); } @@ -485,9 +485,9 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setInternalToolExecutionEnabled( ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); - requestOptions.setInternalToolExecutionMaxAttempts( - ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), - this.defaultOptions.getInternalToolExecutionMaxAttempts())); + requestOptions.setInternalToolExecutionMaxIterations( + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxIterations(), + this.defaultOptions.getInternalToolExecutionMaxIterations())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -498,7 +498,7 @@ Prompt buildRequestPrompt(Prompt prompt) { else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); requestOptions - .setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); + .setInternalToolExecutionMaxIterations(this.defaultOptions.getInternalToolExecutionMaxIterations()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); diff --git a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java index 2449cfee3e8..84369a5f736 100644 --- a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java +++ b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java @@ -155,7 +155,7 @@ public class MiniMaxChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; + private Integer internalToolExecutionMaxIterations = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS; // @formatter:on @@ -180,7 +180,7 @@ public static MiniMaxChatOptions fromOptions(MiniMaxChatOptions fromOptions) { .toolCallbacks(fromOptions.getToolCallbacks()) .toolNames(fromOptions.getToolNames()) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) - .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) + .internalToolExecutionMaxIterations(fromOptions.getInternalToolExecutionMaxIterations()) .toolContext(fromOptions.getToolContext()) .build(); } @@ -355,13 +355,13 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut } @Override - public Integer getInternalToolExecutionMaxAttempts() { - return this.internalToolExecutionMaxAttempts; + public Integer getInternalToolExecutionMaxIterations() { + return this.internalToolExecutionMaxIterations; } @Override - public void setInternalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { - this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; + public void setInternalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { + this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; } @Override @@ -395,9 +395,9 @@ public int hashCode() { result = prime * result + ((this.toolNames == null) ? 0 : this.toolNames.hashCode()); result = prime * result + ((this.internalToolExecutionEnabled == null) ? 0 : this.internalToolExecutionEnabled.hashCode()); - result = prime * result + ((this.internalToolExecutionMaxAttempts == null) - ? ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS - : this.internalToolExecutionMaxAttempts.hashCode()); + result = prime * result + ((this.internalToolExecutionMaxIterations == null) + ? ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS + : this.internalToolExecutionMaxIterations.hashCode()); result = prime * result + ((this.toolContext == null) ? 0 : this.toolContext.hashCode()); return result; } @@ -527,12 +527,12 @@ else if (!this.internalToolExecutionEnabled.equals(other.internalToolExecutionEn return false; } - if (this.internalToolExecutionMaxAttempts == null) { - if (other.internalToolExecutionMaxAttempts != null) { + if (this.internalToolExecutionMaxIterations == null) { + if (other.internalToolExecutionMaxIterations != null) { return false; } } - else if (!this.internalToolExecutionMaxAttempts.equals(other.internalToolExecutionMaxAttempts)) { + else if (!this.internalToolExecutionMaxIterations.equals(other.internalToolExecutionMaxIterations)) { return false; } @@ -676,8 +676,8 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } - public Builder internalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { - this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); + public Builder internalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { + this.options.setInternalToolExecutionMaxIterations(internalToolExecutionMaxIterations); return this; } diff --git a/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/chat/MiniMaxChatOptionsTests.java b/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/chat/MiniMaxChatOptionsTests.java index 37487583521..7371e7373c0 100644 --- a/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/chat/MiniMaxChatOptionsTests.java +++ b/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/chat/MiniMaxChatOptionsTests.java @@ -121,22 +121,22 @@ void testToolCallingStream() { void testOptionsDefaultValue() { var options = new MiniMaxChatOptions(); - assertThat(options.getInternalToolExecutionMaxAttempts()) - .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); + assertThat(options.getInternalToolExecutionMaxIterations()) + .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS); } @Test void testOptionsSetter() { var options = new MiniMaxChatOptions(); - options.setInternalToolExecutionMaxAttempts(3); - assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); + options.setInternalToolExecutionMaxIterations(3); + assertThat(options.getInternalToolExecutionMaxIterations()).isEqualTo(3); } @Test void testOptionsBuilder() { - var options = MiniMaxChatOptions.builder().internalToolExecutionMaxAttempts(3).build(); + var options = MiniMaxChatOptions.builder().internalToolExecutionMaxIterations(3).build(); - assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); + assertThat(options.getInternalToolExecutionMaxIterations()).isEqualTo(3); } } diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java index f9a2c224f58..c87eb0afa4a 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java @@ -186,7 +186,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons return internalCall(prompt, previousChatResponse, 1); } - public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { + public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int iterations) { MistralAiApi.ChatCompletionRequest request = createRequest(prompt, false); @@ -231,7 +231,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons return chatResponse; }); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, iterations)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. @@ -243,7 +243,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons else { // Send the tool execution result back to the model. return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response, attempts + 1); + response, iterations + 1); } } @@ -262,7 +262,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha return internalStream(prompt, previousChatResponse, 1); } - public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { + public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int iterations) { return Flux.deferContextual(contextView -> { var request = createRequest(prompt, true); @@ -323,7 +323,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha // @formatter:off Flux chatResponseFlux = chatResponse.flatMap(response -> { - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, iterations)) { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous return Flux.defer(() -> { @@ -337,7 +337,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha else { // Send the tool execution result back to the model. return this.internalStream(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response, attempts + 1); + response, iterations + 1); } }).subscribeOn(Schedulers.boundedElastic()); } @@ -404,9 +404,9 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setInternalToolExecutionEnabled( ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); - requestOptions.setInternalToolExecutionMaxAttempts( - ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), - this.defaultOptions.getInternalToolExecutionMaxAttempts())); + requestOptions.setInternalToolExecutionMaxIterations( + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxIterations(), + this.defaultOptions.getInternalToolExecutionMaxIterations())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -417,7 +417,7 @@ Prompt buildRequestPrompt(Prompt prompt) { else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); requestOptions - .setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); + .setInternalToolExecutionMaxIterations(this.defaultOptions.getInternalToolExecutionMaxIterations()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java index 7a2cf9d2deb..8fb26aab885 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java @@ -158,7 +158,7 @@ public class MistralAiChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; + private Integer internalToolExecutionMaxIterations = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS; @JsonIgnore private Map toolContext = new HashMap<>(); @@ -184,7 +184,7 @@ public static MistralAiChatOptions fromOptions(MistralAiChatOptions fromOptions) .toolCallbacks(fromOptions.getToolCallbacks()) .toolNames(fromOptions.getToolNames()) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) - .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) + .internalToolExecutionMaxIterations(fromOptions.getInternalToolExecutionMaxIterations()) .toolContext(fromOptions.getToolContext()) .build(); } @@ -353,13 +353,13 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut } @Override - public Integer getInternalToolExecutionMaxAttempts() { - return this.internalToolExecutionMaxAttempts; + public Integer getInternalToolExecutionMaxIterations() { + return this.internalToolExecutionMaxIterations; } @Override - public void setInternalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { - this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; + public void setInternalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; } @Override @@ -390,7 +390,7 @@ public int hashCode() { return Objects.hash(this.model, this.temperature, this.topP, this.maxTokens, this.safePrompt, this.randomSeed, this.responseFormat, this.stop, this.frequencyPenalty, this.presencePenalty, this.n, this.tools, this.toolChoice, this.toolCallbacks, this.tools, this.internalToolExecutionEnabled, - this.internalToolExecutionMaxAttempts, this.toolContext); + this.internalToolExecutionMaxIterations, this.toolContext); } @Override @@ -416,7 +416,7 @@ public boolean equals(Object obj) { && Objects.equals(this.toolCallbacks, other.toolCallbacks) && Objects.equals(this.toolNames, other.toolNames) && Objects.equals(this.internalToolExecutionEnabled, other.internalToolExecutionEnabled) - && Objects.equals(this.internalToolExecutionMaxAttempts, other.internalToolExecutionMaxAttempts) + && Objects.equals(this.internalToolExecutionMaxIterations, other.internalToolExecutionMaxIterations) && Objects.equals(this.toolContext, other.toolContext); } @@ -522,8 +522,8 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } - public Builder internalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { - this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); + public Builder internalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.options.setInternalToolExecutionMaxIterations(internalToolExecutionMaxIterations); return this; } diff --git a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatCompletionRequestTest.java b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatCompletionRequestTest.java index e37fa4b152b..8cd0bc6478c 100644 --- a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatCompletionRequestTest.java +++ b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatCompletionRequestTest.java @@ -74,7 +74,7 @@ void whenToolRuntimeOptionsThenMergeWithDefaults() { MistralAiChatOptions defaultOptions = MistralAiChatOptions.builder() .model("DEFAULT_MODEL") .internalToolExecutionEnabled(true) - .internalToolExecutionMaxAttempts(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS) + .internalToolExecutionMaxIterations(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS) .toolCallbacks(new TestToolCallback("tool1"), new TestToolCallback("tool2")) .toolNames("tool1", "tool2") .toolContext(Map.of("key1", "value1", "key2", "valueA")) @@ -87,7 +87,7 @@ void whenToolRuntimeOptionsThenMergeWithDefaults() { MistralAiChatOptions runtimeOptions = MistralAiChatOptions.builder() .internalToolExecutionEnabled(false) - .internalToolExecutionMaxAttempts(3) + .internalToolExecutionMaxIterations(3) .toolCallbacks(new TestToolCallback("tool3"), new TestToolCallback("tool4")) .toolNames("tool3") .toolContext(Map.of("key2", "valueB")) @@ -96,7 +96,7 @@ void whenToolRuntimeOptionsThenMergeWithDefaults() { assertThat(((ToolCallingChatOptions) prompt.getOptions())).isNotNull(); assertThat(((ToolCallingChatOptions) prompt.getOptions()).getInternalToolExecutionEnabled()).isFalse(); - assertThat(((ToolCallingChatOptions) prompt.getOptions()).getInternalToolExecutionMaxAttempts()).isEqualTo(3); + assertThat(((ToolCallingChatOptions) prompt.getOptions()).getInternalToolExecutionMaxIterations()).isEqualTo(3); assertThat(((ToolCallingChatOptions) prompt.getOptions()).getToolCallbacks()).hasSize(2); assertThat(((ToolCallingChatOptions) prompt.getOptions()).getToolCallbacks() .stream() diff --git a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatOptionsTests.java b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatOptionsTests.java index e94554695eb..2b35d0cb989 100644 --- a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatOptionsTests.java +++ b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatOptionsTests.java @@ -14,24 +14,24 @@ class MistralAiChatOptionsTests { void testOptionsDefault() { var options = new MistralAiChatOptions(); - assertThat(options.getInternalToolExecutionMaxAttempts()) - .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); + assertThat(options.getInternalToolExecutionMaxIterations()) + .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS); } @Test void testOptionsCustom() { var options = new MistralAiChatOptions(); - options.setInternalToolExecutionMaxAttempts(3); + options.setInternalToolExecutionMaxIterations(3); - assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); + assertThat(options.getInternalToolExecutionMaxIterations()).isEqualTo(3); } @Test void testBuilder() { - var options = MistralAiChatOptions.builder().internalToolExecutionMaxAttempts(3).build(); + var options = MistralAiChatOptions.builder().internalToolExecutionMaxIterations(3).build(); - assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); + assertThat(options.getInternalToolExecutionMaxIterations()).isEqualTo(3); } } diff --git a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java index f1153565010..2a9393a068c 100644 --- a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java +++ b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java @@ -225,7 +225,7 @@ public ChatResponse call(Prompt prompt) { return this.internalCall(requestPrompt, null, 1); } - private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { + private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int iterations) { OllamaApi.ChatRequest request = ollamaChatRequest(prompt, false); @@ -268,7 +268,7 @@ private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespon }); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, iterations)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. @@ -280,7 +280,7 @@ private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespon else { // Send the tool execution result back to the model. return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response, attempts + 1); + response, iterations + 1); } } @@ -295,7 +295,7 @@ public Flux stream(Prompt prompt) { return this.internalStream(requestPrompt, null, 1); } - private Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { + private Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int iterations) { return Flux.deferContextual(contextView -> { OllamaApi.ChatRequest request = ollamaChatRequest(prompt, true); @@ -340,7 +340,7 @@ private Flux internalStream(Prompt prompt, ChatResponse previousCh // @formatter:off Flux chatResponseFlux = chatResponse.flatMap(response -> { - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, iterations)) { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous return Flux.defer(() -> { @@ -354,7 +354,7 @@ private Flux internalStream(Prompt prompt, ChatResponse previousCh else { // Send the tool execution result back to the model. return this.internalStream(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response, attempts + 1); + response, iterations + 1); } }).subscribeOn(Schedulers.boundedElastic()); } @@ -396,9 +396,9 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setInternalToolExecutionEnabled( ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); - requestOptions.setInternalToolExecutionMaxAttempts( - ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), - this.defaultOptions.getInternalToolExecutionMaxAttempts())); + requestOptions.setInternalToolExecutionMaxIterations( + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxIterations(), + this.defaultOptions.getInternalToolExecutionMaxIterations())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -409,7 +409,7 @@ Prompt buildRequestPrompt(Prompt prompt) { else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); requestOptions - .setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); + .setInternalToolExecutionMaxIterations(this.defaultOptions.getInternalToolExecutionMaxIterations()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); diff --git a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java index edacc96cc74..68ce993b3bf 100644 --- a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java +++ b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java @@ -323,7 +323,7 @@ public class OllamaOptions implements ToolCallingChatOptions, EmbeddingOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; + private Integer internalToolExecutionMaxIterations = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS; /** * Tool Function Callbacks to register with the ChatModel. @@ -401,7 +401,7 @@ public static OllamaOptions fromOptions(OllamaOptions fromOptions) { .stop(fromOptions.getStop()) .toolNames(fromOptions.getToolNames()) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) - .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) + .internalToolExecutionMaxIterations(fromOptions.getInternalToolExecutionMaxIterations()) .toolCallbacks(fromOptions.getToolCallbacks()) .toolContext(fromOptions.getToolContext()).build(); } @@ -752,13 +752,13 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut } @Override - public Integer getInternalToolExecutionMaxAttempts() { - return this.internalToolExecutionMaxAttempts; + public Integer getInternalToolExecutionMaxIterations() { + return this.internalToolExecutionMaxIterations; } @Override - public void setInternalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { - this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; + public void setInternalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; } @Override @@ -824,7 +824,7 @@ public boolean equals(Object o) { && Objects.equals(this.penalizeNewline, that.penalizeNewline) && Objects.equals(this.stop, that.stop) && Objects.equals(this.toolCallbacks, that.toolCallbacks) && Objects.equals(this.internalToolExecutionEnabled, that.internalToolExecutionEnabled) - && Objects.equals(this.internalToolExecutionMaxAttempts, that.internalToolExecutionMaxAttempts) + && Objects.equals(this.internalToolExecutionMaxIterations, that.internalToolExecutionMaxIterations) && Objects.equals(this.toolNames, that.toolNames) && Objects.equals(this.toolContext, that.toolContext); } @@ -836,7 +836,7 @@ public int hashCode() { this.topP, this.minP, this.tfsZ, this.typicalP, this.repeatLastN, this.temperature, this.repeatPenalty, this.presencePenalty, this.frequencyPenalty, this.mirostat, this.mirostatTau, this.mirostatEta, this.penalizeNewline, this.stop, this.toolCallbacks, this.toolNames, this.internalToolExecutionEnabled, - this.internalToolExecutionMaxAttempts, this.toolContext); + this.internalToolExecutionMaxIterations, this.toolContext); } public static class Builder { @@ -1045,8 +1045,8 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } - public Builder internalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { - this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); + public Builder internalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.options.setInternalToolExecutionMaxIterations(internalToolExecutionMaxIterations); return this; } diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatRequestTests.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatRequestTests.java index 04fd3c97d72..1892db91aa9 100644 --- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatRequestTests.java +++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatRequestTests.java @@ -47,7 +47,7 @@ void whenToolRuntimeOptionsThenMergeWithDefaults() { OllamaOptions defaultOptions = OllamaOptions.builder() .model("MODEL_NAME") .internalToolExecutionEnabled(true) - .internalToolExecutionMaxAttempts(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS) + .internalToolExecutionMaxIterations(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS) .toolCallbacks(new TestToolCallback("tool1"), new TestToolCallback("tool2")) .toolNames("tool1", "tool2") .toolContext(Map.of("key1", "value1", "key2", "valueA")) @@ -59,7 +59,7 @@ void whenToolRuntimeOptionsThenMergeWithDefaults() { OllamaOptions runtimeOptions = OllamaOptions.builder() .internalToolExecutionEnabled(false) - .internalToolExecutionMaxAttempts(3) + .internalToolExecutionMaxIterations(3) .toolCallbacks(new TestToolCallback("tool3"), new TestToolCallback("tool4")) .toolNames("tool3") .toolContext(Map.of("key2", "valueB")) @@ -68,7 +68,7 @@ void whenToolRuntimeOptionsThenMergeWithDefaults() { assertThat(((ToolCallingChatOptions) prompt.getOptions())).isNotNull(); assertThat(((ToolCallingChatOptions) prompt.getOptions()).getInternalToolExecutionEnabled()).isFalse(); - assertThat(((ToolCallingChatOptions) prompt.getOptions()).getInternalToolExecutionMaxAttempts()).isEqualTo(3); + assertThat(((ToolCallingChatOptions) prompt.getOptions()).getInternalToolExecutionMaxIterations()).isEqualTo(3); assertThat(((ToolCallingChatOptions) prompt.getOptions()).getToolCallbacks()).hasSize(2); assertThat(((ToolCallingChatOptions) prompt.getOptions()).getToolCallbacks() .stream() diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java index fa2ba0bf4e6..64262522ded 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java @@ -187,7 +187,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons return internalCall(prompt, previousChatResponse, 1); } - public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { + public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int iterations) { ChatCompletionRequest request = createRequest(prompt, false); @@ -246,7 +246,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons }); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, iterations)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. @@ -258,7 +258,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons else { // Send the tool execution result back to the model. return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response, attempts + 1); + response, iterations + 1); } } @@ -277,7 +277,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha return internalStream(prompt, previousChatResponse, 1); } - public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { + public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int iterations) { return Flux.deferContextual(contextView -> { ChatCompletionRequest request = createRequest(prompt, true); @@ -372,7 +372,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha // @formatter:off Flux flux = chatResponse.flatMap(response -> { - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, iterations)) { return Flux.defer(() -> { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous @@ -386,7 +386,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha else { // Send the tool execution result back to the model. return this.internalStream(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response, attempts + 1); + response, iterations + 1); } }).subscribeOn(Schedulers.boundedElastic()); } @@ -530,9 +530,9 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setInternalToolExecutionEnabled( ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); - requestOptions.setInternalToolExecutionMaxAttempts( - ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), - this.defaultOptions.getInternalToolExecutionMaxAttempts())); + requestOptions.setInternalToolExecutionMaxIterations( + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxIterations(), + this.defaultOptions.getInternalToolExecutionMaxIterations())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -544,7 +544,7 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setHttpHeaders(this.defaultOptions.getHttpHeaders()); requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); requestOptions - .setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); + .setInternalToolExecutionMaxIterations(this.defaultOptions.getInternalToolExecutionMaxIterations()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java index b0257016ab1..16f1c572e1d 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java @@ -220,7 +220,7 @@ public class OpenAiChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; + private Integer internalToolExecutionMaxIterations = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS; /** * Optional HTTP headers to be added to the chat completion request. @@ -266,7 +266,7 @@ public static OpenAiChatOptions fromOptions(OpenAiChatOptions fromOptions) { .toolNames(fromOptions.getToolNames() != null ? new HashSet<>(fromOptions.getToolNames()) : null) .httpHeaders(fromOptions.getHttpHeaders() != null ? new HashMap<>(fromOptions.getHttpHeaders()) : null) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) - .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) + .internalToolExecutionMaxIterations(fromOptions.getInternalToolExecutionMaxIterations()) .toolContext(fromOptions.getToolContext() != null ? new HashMap<>(fromOptions.getToolContext()) : null) .store(fromOptions.getStore()) .metadata(fromOptions.getMetadata()) @@ -510,13 +510,13 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut } @Override - public Integer getInternalToolExecutionMaxAttempts() { - return this.internalToolExecutionMaxAttempts; + public Integer getInternalToolExecutionMaxIterations() { + return this.internalToolExecutionMaxIterations; } @Override - public void setInternalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { - this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; + public void setInternalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; } public Map getHttpHeaders() { @@ -588,7 +588,7 @@ public int hashCode() { this.maxTokens, this.maxCompletionTokens, this.n, this.presencePenalty, this.responseFormat, this.streamOptions, this.seed, this.stop, this.temperature, this.topP, this.tools, this.toolChoice, this.user, this.parallelToolCalls, this.toolCallbacks, this.toolNames, this.httpHeaders, - this.internalToolExecutionEnabled, this.internalToolExecutionMaxAttempts, this.toolContext, + this.internalToolExecutionEnabled, this.internalToolExecutionMaxIterations, this.toolContext, this.outputModalities, this.outputAudio, this.store, this.metadata, this.reasoningEffort, this.webSearchOptions); } @@ -619,7 +619,7 @@ public boolean equals(Object o) { && Objects.equals(this.httpHeaders, other.httpHeaders) && Objects.equals(this.toolContext, other.toolContext) && Objects.equals(this.internalToolExecutionEnabled, other.internalToolExecutionEnabled) - && Objects.equals(this.internalToolExecutionMaxAttempts, other.internalToolExecutionMaxAttempts) + && Objects.equals(this.internalToolExecutionMaxIterations, other.internalToolExecutionMaxIterations) && Objects.equals(this.outputModalities, other.outputModalities) && Objects.equals(this.outputAudio, other.outputAudio) && Objects.equals(this.store, other.store) && Objects.equals(this.metadata, other.metadata) @@ -782,8 +782,8 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } - public Builder internalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { - this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); + public Builder internalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.options.setInternalToolExecutionMaxIterations(internalToolExecutionMaxIterations); return this; } diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/ChatCompletionRequestTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/ChatCompletionRequestTests.java index 559e7f7e4fe..e5885569cdf 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/ChatCompletionRequestTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/ChatCompletionRequestTests.java @@ -45,7 +45,7 @@ void whenToolRuntimeOptionsThenMergeWithDefaults() { OpenAiChatOptions defaultOptions = OpenAiChatOptions.builder() .model("DEFAULT_MODEL") .internalToolExecutionEnabled(true) - .internalToolExecutionMaxAttempts(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS) + .internalToolExecutionMaxIterations(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS) .toolCallbacks(new TestToolCallback("tool1"), new TestToolCallback("tool2")) .toolNames("tool1", "tool2") .toolContext(Map.of("key1", "value1", "key2", "valueA")) @@ -58,7 +58,7 @@ void whenToolRuntimeOptionsThenMergeWithDefaults() { OpenAiChatOptions runtimeOptions = OpenAiChatOptions.builder() .internalToolExecutionEnabled(false) - .internalToolExecutionMaxAttempts(10) + .internalToolExecutionMaxIterations(10) .toolCallbacks(new TestToolCallback("tool3"), new TestToolCallback("tool4")) .toolNames("tool3") .toolContext(Map.of("key2", "valueB")) @@ -67,7 +67,8 @@ void whenToolRuntimeOptionsThenMergeWithDefaults() { assertThat(((ToolCallingChatOptions) prompt.getOptions())).isNotNull(); assertThat(((ToolCallingChatOptions) prompt.getOptions()).getInternalToolExecutionEnabled()).isFalse(); - assertThat(((ToolCallingChatOptions) prompt.getOptions()).getInternalToolExecutionMaxAttempts()).isEqualTo(10); + assertThat(((ToolCallingChatOptions) prompt.getOptions()).getInternalToolExecutionMaxIterations()) + .isEqualTo(10); assertThat(((ToolCallingChatOptions) prompt.getOptions()).getToolCallbacks()).hasSize(2); assertThat(((ToolCallingChatOptions) prompt.getOptions()).getToolCallbacks() .stream() diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiChatOptionsTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiChatOptionsTests.java index d73a2a09603..e47bbc79506 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiChatOptionsTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiChatOptionsTests.java @@ -81,7 +81,7 @@ void testBuilderWithAllFields() { .metadata(metadata) .reasoningEffort("medium") .internalToolExecutionEnabled(false) - .internalToolExecutionMaxAttempts(10) + .internalToolExecutionMaxIterations(10) .httpHeaders(Map.of("header1", "value1")) .toolContext(toolContext) .build(); @@ -91,7 +91,7 @@ void testBuilderWithAllFields() { "maxCompletionTokens", "n", "outputModalities", "outputAudio", "presencePenalty", "responseFormat", "streamOptions", "seed", "stop", "temperature", "topP", "tools", "toolChoice", "user", "parallelToolCalls", "store", "metadata", "reasoningEffort", "internalToolExecutionEnabled", - "internalToolExecutionMaxAttempts", "httpHeaders", "toolContext") + "internalToolExecutionMaxIterations", "httpHeaders", "toolContext") .containsExactly("test-model", 0.5, logitBias, true, 5, 100, 50, 2, outputModalities, outputAudio, 0.8, responseFormat, streamOptions, 12345, stopSequences, 0.7, 0.9, tools, toolChoice, "test-user", true, false, metadata, "medium", false, 10, Map.of("header1", "value1"), toolContext); @@ -141,7 +141,7 @@ void testCopy() { .metadata(metadata) .reasoningEffort("low") .internalToolExecutionEnabled(true) - .internalToolExecutionMaxAttempts(3) + .internalToolExecutionMaxIterations(3) .httpHeaders(Map.of("header1", "value1")) .build(); @@ -190,7 +190,7 @@ void testSetters() { options.setMetadata(metadata); options.setReasoningEffort("high"); options.setInternalToolExecutionEnabled(false); - options.setInternalToolExecutionMaxAttempts(3); + options.setInternalToolExecutionMaxIterations(3); options.setHttpHeaders(Map.of("header2", "value2")); assertThat(options.getModel()).isEqualTo("test-model"); @@ -218,7 +218,7 @@ void testSetters() { assertThat(options.getMetadata()).isEqualTo(metadata); assertThat(options.getReasoningEffort()).isEqualTo("high"); assertThat(options.getInternalToolExecutionEnabled()).isFalse(); - assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); + assertThat(options.getInternalToolExecutionMaxIterations()).isEqualTo(3); assertThat(options.getHttpHeaders()).isEqualTo(Map.of("header2", "value2")); assertThat(options.getStreamUsage()).isTrue(); options.setStreamUsage(false); @@ -258,8 +258,8 @@ void testDefaultValues() { assertThat(options.getReasoningEffort()).isNull(); assertThat(options.getToolCallbacks()).isNotNull().isEmpty(); assertThat(options.getInternalToolExecutionEnabled()).isNull(); - assertThat(options.getInternalToolExecutionMaxAttempts()) - .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); + assertThat(options.getInternalToolExecutionMaxIterations()) + .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS); assertThat(options.getHttpHeaders()).isNotNull().isEmpty(); assertThat(options.getToolContext()).isEqualTo(new HashMap<>()); assertThat(options.getStreamUsage()).isFalse(); diff --git a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java index 146f2795971..ed709e58061 100644 --- a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java +++ b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java @@ -398,7 +398,7 @@ private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespon return this.internalCall(prompt, previousChatResponse, 1); } - private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int attempts) { + private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse, int iterations) { ChatModelObservationContext observationContext = ChatModelObservationContext.builder() .prompt(prompt) @@ -431,7 +431,7 @@ private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespon return chatResponse; })); - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, iterations)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. @@ -443,7 +443,7 @@ private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespon else { // Send the tool execution result back to the model. return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), - response, attempts + 1); + response, iterations + 1); } } @@ -475,9 +475,9 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setInternalToolExecutionEnabled( ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); - requestOptions.setInternalToolExecutionMaxAttempts( - ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), - this.defaultOptions.getInternalToolExecutionMaxAttempts())); + requestOptions.setInternalToolExecutionMaxIterations( + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxIterations(), + this.defaultOptions.getInternalToolExecutionMaxIterations())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -493,7 +493,7 @@ Prompt buildRequestPrompt(Prompt prompt) { else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); requestOptions - .setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); + .setInternalToolExecutionMaxIterations(this.defaultOptions.getInternalToolExecutionMaxIterations()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); @@ -517,7 +517,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha return this.internalStream(prompt, previousChatResponse, 1); } - public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int attempts) { + public Flux internalStream(Prompt prompt, ChatResponse previousChatResponse, int iterations) { return Flux.deferContextual(contextView -> { ChatModelObservationContext observationContext = ChatModelObservationContext.builder() @@ -553,7 +553,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha // @formatter:off Flux flux = chatResponseFlux.flatMap(response -> { - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, attempts)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response, iterations)) { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous return Flux.defer(() -> { @@ -569,7 +569,7 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha return this.internalStream( new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), response, - attempts + 1); + iterations + 1); } }).subscribeOn(Schedulers.boundedElastic()); } diff --git a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java index 0de968ffec0..46315a9dec2 100644 --- a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java +++ b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java @@ -128,7 +128,7 @@ public class VertexAiGeminiChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; + private Integer internalToolExecutionMaxIterations = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS; @JsonIgnore private Map toolContext = new HashMap<>(); @@ -165,7 +165,7 @@ public static VertexAiGeminiChatOptions fromOptions(VertexAiGeminiChatOptions fr options.setGoogleSearchRetrieval(fromOptions.getGoogleSearchRetrieval()); options.setSafetySettings(fromOptions.getSafetySettings()); options.setInternalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()); - options.setInternalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()); + options.setInternalToolExecutionMaxIterations(fromOptions.getInternalToolExecutionMaxIterations()); options.setToolContext(fromOptions.getToolContext()); return options; } @@ -287,13 +287,13 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut } @Override - public Integer getInternalToolExecutionMaxAttempts() { - return this.internalToolExecutionMaxAttempts; + public Integer getInternalToolExecutionMaxIterations() { + return this.internalToolExecutionMaxIterations; } @Override - public void setInternalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { - this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; + public void setInternalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; } @Override @@ -361,7 +361,7 @@ public boolean equals(Object o) { && Objects.equals(this.toolNames, that.toolNames) && Objects.equals(this.safetySettings, that.safetySettings) && Objects.equals(this.internalToolExecutionEnabled, that.internalToolExecutionEnabled) - && Objects.equals(this.internalToolExecutionMaxAttempts, that.internalToolExecutionMaxAttempts) + && Objects.equals(this.internalToolExecutionMaxIterations, that.internalToolExecutionMaxIterations) && Objects.equals(this.toolContext, that.toolContext); } @@ -370,7 +370,7 @@ public int hashCode() { return Objects.hash(this.stopSequences, this.temperature, this.topP, this.topK, this.candidateCount, this.frequencyPenalty, this.presencePenalty, this.maxOutputTokens, this.model, this.responseMimeType, this.toolCallbacks, this.toolNames, this.googleSearchRetrieval, this.safetySettings, - this.internalToolExecutionEnabled, this.internalToolExecutionMaxAttempts, this.toolContext); + this.internalToolExecutionEnabled, this.internalToolExecutionMaxIterations, this.toolContext); } @Override @@ -494,8 +494,8 @@ public Builder internalToolExecutionEnabled(boolean internalToolExecutionEnabled return this; } - public Builder internalToolExecutionMaxAttempts(Integer internalToolExecutionMaxAttempts) { - this.options.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; + public Builder internalToolExecutionMaxIterations(Integer internalToolExecutionMaxIterations) { + this.options.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; return this; } diff --git a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptionsTest.java b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptionsTest.java index 4406eaba005..d313890c942 100644 --- a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptionsTest.java +++ b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptionsTest.java @@ -11,33 +11,33 @@ class VertexAiGeminiChatOptionsTest { void optionsDefault() { var options = new VertexAiGeminiChatOptions(); - assertEquals(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS, - options.getInternalToolExecutionMaxAttempts()); + assertEquals(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS, + options.getInternalToolExecutionMaxIterations()); } @Test void builderDefault() { var options = VertexAiGeminiChatOptions.builder().build(); - assertEquals(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS, - options.getInternalToolExecutionMaxAttempts()); + assertEquals(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS, + options.getInternalToolExecutionMaxIterations()); } @Test void testBuilder() { - var options = VertexAiGeminiChatOptions.builder().internalToolExecutionMaxAttempts(3).build(); + var options = VertexAiGeminiChatOptions.builder().internalToolExecutionMaxIterations(3).build(); - assertEquals(3, options.getInternalToolExecutionMaxAttempts()); + assertEquals(3, options.getInternalToolExecutionMaxIterations()); } @Test void fromOptions() { var original = new VertexAiGeminiChatOptions(); - original.setInternalToolExecutionMaxAttempts(3); + original.setInternalToolExecutionMaxIterations(3); var copied = VertexAiGeminiChatOptions.fromOptions(original); - assertEquals(original.getInternalToolExecutionMaxAttempts(), copied.getInternalToolExecutionMaxAttempts()); + assertEquals(original.getInternalToolExecutionMaxIterations(), copied.getInternalToolExecutionMaxIterations()); } } diff --git a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatModel.java b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatModel.java index fef9458808f..3f2ff1242ee 100644 --- a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatModel.java +++ b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatModel.java @@ -242,7 +242,7 @@ public ChatResponse call(Prompt prompt) { return internalCall(requestPrompt, 1); } - private ChatResponse internalCall(Prompt requestPrompt, int attempts) { + private ChatResponse internalCall(Prompt requestPrompt, int iterations) { ChatCompletionRequest request = createRequest(requestPrompt, false); ChatModelObservationContext observationContext = ChatModelObservationContext.builder() @@ -285,7 +285,7 @@ private ChatResponse internalCall(Prompt requestPrompt, int attempts) { return chatResponse; }); if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(requestPrompt.getOptions(), response, - attempts)) { + iterations)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(requestPrompt, response); if (toolExecutionResult.returnDirect()) { // Return tool execution result directly to the client. @@ -298,7 +298,7 @@ private ChatResponse internalCall(Prompt requestPrompt, int attempts) { // Send the tool execution result back to the model. return this.internalCall( new Prompt(toolExecutionResult.conversationHistory(), requestPrompt.getOptions()), - attempts + 1); + iterations + 1); } } return response; @@ -314,7 +314,7 @@ public Flux stream(Prompt prompt) { return internalStream(prompt, 1); } - private Flux internalStream(Prompt prompt, int attempts) { + private Flux internalStream(Prompt prompt, int iterations) { return Flux.deferContextual(contextView -> { // Before moving any further, build the final request Prompt, // merging runtime and default options. @@ -369,7 +369,7 @@ private Flux internalStream(Prompt prompt, int attempts) { // @formatter:off Flux flux = chatResponse.flatMap(response -> { - if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(requestPrompt.getOptions(), response, attempts)) { + if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(requestPrompt.getOptions(), response, iterations)) { return Flux.defer(() -> { // FIXME: bounded elastic needs to be used since tool calling // is currently only synchronous @@ -384,7 +384,7 @@ private Flux internalStream(Prompt prompt, int attempts) { // Send the tool execution result back to the model. return this.internalStream( new Prompt(toolExecutionResult.conversationHistory(), requestPrompt.getOptions()), - attempts + 1); + iterations + 1); } }).subscribeOn(Schedulers.boundedElastic()); } @@ -464,9 +464,9 @@ Prompt buildRequestPrompt(Prompt prompt) { requestOptions.setInternalToolExecutionEnabled( ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionEnabled(), this.defaultOptions.getInternalToolExecutionEnabled())); - requestOptions.setInternalToolExecutionMaxAttempts( - ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxAttempts(), - this.defaultOptions.getInternalToolExecutionMaxAttempts())); + requestOptions.setInternalToolExecutionMaxIterations( + ModelOptionsUtils.mergeOption(runtimeOptions.getInternalToolExecutionMaxIterations(), + this.defaultOptions.getInternalToolExecutionMaxIterations())); requestOptions.setToolNames(ToolCallingChatOptions.mergeToolNames(runtimeOptions.getToolNames(), this.defaultOptions.getToolNames())); requestOptions.setToolCallbacks(ToolCallingChatOptions.mergeToolCallbacks(runtimeOptions.getToolCallbacks(), @@ -477,7 +477,7 @@ Prompt buildRequestPrompt(Prompt prompt) { else { requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled()); requestOptions - .setInternalToolExecutionMaxAttempts(this.defaultOptions.getInternalToolExecutionMaxAttempts()); + .setInternalToolExecutionMaxIterations(this.defaultOptions.getInternalToolExecutionMaxIterations()); requestOptions.setToolNames(this.defaultOptions.getToolNames()); requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks()); requestOptions.setToolContext(this.defaultOptions.getToolContext()); diff --git a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java index cb15f4bb52a..1b306d619f3 100644 --- a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java +++ b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java @@ -127,7 +127,7 @@ public class ZhiPuAiChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @JsonIgnore - private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; + private Integer internalToolExecutionMaxIterations = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS; @JsonIgnore private Map toolContext = new HashMap<>(); @@ -152,7 +152,7 @@ public static ZhiPuAiChatOptions fromOptions(ZhiPuAiChatOptions fromOptions) { .toolCallbacks(fromOptions.getToolCallbacks()) .toolNames(fromOptions.getToolNames()) .internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled()) - .internalToolExecutionMaxAttempts(fromOptions.getInternalToolExecutionMaxAttempts()) + .internalToolExecutionMaxIterations(fromOptions.getInternalToolExecutionMaxIterations()) .toolContext(fromOptions.getToolContext()) .build(); } @@ -313,13 +313,13 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut } @Override - public Integer getInternalToolExecutionMaxAttempts() { - return this.internalToolExecutionMaxAttempts; + public Integer getInternalToolExecutionMaxIterations() { + return this.internalToolExecutionMaxIterations; } @Override - public void setInternalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { - this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; + public void setInternalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; } @Override @@ -346,8 +346,8 @@ public int hashCode() { result = prime * result + ((this.user == null) ? 0 : this.user.hashCode()); result = prime * result + ((this.internalToolExecutionEnabled == null) ? 0 : this.internalToolExecutionEnabled.hashCode()); - result = prime * result + ((this.internalToolExecutionMaxAttempts == null) ? 0 - : this.internalToolExecutionMaxAttempts.hashCode()); + result = prime * result + ((this.internalToolExecutionMaxIterations == null) ? 0 + : this.internalToolExecutionMaxIterations.hashCode()); result = prime * result + ((this.toolCallbacks == null) ? 0 : this.toolCallbacks.hashCode()); result = prime * result + ((this.toolNames == null) ? 0 : this.toolNames.hashCode()); result = prime * result + ((this.toolContext == null) ? 0 : this.toolContext.hashCode()); @@ -454,12 +454,12 @@ else if (!this.doSample.equals(other.doSample)) { else if (!this.internalToolExecutionEnabled.equals(other.internalToolExecutionEnabled)) { return false; } - if (this.internalToolExecutionMaxAttempts == null) { - if (other.internalToolExecutionMaxAttempts != null) { + if (this.internalToolExecutionMaxIterations == null) { + if (other.internalToolExecutionMaxIterations != null) { return false; } } - else if (!this.internalToolExecutionMaxAttempts.equals(other.internalToolExecutionMaxAttempts)) { + else if (!this.internalToolExecutionMaxIterations.equals(other.internalToolExecutionMaxIterations)) { return false; } if (this.toolContext == null) { @@ -493,10 +493,10 @@ public ToolCallingChatOptions merge(ChatOptions options) { builder.internalToolExecutionEnabled(toolCallingChatOptions.getInternalToolExecutionEnabled() != null ? (toolCallingChatOptions).getInternalToolExecutionEnabled() : this.getInternalToolExecutionEnabled()); - builder - .internalToolExecutionMaxAttempts(toolCallingChatOptions.getInternalToolExecutionMaxAttempts() != null - ? toolCallingChatOptions.getInternalToolExecutionMaxAttempts() - : this.getInternalToolExecutionMaxAttempts()); + builder.internalToolExecutionMaxIterations( + toolCallingChatOptions.getInternalToolExecutionMaxIterations() != null + ? toolCallingChatOptions.getInternalToolExecutionMaxIterations() + : this.getInternalToolExecutionMaxIterations()); Set toolNames = new HashSet<>(); if (this.toolNames != null) { @@ -527,7 +527,7 @@ public ToolCallingChatOptions merge(ChatOptions options) { } else { builder.internalToolExecutionEnabled(this.internalToolExecutionEnabled); - builder.internalToolExecutionMaxAttempts(this.internalToolExecutionMaxAttempts); + builder.internalToolExecutionMaxIterations(this.internalToolExecutionMaxIterations); builder.toolNames(this.toolNames != null ? new HashSet<>(this.toolNames) : null); builder.toolCallbacks(this.toolCallbacks != null ? new ArrayList<>(this.toolCallbacks) : null); builder.toolContext(this.toolContext != null ? new HashMap<>(this.toolContext) : null); @@ -633,8 +633,8 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut return this; } - public Builder internalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { - this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); + public Builder internalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.options.setInternalToolExecutionMaxIterations(internalToolExecutionMaxIterations); return this; } diff --git a/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/chat/ZhiPuAiChatOptionsTests.java b/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/chat/ZhiPuAiChatOptionsTests.java index 29b38e42e18..0ba7ad99a76 100644 --- a/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/chat/ZhiPuAiChatOptionsTests.java +++ b/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/chat/ZhiPuAiChatOptionsTests.java @@ -15,22 +15,22 @@ class ZhiPuAiChatOptionsTests { void testDefaultValue() { var options = new ZhiPuAiChatOptions(); - assertThat(options.getInternalToolExecutionMaxAttempts()) - .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); + assertThat(options.getInternalToolExecutionMaxIterations()) + .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS); } @Test void testSetter() { var options = new ZhiPuAiChatOptions(); - options.setInternalToolExecutionMaxAttempts(3); - assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); + options.setInternalToolExecutionMaxIterations(3); + assertThat(options.getInternalToolExecutionMaxIterations()).isEqualTo(3); } @Test void testBuilder() { - var options = ZhiPuAiChatOptions.builder().internalToolExecutionMaxAttempts(3).build(); + var options = ZhiPuAiChatOptions.builder().internalToolExecutionMaxIterations(3).build(); - assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); + assertThat(options.getInternalToolExecutionMaxIterations()).isEqualTo(3); } } diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java index c41e5528911..ac551fea104 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptions.java @@ -48,7 +48,7 @@ public class DefaultToolCallingChatOptions implements ToolCallingChatOptions { private Boolean internalToolExecutionEnabled; @Nullable - private Integer internalToolExecutionMaxAttempts = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS; + private Integer internalToolExecutionMaxIterations = ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS; @Nullable private String model; @@ -123,13 +123,13 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut } @Override - public Integer getInternalToolExecutionMaxAttempts() { - return this.internalToolExecutionMaxAttempts; + public Integer getInternalToolExecutionMaxIterations() { + return this.internalToolExecutionMaxIterations; } @Override - public void setInternalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts) { - this.internalToolExecutionMaxAttempts = internalToolExecutionMaxAttempts; + public void setInternalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations) { + this.internalToolExecutionMaxIterations = internalToolExecutionMaxIterations; } @Override @@ -220,7 +220,7 @@ public T copy() { options.setToolNames(getToolNames()); options.setToolContext(getToolContext()); options.setInternalToolExecutionEnabled(getInternalToolExecutionEnabled()); - options.setInternalToolExecutionMaxAttempts(getInternalToolExecutionMaxAttempts()); + options.setInternalToolExecutionMaxIterations(getInternalToolExecutionMaxIterations()); options.setModel(getModel()); options.setFrequencyPenalty(getFrequencyPenalty()); options.setMaxTokens(getMaxTokens()); @@ -293,9 +293,9 @@ public ToolCallingChatOptions.Builder internalToolExecutionEnabled( } @Override - public ToolCallingChatOptions.Builder internalToolExecutionMaxAttempts( - @Nullable Integer internalToolExecutionMaxAttempts) { - this.options.setInternalToolExecutionMaxAttempts(internalToolExecutionMaxAttempts); + public ToolCallingChatOptions.Builder internalToolExecutionMaxIterations( + @Nullable Integer internalToolExecutionMaxIterations) { + this.options.setInternalToolExecutionMaxIterations(internalToolExecutionMaxIterations); return this; } diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java index 34d5a2b57a3..02c1472b02e 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java @@ -49,7 +49,7 @@ public interface ToolCallingChatOptions extends ChatOptions { */ int TOOL_EXECUTION_NO_LIMIT = Integer.MAX_VALUE; - int DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS = TOOL_EXECUTION_NO_LIMIT; + int DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS = TOOL_EXECUTION_NO_LIMIT; /** * ToolCallbacks to be registered with the ChatModel. @@ -85,18 +85,18 @@ public interface ToolCallingChatOptions extends ChatOptions { void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecutionEnabled); /** - * Get the maximum number of attempts for tool execution. - * @return the maximum number of attempts. + * Get the maximum number of iteration for tool execution. + * @return the maximum number of iteration. * @see #getInternalToolExecutionEnabled() */ @Nullable - Integer getInternalToolExecutionMaxAttempts(); + Integer getInternalToolExecutionMaxIterations(); /** - * Set the maximum number of attempts for tool execution. - * @param internalToolExecutionMaxAttempts the maximum number of attempts. + * Set the maximum number of iteration for tool execution. + * @param internalToolExecutionMaxIterations the maximum number of iteration. */ - void setInternalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts); + void setInternalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations); /** * Get the configured tool context. @@ -131,16 +131,16 @@ static boolean isInternalToolExecutionEnabled(ChatOptions chatOptions) { return internalToolExecutionEnabled; } - static boolean isInternalToolExecutionEnabled(ChatOptions chatOptions, int attempts) { + static boolean isInternalToolExecutionEnabled(ChatOptions chatOptions, int iterations) { boolean isInternalToolExecutionEnabled = isInternalToolExecutionEnabled(chatOptions); if (!isInternalToolExecutionEnabled) { return false; } if (chatOptions instanceof ToolCallingChatOptions toolCallingChatOptions - && toolCallingChatOptions.getInternalToolExecutionMaxAttempts() != null) { - int maxAttempts = toolCallingChatOptions.getInternalToolExecutionMaxAttempts(); - return attempts <= maxAttempts; + && toolCallingChatOptions.getInternalToolExecutionMaxIterations() != null) { + int maxIterations = toolCallingChatOptions.getInternalToolExecutionMaxIterations(); + return iterations <= maxIterations; } return DEFAULT_TOOL_EXECUTION_ENABLED; @@ -217,10 +217,10 @@ interface Builder extends ChatOptions.Builder { /** * the maximum number of attempts for tool execution. - * @param internalToolExecutionMaxAttempts the maximum number of attempts. + * @param internalToolExecutionMaxIterations the maximum number of iteration. * @return the {@link ToolCallingChatOptions} Builder. */ - Builder internalToolExecutionMaxAttempts(@Nullable Integer internalToolExecutionMaxAttempts); + Builder internalToolExecutionMaxIterations(@Nullable Integer internalToolExecutionMaxIterations); /** * Add a {@link Map} of context values into tool context. diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java index 891f1d9dbc7..3dde3fc6bc6 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java @@ -103,8 +103,8 @@ default boolean isInternalToolExecutionEnabled(ChatOptions chatOptions, int atte } if (chatOptions instanceof ToolCallingChatOptions toolCallingChatOptions) { - return toolCallingChatOptions.getInternalToolExecutionMaxAttempts() == null - || attempts <= toolCallingChatOptions.getInternalToolExecutionMaxAttempts(); + return toolCallingChatOptions.getInternalToolExecutionMaxIterations() == null + || attempts <= toolCallingChatOptions.getInternalToolExecutionMaxIterations(); } return internalToolExecutionEnabled; } diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java index a3d9aba4f91..591132f9897 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java @@ -50,7 +50,7 @@ default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse * @param chatResponse The response from the chat model * @param attempts The number of attempts * @return true if tool execution should be performed, false otherwise - * @see ToolCallingChatOptions#getInternalToolExecutionMaxAttempts() + * @see ToolCallingChatOptions#getInternalToolExecutionMaxIterations() * @see #isToolExecutionRequired(ChatOptions, ChatResponse) */ default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse chatResponse, int attempts) { @@ -60,8 +60,8 @@ default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse } if (promptOptions instanceof ToolCallingChatOptions toolCallingChatOptions) { - return toolCallingChatOptions.getInternalToolExecutionMaxAttempts() == null - || attempts <= toolCallingChatOptions.getInternalToolExecutionMaxAttempts(); + return toolCallingChatOptions.getInternalToolExecutionMaxIterations() == null + || attempts <= toolCallingChatOptions.getInternalToolExecutionMaxIterations(); } return isToolExecutionRequired; } diff --git a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptionsTests.java b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptionsTests.java index d15de26bb63..309c74b92e8 100644 --- a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptionsTests.java +++ b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptionsTests.java @@ -141,7 +141,7 @@ void copyShouldCreateNewInstanceWithSameValues() { original.setToolNames(Set.of("tool1")); original.setToolContext(Map.of("key", "value")); original.setInternalToolExecutionEnabled(true); - original.setInternalToolExecutionMaxAttempts(ToolCallingChatOptions.TOOL_EXECUTION_NO_LIMIT); + original.setInternalToolExecutionMaxIterations(ToolCallingChatOptions.TOOL_EXECUTION_NO_LIMIT); original.setModel("gpt-4"); original.setTemperature(0.7); @@ -152,8 +152,8 @@ void copyShouldCreateNewInstanceWithSameValues() { assertThat(c.getToolNames()).isEqualTo(original.getToolNames()); assertThat(c.getToolContext()).isEqualTo(original.getToolContext()); assertThat(c.getInternalToolExecutionEnabled()).isEqualTo(original.getInternalToolExecutionEnabled()); - assertThat(c.getInternalToolExecutionMaxAttempts()) - .isEqualTo(original.getInternalToolExecutionMaxAttempts()); + assertThat(c.getInternalToolExecutionMaxIterations()) + .isEqualTo(original.getInternalToolExecutionMaxIterations()); assertThat(c.getModel()).isEqualTo(original.getModel()); assertThat(c.getTemperature()).isEqualTo(original.getTemperature()); }); @@ -184,7 +184,7 @@ void builderShouldCreateOptionsWithAllProperties() { .toolNames(Set.of("tool1")) .toolContext(context) .internalToolExecutionEnabled(true) - .internalToolExecutionMaxAttempts(3) + .internalToolExecutionMaxIterations(3) .model("gpt-4") .temperature(0.7) .maxTokens(100) @@ -200,7 +200,7 @@ void builderShouldCreateOptionsWithAllProperties() { assertThat(o.getToolNames()).containsExactly("tool1"); assertThat(o.getToolContext()).isEqualTo(context); assertThat(o.getInternalToolExecutionEnabled()).isTrue(); - assertThat(o.getInternalToolExecutionMaxAttempts()).isEqualTo(3); + assertThat(o.getInternalToolExecutionMaxIterations()).isEqualTo(3); assertThat(o.getModel()).isEqualTo("gpt-4"); assertThat(o.getTemperature()).isEqualTo(0.7); assertThat(o.getMaxTokens()).isEqualTo(100); @@ -241,11 +241,11 @@ void deprecatedMethodsShouldWorkCorrectly() { assertThat(options.getInternalToolExecutionEnabled()).isTrue(); // default value check - assertThat(options.getInternalToolExecutionMaxAttempts()) - .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ATTEMPTS); + assertThat(options.getInternalToolExecutionMaxIterations()) + .isEqualTo(ToolCallingChatOptions.DEFAULT_TOOL_EXECUTION_MAX_ITERATIONS); - options.setInternalToolExecutionMaxAttempts(3); - assertThat(options.getInternalToolExecutionMaxAttempts()).isEqualTo(3); + options.setInternalToolExecutionMaxIterations(3); + assertThat(options.getInternalToolExecutionMaxIterations()).isEqualTo(3); } } diff --git a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolCallingChatOptionsTests.java b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolCallingChatOptionsTests.java index 0f82700e516..8e17c5252d7 100644 --- a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolCallingChatOptionsTests.java +++ b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolCallingChatOptionsTests.java @@ -52,15 +52,15 @@ void whenToolCallingChatOptionsAndExecutionEnabledFalse() { } @Test - void whenToolCallingChatOptionsAndMaxAttemptsOver() { + void whenToolCallingChatOptionsAndMaxIterationsOver() { ToolCallingChatOptions options = new DefaultToolCallingChatOptions(); - options.setInternalToolExecutionMaxAttempts(1); + options.setInternalToolExecutionMaxIterations(1); // 3 > 1 assertThat(ToolCallingChatOptions.isInternalToolExecutionEnabled(options, 3)).isFalse(); } @Test - void whenToolCallingChatOptionsAndMaxAttemptsDefault() { + void whenToolCallingChatOptionsAndMaxIterationsDefault() { ToolCallingChatOptions options = new DefaultToolCallingChatOptions(); assertThat(ToolCallingChatOptions.isInternalToolExecutionEnabled(options, 1)).isTrue(); } diff --git a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityCheckerTest.java b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityCheckerTest.java index 190d51a092a..21b79860f4c 100644 --- a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityCheckerTest.java +++ b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityCheckerTest.java @@ -8,7 +8,6 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; class ToolExecutionEligibilityCheckerTest { @@ -18,7 +17,7 @@ void isToolExecutionRequired() { ToolCallingChatOptions promptOptions = ToolCallingChatOptions.builder().build(); ChatResponse chatResponse = new ChatResponse(List.of(new Generation(new AssistantMessage("test")))); - promptOptions.setInternalToolExecutionMaxAttempts(2); + promptOptions.setInternalToolExecutionMaxIterations(2); assertThat(checker.isToolExecutionRequired(promptOptions, chatResponse, 1)).isTrue(); assertThat(checker.isToolExecutionRequired(promptOptions, chatResponse, 2)).isTrue(); @@ -33,7 +32,7 @@ void isInternalToolExecutionEnabled() { ToolExecutionEligibilityChecker checker = new TestToolExecutionEligibilityChecker(); ToolCallingChatOptions promptOptions = ToolCallingChatOptions.builder().build(); - promptOptions.setInternalToolExecutionMaxAttempts(2); + promptOptions.setInternalToolExecutionMaxIterations(2); assertThat(checker.isInternalToolExecutionEnabled(promptOptions, 1)).isTrue(); assertThat(checker.isInternalToolExecutionEnabled(promptOptions, 2)).isTrue(); diff --git a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicateTests.java b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicateTests.java index 4f40d6aa732..7e1c15849f0 100644 --- a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicateTests.java +++ b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicateTests.java @@ -50,7 +50,7 @@ void whenIsToolExecutionRequiredWithAttempts() { ToolExecutionEligibilityPredicate predicate = new TestToolExecutionEligibilityPredicate(); ToolCallingChatOptions promptOptions = ToolCallingChatOptions.builder().build(); ChatResponse chatResponse = new ChatResponse(List.of(new Generation(new AssistantMessage("test")))); - promptOptions.setInternalToolExecutionMaxAttempts(2); + promptOptions.setInternalToolExecutionMaxIterations(2); assertThat(predicate.isToolExecutionRequired(promptOptions, chatResponse, 1)).isTrue(); assertThat(predicate.isToolExecutionRequired(promptOptions, chatResponse, 2)).isTrue(); From 80c2e264e7fb2b94dbfc2f450ee6e022cf1aca17 Mon Sep 17 00:00:00 2001 From: lambochen Date: Mon, 2 Jun 2025 17:39:21 +0300 Subject: [PATCH 25/25] rename iterations to toolExecutionIterations Signed-off-by: lambochen --- .../ai/model/tool/ToolCallingChatOptions.java | 4 ++-- .../tool/ToolExecutionEligibilityChecker.java | 18 +++++++++++------- .../ToolExecutionEligibilityPredicate.java | 9 +++++---- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java index 02c1472b02e..583f2f0d791 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolCallingChatOptions.java @@ -131,7 +131,7 @@ static boolean isInternalToolExecutionEnabled(ChatOptions chatOptions) { return internalToolExecutionEnabled; } - static boolean isInternalToolExecutionEnabled(ChatOptions chatOptions, int iterations) { + static boolean isInternalToolExecutionEnabled(ChatOptions chatOptions, int toolExecutionIterations) { boolean isInternalToolExecutionEnabled = isInternalToolExecutionEnabled(chatOptions); if (!isInternalToolExecutionEnabled) { return false; @@ -140,7 +140,7 @@ static boolean isInternalToolExecutionEnabled(ChatOptions chatOptions, int itera if (chatOptions instanceof ToolCallingChatOptions toolCallingChatOptions && toolCallingChatOptions.getInternalToolExecutionMaxIterations() != null) { int maxIterations = toolCallingChatOptions.getInternalToolExecutionMaxIterations(); - return iterations <= maxIterations; + return toolExecutionIterations <= maxIterations; } return DEFAULT_TOOL_EXECUTION_ENABLED; diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java index 3dde3fc6bc6..5c12046e1c4 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityChecker.java @@ -46,16 +46,19 @@ default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse /** * Determines if tool execution should be performed based on the prompt options and - * chat response and attempts. + * chat response and toolExecutionIterations. * @param promptOptions The options from the prompt * @param chatResponse The response from the chat model - * @param attempts The number of attempts to execute the tool + * @param toolExecutionIterations The number of toolExecutionIterations to execute the + * tool * @return true if tool execution should be performed, false otherwise */ - default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse chatResponse, int attempts) { + default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse chatResponse, + int toolExecutionIterations) { Assert.notNull(promptOptions, "promptOptions cannot be null"); Assert.notNull(chatResponse, "chatResponse cannot be null"); - return this.isInternalToolExecutionEnabled(promptOptions, attempts) && this.isToolCallResponse(chatResponse); + return this.isInternalToolExecutionEnabled(promptOptions, toolExecutionIterations) + && this.isToolCallResponse(chatResponse); } /** @@ -92,11 +95,12 @@ default boolean isInternalToolExecutionEnabled(ChatOptions chatOptions) { /** * Determines if tool execution should be performed by the Spring AI or by the client. * @param chatOptions The options from the chat - * @param attempts The number of attempts to execute the tool + * @param toolExecutionIterations The number of toolExecutionIterations to execute the + * tool * @return true if tool execution should be performed by Spring AI, false if it should * be performed by the client */ - default boolean isInternalToolExecutionEnabled(ChatOptions chatOptions, int attempts) { + default boolean isInternalToolExecutionEnabled(ChatOptions chatOptions, int toolExecutionIterations) { boolean internalToolExecutionEnabled = isInternalToolExecutionEnabled(chatOptions); if (!internalToolExecutionEnabled) { return internalToolExecutionEnabled; @@ -104,7 +108,7 @@ default boolean isInternalToolExecutionEnabled(ChatOptions chatOptions, int atte if (chatOptions instanceof ToolCallingChatOptions toolCallingChatOptions) { return toolCallingChatOptions.getInternalToolExecutionMaxIterations() == null - || attempts <= toolCallingChatOptions.getInternalToolExecutionMaxIterations(); + || toolExecutionIterations <= toolCallingChatOptions.getInternalToolExecutionMaxIterations(); } return internalToolExecutionEnabled; } diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java index 591132f9897..a1181017986 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/ToolExecutionEligibilityPredicate.java @@ -45,15 +45,16 @@ default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse /** * Determines if tool execution should be performed based on the prompt options and - * chat response and the number of attempts. + * chat response and the number of toolExecutionIterations. * @param promptOptions The options from the prompt * @param chatResponse The response from the chat model - * @param attempts The number of attempts + * @param toolExecutionIterations The number of toolExecutionIterations * @return true if tool execution should be performed, false otherwise * @see ToolCallingChatOptions#getInternalToolExecutionMaxIterations() * @see #isToolExecutionRequired(ChatOptions, ChatResponse) */ - default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse chatResponse, int attempts) { + default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse chatResponse, + int toolExecutionIterations) { boolean isToolExecutionRequired = isToolExecutionRequired(promptOptions, chatResponse); if (!isToolExecutionRequired) { return isToolExecutionRequired; @@ -61,7 +62,7 @@ default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse if (promptOptions instanceof ToolCallingChatOptions toolCallingChatOptions) { return toolCallingChatOptions.getInternalToolExecutionMaxIterations() == null - || attempts <= toolCallingChatOptions.getInternalToolExecutionMaxIterations(); + || toolExecutionIterations <= toolCallingChatOptions.getInternalToolExecutionMaxIterations(); } return isToolExecutionRequired; }