feat: Add equals, hashCode, deep copy, and tests to AnthropicChatOptions

This commit enhances `AnthropicChatOptions` by:

- Adding `equals` and `hashCode` methods for proper object comparison.
- Implementing a deep `copy()` method, creating new instances of mutable collections (List, Set, Map, Metadata) to prevent shared state.
- Adding `AnthropicChatOptionsTests` to verify `copy()`, builders, setters, and default values.

Signed-off-by: Alexandros Pappas <apappascs@gmail.com>
This commit is contained in:
Alexandros Pappas
2025-03-01 16:52:00 +01:00
committed by Christian Tzolov
parent 53bb6a5b00
commit 092bbae5a1
2 changed files with 138 additions and 5 deletions

View File

@@ -22,6 +22,7 @@ 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;
@@ -97,15 +98,17 @@ public class AnthropicChatOptions implements ToolCallingChatOptions {
return builder().model(fromOptions.getModel())
.maxTokens(fromOptions.getMaxTokens())
.metadata(fromOptions.getMetadata())
.stopSequences(fromOptions.getStopSequences())
.stopSequences(
fromOptions.getStopSequences() != null ? new ArrayList<>(fromOptions.getStopSequences()) : null)
.temperature(fromOptions.getTemperature())
.topP(fromOptions.getTopP())
.topK(fromOptions.getTopK())
.toolCallbacks(fromOptions.getToolCallbacks())
.toolNames(fromOptions.getToolNames())
.toolCallbacks(
fromOptions.getToolCallbacks() != null ? new ArrayList<>(fromOptions.getToolCallbacks()) : null)
.toolNames(fromOptions.getToolNames() != null ? new HashSet<>(fromOptions.getToolNames()) : null)
.internalToolExecutionEnabled(fromOptions.isInternalToolExecutionEnabled())
.toolContext(fromOptions.getToolContext())
.httpHeaders(fromOptions.getHttpHeaders())
.toolContext(fromOptions.getToolContext() != null ? new HashMap<>(fromOptions.getToolContext()) : null)
.httpHeaders(fromOptions.getHttpHeaders() != null ? new HashMap<>(fromOptions.getHttpHeaders()) : null)
.build();
}
@@ -288,10 +291,36 @@ public class AnthropicChatOptions implements ToolCallingChatOptions {
}
@Override
@SuppressWarnings("unchecked")
public AnthropicChatOptions copy() {
return fromOptions(this);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof AnthropicChatOptions that)) {
return false;
}
return Objects.equals(this.model, that.model) && Objects.equals(this.maxTokens, that.maxTokens)
&& Objects.equals(this.metadata, that.metadata)
&& Objects.equals(this.stopSequences, that.stopSequences)
&& Objects.equals(this.temperature, that.temperature) && Objects.equals(this.topP, that.topP)
&& Objects.equals(this.topK, that.topK) && Objects.equals(this.toolCallbacks, that.toolCallbacks)
&& Objects.equals(this.toolNames, that.toolNames)
&& Objects.equals(this.internalToolExecutionEnabled, that.internalToolExecutionEnabled)
&& Objects.equals(this.toolContext, that.toolContext)
&& Objects.equals(this.httpHeaders, that.httpHeaders);
}
@Override
public int hashCode() {
return Objects.hash(model, maxTokens, metadata, stopSequences, temperature, topP, topK, toolCallbacks,
toolNames, internalToolExecutionEnabled, toolContext, httpHeaders);
}
public static class Builder {
private final AnthropicChatOptions options = new AnthropicChatOptions();

View File

@@ -0,0 +1,104 @@
/*
* 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.anthropic;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionRequest.Metadata;
/**
* Tests for {@link AnthropicChatOptions}.
*
* @author Alexandros Pappas
*/
class AnthropicChatOptionsTests {
@Test
void testBuilderWithAllFields() {
AnthropicChatOptions options = AnthropicChatOptions.builder()
.model("test-model")
.maxTokens(100)
.stopSequences(List.of("stop1", "stop2"))
.temperature(0.7)
.topP(0.8)
.topK(50)
.metadata(new Metadata("userId_123"))
.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"));
}
@Test
void testCopy() {
AnthropicChatOptions original = AnthropicChatOptions.builder()
.model("test-model")
.maxTokens(100)
.stopSequences(List.of("stop1", "stop2"))
.temperature(0.7)
.topP(0.8)
.topK(50)
.metadata(new Metadata("userId_123"))
.toolContext(Map.of("key1", "value1"))
.build();
AnthropicChatOptions copied = original.copy();
assertThat(copied).isNotSameAs(original).isEqualTo(original);
// Ensure deep copy
assertThat(copied.getStopSequences()).isNotSameAs(original.getStopSequences());
assertThat(copied.getToolContext()).isNotSameAs(original.getToolContext());
}
@Test
void testSetters() {
AnthropicChatOptions options = new AnthropicChatOptions();
options.setModel("test-model");
options.setMaxTokens(100);
options.setTemperature(0.7);
options.setTopK(50);
options.setTopP(0.8);
options.setStopSequences(List.of("stop1", "stop2"));
options.setMetadata(new Metadata("userId_123"));
assertThat(options.getModel()).isEqualTo("test-model");
assertThat(options.getMaxTokens()).isEqualTo(100);
assertThat(options.getTemperature()).isEqualTo(0.7);
assertThat(options.getTopK()).isEqualTo(50);
assertThat(options.getTopP()).isEqualTo(0.8);
assertThat(options.getStopSequences()).isEqualTo(List.of("stop1", "stop2"));
assertThat(options.getMetadata()).isEqualTo(new Metadata("userId_123"));
}
@Test
void testDefaultValues() {
AnthropicChatOptions options = new AnthropicChatOptions();
assertThat(options.getModel()).isNull();
assertThat(options.getMaxTokens()).isNull();
assertThat(options.getTemperature()).isNull();
assertThat(options.getTopK()).isNull();
assertThat(options.getTopP()).isNull();
assertThat(options.getStopSequences()).isNull();
assertThat(options.getMetadata()).isNull();
}
}