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 9d2614396..a8f1e62e7 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 @@ -18,10 +18,12 @@ package org.springframework.ai.minimax; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -45,6 +47,7 @@ import org.springframework.util.Assert; * @author Geng Rong * @author Thomas Vitale * @author Ilayaperumal Gopinathan + * @author Alexandros Pappas * @since 1.0.0 M1 */ @JsonInclude(Include.NON_NULL) @@ -167,11 +170,11 @@ public class MiniMaxChatOptions implements ToolCallingChatOptions { .presencePenalty(fromOptions.getPresencePenalty()) .responseFormat(fromOptions.getResponseFormat()) .seed(fromOptions.getSeed()) - .stop(fromOptions.getStop()) + .stop(fromOptions.getStop() != null ? new ArrayList<>(fromOptions.getStop()) : null) .temperature(fromOptions.getTemperature()) .topP(fromOptions.getTopP()) .maskSensitiveInfo(fromOptions.getMaskSensitiveInfo()) - .tools(fromOptions.getTools()) + .tools(fromOptions.getTools() != null ? new ArrayList<>(fromOptions.getTools()) : null) .toolChoice(fromOptions.getToolChoice()) .toolCallbacks(fromOptions.getToolCallbacks()) .toolNames(fromOptions.getToolNames()) @@ -252,7 +255,7 @@ public class MiniMaxChatOptions implements ToolCallingChatOptions { } public List getStop() { - return this.stop; + return (this.stop != null) ? Collections.unmodifiableList(this.stop) : null; } public void setStop(List stop) { @@ -286,7 +289,7 @@ public class MiniMaxChatOptions implements ToolCallingChatOptions { } public List getTools() { - return this.tools; + return (this.tools != null) ? Collections.unmodifiableList(this.tools) : null; } public void setTools(List tools) { @@ -310,7 +313,7 @@ public class MiniMaxChatOptions implements ToolCallingChatOptions { @Override @JsonIgnore public List getToolCallbacks() { - return this.toolCallbacks; + return Collections.unmodifiableList(this.toolCallbacks); } @Override @@ -324,7 +327,7 @@ public class MiniMaxChatOptions implements ToolCallingChatOptions { @Override @JsonIgnore public Set getToolNames() { - return this.toolNames; + return Collections.unmodifiableSet(this.toolNames); } @Override @@ -351,7 +354,7 @@ public class MiniMaxChatOptions implements ToolCallingChatOptions { @Override public Map getToolContext() { - return this.toolContext; + return (this.toolContext != null) ? Collections.unmodifiableMap(this.toolContext) : null; } @Override @@ -361,182 +364,28 @@ public class MiniMaxChatOptions implements ToolCallingChatOptions { @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((this.model == null) ? 0 : this.model.hashCode()); - result = prime * result + ((this.frequencyPenalty == null) ? 0 : this.frequencyPenalty.hashCode()); - result = prime * result + ((this.maxTokens == null) ? 0 : this.maxTokens.hashCode()); - result = prime * result + ((this.n == null) ? 0 : this.n.hashCode()); - result = prime * result + ((this.presencePenalty == null) ? 0 : this.presencePenalty.hashCode()); - result = prime * result + ((this.responseFormat == null) ? 0 : this.responseFormat.hashCode()); - result = prime * result + ((this.seed == null) ? 0 : this.seed.hashCode()); - result = prime * result + ((this.stop == null) ? 0 : this.stop.hashCode()); - result = prime * result + ((this.temperature == null) ? 0 : this.temperature.hashCode()); - result = prime * result + ((this.topP == null) ? 0 : this.topP.hashCode()); - result = prime * result + ((this.maskSensitiveInfo == null) ? 0 : this.maskSensitiveInfo.hashCode()); - result = prime * result + ((this.tools == null) ? 0 : this.tools.hashCode()); - result = prime * result + ((this.toolChoice == null) ? 0 : this.toolChoice.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.internalToolExecutionEnabled == null) ? 0 : this.internalToolExecutionEnabled.hashCode()); - result = prime * result + ((this.toolContext == null) ? 0 : this.toolContext.hashCode()); - return result; + return Objects.hash(model, frequencyPenalty, maxTokens, n, presencePenalty, responseFormat, seed, stop, + temperature, topP, maskSensitiveInfo, tools, toolChoice, toolCallbacks, toolNames, toolContext, + internalToolExecutionEnabled); } @Override - public boolean equals(Object obj) { - if (this == obj) { + public boolean equals(Object o) { + if (this == o) return true; - } - if (obj == null) { + if (o == null || getClass() != o.getClass()) return false; - } - if (getClass() != obj.getClass()) { - return false; - } - MiniMaxChatOptions other = (MiniMaxChatOptions) obj; - if (this.model == null) { - if (other.model != null) { - return false; - } - } - else if (!this.model.equals(other.model)) { - return false; - } - if (this.frequencyPenalty == null) { - if (other.frequencyPenalty != null) { - return false; - } - } - else if (!this.frequencyPenalty.equals(other.frequencyPenalty)) { - return false; - } - if (this.maxTokens == null) { - if (other.maxTokens != null) { - return false; - } - } - else if (!this.maxTokens.equals(other.maxTokens)) { - return false; - } - if (this.n == null) { - if (other.n != null) { - return false; - } - } - else if (!this.n.equals(other.n)) { - return false; - } - if (this.presencePenalty == null) { - if (other.presencePenalty != null) { - return false; - } - } - else if (!this.presencePenalty.equals(other.presencePenalty)) { - return false; - } - if (this.responseFormat == null) { - if (other.responseFormat != null) { - return false; - } - } - else if (!this.responseFormat.equals(other.responseFormat)) { - return false; - } - if (this.seed == null) { - if (other.seed != null) { - return false; - } - } - else if (!this.seed.equals(other.seed)) { - return false; - } - if (this.stop == null) { - if (other.stop != null) { - return false; - } - } - else if (!this.stop.equals(other.stop)) { - return false; - } - if (this.temperature == null) { - if (other.temperature != null) { - return false; - } - } - else if (!this.temperature.equals(other.temperature)) { - return false; - } - if (this.topP == null) { - if (other.topP != null) { - return false; - } - } - else if (!this.topP.equals(other.topP)) { - return false; - } - if (this.maskSensitiveInfo == null) { - if (other.maskSensitiveInfo != null) { - return false; - } - } - else if (!this.maskSensitiveInfo.equals(other.maskSensitiveInfo)) { - return false; - } - if (this.tools == null) { - if (other.tools != null) { - return false; - } - } - else if (!this.tools.equals(other.tools)) { - return false; - } - if (this.toolChoice == null) { - if (other.toolChoice != null) { - return false; - } - } - else if (!this.toolChoice.equals(other.toolChoice)) { - return false; - } - if (this.internalToolExecutionEnabled == null) { - if (other.internalToolExecutionEnabled != null) { - return false; - } - } - else if (!this.internalToolExecutionEnabled.equals(other.internalToolExecutionEnabled)) { - return false; - } - - if (this.toolNames == null) { - if (other.toolNames != null) { - return false; - } - } - else if (!this.toolNames.equals(other.toolNames)) { - return false; - } - - if (this.toolCallbacks == null) { - if (other.toolCallbacks != null) { - return false; - } - } - else if (!this.toolCallbacks.equals(other.toolCallbacks)) { - return false; - } - - if (this.toolContext == null) { - if (other.toolContext != null) { - return false; - } - } - else if (!this.toolContext.equals(other.toolContext)) { - return false; - } - - return true; + MiniMaxChatOptions that = (MiniMaxChatOptions) o; + return Objects.equals(model, that.model) && Objects.equals(frequencyPenalty, that.frequencyPenalty) + && Objects.equals(maxTokens, that.maxTokens) && Objects.equals(n, that.n) + && Objects.equals(presencePenalty, that.presencePenalty) + && Objects.equals(responseFormat, that.responseFormat) && Objects.equals(seed, that.seed) + && Objects.equals(stop, that.stop) && Objects.equals(temperature, that.temperature) + && Objects.equals(topP, that.topP) && Objects.equals(maskSensitiveInfo, that.maskSensitiveInfo) + && Objects.equals(tools, that.tools) && Objects.equals(toolChoice, that.toolChoice) + && Objects.equals(toolCallbacks, that.toolCallbacks) && Objects.equals(toolNames, that.toolNames) + && Objects.equals(toolContext, that.toolContext) + && Objects.equals(internalToolExecutionEnabled, that.internalToolExecutionEnabled); } @Override diff --git a/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/MiniMaxChatOptionsTests.java b/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/MiniMaxChatOptionsTests.java new file mode 100644 index 000000000..cbcd25a87 --- /dev/null +++ b/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/MiniMaxChatOptionsTests.java @@ -0,0 +1,208 @@ +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.minimax; + +import org.junit.jupiter.api.Test; +import org.springframework.ai.minimax.api.MiniMaxApi; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Tests for {@link MiniMaxChatOptions}. + * + * @author Alexandros Pappas + */ +class MiniMaxChatOptionsTests { + + @Test + void testBuilderWithAllFields() { + MiniMaxChatOptions options = MiniMaxChatOptions.builder() + .model("test-model") + .frequencyPenalty(0.5) + .maxTokens(10) + .N(1) + .presencePenalty(0.5) + .responseFormat(new MiniMaxApi.ChatCompletionRequest.ResponseFormat("text")) + .seed(1) + .stop(List.of("test")) + .temperature(0.6) + .topP(0.6) + .maskSensitiveInfo(false) + .toolChoice("test") + .internalToolExecutionEnabled(true) + .toolContext(Map.of("key1", "value1")) + .build(); + + assertThat(options) + .extracting("model", "frequencyPenalty", "maxTokens", "N", "presencePenalty", "responseFormat", "seed", + "stop", "temperature", "topP", "maskSensitiveInfo", "toolChoice", "internalToolExecutionEnabled", + "toolContext") + .containsExactly("test-model", 0.5, 10, 1, 0.5, new MiniMaxApi.ChatCompletionRequest.ResponseFormat("text"), + 1, List.of("test"), 0.6, 0.6, false, "test", true, Map.of("key1", "value1")); + } + + @Test + void testCopy() { + MiniMaxChatOptions original = MiniMaxChatOptions.builder() + .model("test-model") + .frequencyPenalty(0.5) + .maxTokens(10) + .N(1) + .presencePenalty(0.5) + .responseFormat(new MiniMaxApi.ChatCompletionRequest.ResponseFormat("text")) + .seed(1) + .stop(List.of("test")) + .temperature(0.6) + .topP(0.6) + .maskSensitiveInfo(false) + .toolChoice("test") + .internalToolExecutionEnabled(true) + .toolContext(Map.of("key1", "value1")) + .build(); + + MiniMaxChatOptions copied = original.copy(); + + assertThat(copied).isNotSameAs(original).isEqualTo(original); + // Ensure deep copy + assertThat(copied.getStop()).isNotSameAs(original.getStop()); + assertThat(copied.getToolContext()).isNotSameAs(original.getToolContext()); + } + + @Test + void testNotEquals() { + MiniMaxChatOptions options1 = MiniMaxChatOptions.builder().model("model1").build(); + MiniMaxChatOptions options2 = MiniMaxChatOptions.builder().model("model2").build(); + + assertThat(options1).isNotEqualTo(options2); + } + + @Test + void testSettersWithNulls() { + MiniMaxChatOptions options = new MiniMaxChatOptions(); + options.setModel(null); + options.setFrequencyPenalty(null); + options.setMaxTokens(null); + options.setN(null); + options.setPresencePenalty(null); + options.setResponseFormat(null); + options.setSeed(null); + options.setStop(null); + options.setTemperature(null); + options.setTopP(null); + options.setMaskSensitiveInfo(null); + options.setTools(null); + options.setToolChoice(null); + options.setInternalToolExecutionEnabled(null); + options.setToolContext(null); + + assertThat(options.getModel()).isNull(); + assertThat(options.getFrequencyPenalty()).isNull(); + assertThat(options.getMaxTokens()).isNull(); + assertThat(options.getN()).isNull(); + assertThat(options.getPresencePenalty()).isNull(); + assertThat(options.getResponseFormat()).isNull(); + assertThat(options.getSeed()).isNull(); + assertThat(options.getStop()).isNull(); + assertThat(options.getTemperature()).isNull(); + assertThat(options.getTopP()).isNull(); + assertThat(options.getMaskSensitiveInfo()).isNull(); + assertThat(options.getTools()).isNull(); + assertThat(options.getToolChoice()).isNull(); + assertThat(options.getInternalToolExecutionEnabled()).isNull(); + assertThat(options.getToolContext()).isNull(); + } + + @Test + void testImmutabilityOfCollections() { + MiniMaxChatOptions options = MiniMaxChatOptions.builder() + .stop(new java.util.ArrayList<>(List.of("stop"))) + .tools(new java.util.ArrayList<>(List.of(new MiniMaxApi.FunctionTool(MiniMaxApi.FunctionTool.Type.FUNCTION, + new MiniMaxApi.FunctionTool.Function("name", "desc", (Map) null))))) + .toolCallbacks(new java.util.ArrayList<>(List.of())) + .toolNames(new java.util.HashSet<>(Set.of("tool"))) + .toolContext(new java.util.HashMap<>(Map.of("key", "value"))) + .build(); + + assertThatThrownBy(() -> options.getStop().add("another")).isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> options.getTools().add(null)).isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> options.getToolCallbacks().add(null)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> options.getToolNames().add("another")) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> options.getToolContext().put("another", "value")) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + void testSetters() { + MiniMaxChatOptions options = new MiniMaxChatOptions(); + options.setModel("test-model"); + options.setFrequencyPenalty(0.5); + options.setMaxTokens(10); + options.setN(1); + options.setPresencePenalty(0.5); + options.setResponseFormat(new MiniMaxApi.ChatCompletionRequest.ResponseFormat("text")); + options.setSeed(1); + options.setStop(List.of("test")); + options.setTemperature(0.6); + options.setTopP(0.6); + options.setMaskSensitiveInfo(false); + options.setToolChoice("test"); + options.setInternalToolExecutionEnabled(true); + options.setToolContext(Map.of("key1", "value1")); + + assertThat(options.getModel()).isEqualTo("test-model"); + assertThat(options.getFrequencyPenalty()).isEqualTo(0.5); + assertThat(options.getMaxTokens()).isEqualTo(10); + assertThat(options.getN()).isEqualTo(1); + assertThat(options.getPresencePenalty()).isEqualTo(0.5); + assertThat(options.getResponseFormat()).isEqualTo(new MiniMaxApi.ChatCompletionRequest.ResponseFormat("text")); + assertThat(options.getSeed()).isEqualTo(1); + assertThat(options.getStop()).isEqualTo(List.of("test")); + assertThat(options.getTemperature()).isEqualTo(0.6); + assertThat(options.getTopP()).isEqualTo(0.6); + assertThat(options.getMaskSensitiveInfo()).isEqualTo(false); + assertThat(options.getToolChoice()).isEqualTo("test"); + assertThat(options.getInternalToolExecutionEnabled()).isEqualTo(true); + assertThat(options.getToolContext()).isEqualTo(Map.of("key1", "value1")); + } + + @Test + void testDefaultValues() { + MiniMaxChatOptions options = new MiniMaxChatOptions(); + assertThat(options.getModel()).isNull(); + assertThat(options.getFrequencyPenalty()).isNull(); + assertThat(options.getMaxTokens()).isNull(); + assertThat(options.getN()).isNull(); + assertThat(options.getPresencePenalty()).isNull(); + assertThat(options.getResponseFormat()).isNull(); + assertThat(options.getSeed()).isNull(); + assertThat(options.getStop()).isNull(); + assertThat(options.getTemperature()).isNull(); + assertThat(options.getTopP()).isNull(); + assertThat(options.getMaskSensitiveInfo()).isNull(); + assertThat(options.getToolChoice()).isNull(); + assertThat(options.getInternalToolExecutionEnabled()).isNull(); + assertThat(options.getToolContext()).isEqualTo(new java.util.HashMap<>()); + } + +}