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.
- Add more tests for objects not equal, defensive unmodifiable getters etc.

Signed-off-by: Alexandros Pappas <apappascs@gmail.com>
This commit is contained in:
Alexandros Pappas
2025-03-02 14:22:18 +01:00
committed by Mark Pollack
parent f89530c314
commit dc9ea17fc6
2 changed files with 235 additions and 178 deletions

View File

@@ -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<String> getStop() {
return this.stop;
return (this.stop != null) ? Collections.unmodifiableList(this.stop) : null;
}
public void setStop(List<String> stop) {
@@ -286,7 +289,7 @@ public class MiniMaxChatOptions implements ToolCallingChatOptions {
}
public List<MiniMaxApi.FunctionTool> getTools() {
return this.tools;
return (this.tools != null) ? Collections.unmodifiableList(this.tools) : null;
}
public void setTools(List<MiniMaxApi.FunctionTool> tools) {
@@ -310,7 +313,7 @@ public class MiniMaxChatOptions implements ToolCallingChatOptions {
@Override
@JsonIgnore
public List<ToolCallback> getToolCallbacks() {
return this.toolCallbacks;
return Collections.unmodifiableList(this.toolCallbacks);
}
@Override
@@ -324,7 +327,7 @@ public class MiniMaxChatOptions implements ToolCallingChatOptions {
@Override
@JsonIgnore
public Set<String> getToolNames() {
return this.toolNames;
return Collections.unmodifiableSet(this.toolNames);
}
@Override
@@ -351,7 +354,7 @@ public class MiniMaxChatOptions implements ToolCallingChatOptions {
@Override
public Map<String, Object> 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

View File

@@ -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<String, Object>) 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<>());
}
}