From 15f1fa7b9d2852b23661b0714b8ad12a43694507 Mon Sep 17 00:00:00 2001 From: Alexandros Pappas Date: Sun, 2 Mar 2025 14:22:18 +0100 Subject: [PATCH] feat: equals, hashCode, deep copy, and tests to MiniMaxChatOptions This commit enhances MiniMaxChatOptions by: - Updating `equals` and `hashCode` methods for proper object comparison. - Updating `copy()` method, creating new instances of mutable collections (List, Set, Map, Metadata) to prevent shared state. - Adding `MiniMaxChatOptionsTests` to verify `copy()`, builders, setters, and default values. Signed-off-by: Alexandros Pappas --- .../ai/minimax/MiniMaxChatOptions.java | 180 +++--------------- .../ai/minimax/MiniMaxChatOptionsTests.java | 141 ++++++++++++++ 2 files changed, 167 insertions(+), 154 deletions(-) create mode 100644 models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/MiniMaxChatOptionsTests.java 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 244c1fce699..c54e428e8db 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 @@ -17,9 +17,11 @@ package org.springframework.ai.minimax; import java.util.ArrayList; +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; @@ -43,6 +45,7 @@ * @author Geng Rong * @author Thomas Vitale * @author Ilayaperumal Gopinathan + * @author Alexandros Pappas * @since 1.0.0 M1 */ @JsonInclude(Include.NON_NULL) @@ -146,7 +149,7 @@ public class MiniMaxChatOptions implements FunctionCallingOptions { private Boolean proxyToolCalls; @JsonIgnore - private Map toolContext; + private Map toolContext = new HashMap<>(); // @formatter:on @@ -162,16 +165,17 @@ public static MiniMaxChatOptions fromOptions(MiniMaxChatOptions fromOptions) { .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()) - .functionCallbacks(fromOptions.getFunctionCallbacks()) - .functions(fromOptions.getFunctions()) + .functionCallbacks(fromOptions.getFunctionCallbacks() != null + ? new ArrayList<>(fromOptions.getFunctionCallbacks()) : null) + .functions(fromOptions.getFunctions() != null ? new HashSet<>(fromOptions.getFunctions()) : null) .proxyToolCalls(fromOptions.getProxyToolCalls()) - .toolContext(fromOptions.getToolContext()) + .toolContext(fromOptions.getToolContext() != null ? new HashMap<>(fromOptions.getToolContext()) : null) .build(); } @@ -342,164 +346,32 @@ public void setToolContext(Map toolContext) { @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.proxyToolCalls == null) ? 0 : this.proxyToolCalls.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, proxyToolCalls, toolContext); } @Override - public boolean equals(Object obj) { - if (this == obj) { + public boolean equals(Object o) { + if (this == o) { return true; } - if (obj == null) { + if (!(o instanceof MiniMaxChatOptions that)) { 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.proxyToolCalls == null) { - if (other.proxyToolCalls != null) { - return false; - } - } - else if (!this.proxyToolCalls.equals(other.proxyToolCalls)) { - return false; - } - - if (this.toolContext == null) { - if (other.toolContext != null) { - return false; - } - } - else if (!this.toolContext.equals(other.toolContext)) { - return false; - } - - return true; + return Objects.equals(this.model, that.model) && Objects.equals(this.frequencyPenalty, that.frequencyPenalty) + && Objects.equals(this.maxTokens, that.maxTokens) && Objects.equals(this.n, that.n) + && Objects.equals(this.presencePenalty, that.presencePenalty) + && Objects.equals(this.responseFormat, that.responseFormat) && Objects.equals(this.seed, that.seed) + && Objects.equals(this.stop, that.stop) && Objects.equals(this.temperature, that.temperature) + && Objects.equals(this.topP, that.topP) + && Objects.equals(this.maskSensitiveInfo, that.maskSensitiveInfo) + && Objects.equals(this.tools, that.tools) && Objects.equals(this.toolChoice, that.toolChoice) + && Objects.equals(this.proxyToolCalls, that.proxyToolCalls) + && Objects.equals(this.toolContext, that.toolContext); } @Override + @SuppressWarnings("unchecked") public MiniMaxChatOptions copy() { return fromOptions(this); } 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 00000000000..6fbd605fe48 --- /dev/null +++ b/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/MiniMaxChatOptionsTests.java @@ -0,0 +1,141 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; + +/** + * 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") + .proxyToolCalls(true) + .toolContext(Map.of("key1", "value1")) + .build(); + + assertThat(options) + .extracting("model", "frequencyPenalty", "maxTokens", "N", "presencePenalty", "responseFormat", "seed", + "stopSequences", "temperature", "topP", "maskSensitiveInfo", "toolChoice", "proxyToolCalls", + "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") + .proxyToolCalls(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 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.setStopSequences(List.of("test")); + options.setTemperature(0.6); + options.setTopP(0.6); + options.setMaskSensitiveInfo(false); + options.setToolChoice("test"); + options.setProxyToolCalls(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.getStopSequences()).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.getProxyToolCalls()).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.getStopSequences()).isNull(); + assertThat(options.getTemperature()).isNull(); + assertThat(options.getTopP()).isNull(); + assertThat(options.getMaskSensitiveInfo()).isNull(); + assertThat(options.getToolChoice()).isNull(); + assertThat(options.getProxyToolCalls()).isNull(); + assertThat(options.getToolContext()).isEqualTo(new java.util.HashMap<>()); + } + +}