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:
committed by
Mark Pollack
parent
f89530c314
commit
dc9ea17fc6
@@ -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
|
||||
|
||||
@@ -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<>());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user