Skip to content

Commit 092bbae

Browse files
apappascstzolov
authored andcommitted
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 <[email protected]>
1 parent 53bb6a5 commit 092bbae

File tree

2 files changed

+138
-5
lines changed

2 files changed

+138
-5
lines changed

Diff for: models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java

+34-5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.HashSet;
2323
import java.util.List;
2424
import java.util.Map;
25+
import java.util.Objects;
2526
import java.util.Set;
2627

2728
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -97,15 +98,17 @@ public static AnthropicChatOptions fromOptions(AnthropicChatOptions fromOptions)
9798
return builder().model(fromOptions.getModel())
9899
.maxTokens(fromOptions.getMaxTokens())
99100
.metadata(fromOptions.getMetadata())
100-
.stopSequences(fromOptions.getStopSequences())
101+
.stopSequences(
102+
fromOptions.getStopSequences() != null ? new ArrayList<>(fromOptions.getStopSequences()) : null)
101103
.temperature(fromOptions.getTemperature())
102104
.topP(fromOptions.getTopP())
103105
.topK(fromOptions.getTopK())
104-
.toolCallbacks(fromOptions.getToolCallbacks())
105-
.toolNames(fromOptions.getToolNames())
106+
.toolCallbacks(
107+
fromOptions.getToolCallbacks() != null ? new ArrayList<>(fromOptions.getToolCallbacks()) : null)
108+
.toolNames(fromOptions.getToolNames() != null ? new HashSet<>(fromOptions.getToolNames()) : null)
106109
.internalToolExecutionEnabled(fromOptions.isInternalToolExecutionEnabled())
107-
.toolContext(fromOptions.getToolContext())
108-
.httpHeaders(fromOptions.getHttpHeaders())
110+
.toolContext(fromOptions.getToolContext() != null ? new HashMap<>(fromOptions.getToolContext()) : null)
111+
.httpHeaders(fromOptions.getHttpHeaders() != null ? new HashMap<>(fromOptions.getHttpHeaders()) : null)
109112
.build();
110113
}
111114

@@ -288,10 +291,36 @@ public void setHttpHeaders(Map<String, String> httpHeaders) {
288291
}
289292

290293
@Override
294+
@SuppressWarnings("unchecked")
291295
public AnthropicChatOptions copy() {
292296
return fromOptions(this);
293297
}
294298

299+
@Override
300+
public boolean equals(Object o) {
301+
if (this == o) {
302+
return true;
303+
}
304+
if (!(o instanceof AnthropicChatOptions that)) {
305+
return false;
306+
}
307+
return Objects.equals(this.model, that.model) && Objects.equals(this.maxTokens, that.maxTokens)
308+
&& Objects.equals(this.metadata, that.metadata)
309+
&& Objects.equals(this.stopSequences, that.stopSequences)
310+
&& Objects.equals(this.temperature, that.temperature) && Objects.equals(this.topP, that.topP)
311+
&& Objects.equals(this.topK, that.topK) && Objects.equals(this.toolCallbacks, that.toolCallbacks)
312+
&& Objects.equals(this.toolNames, that.toolNames)
313+
&& Objects.equals(this.internalToolExecutionEnabled, that.internalToolExecutionEnabled)
314+
&& Objects.equals(this.toolContext, that.toolContext)
315+
&& Objects.equals(this.httpHeaders, that.httpHeaders);
316+
}
317+
318+
@Override
319+
public int hashCode() {
320+
return Objects.hash(model, maxTokens, metadata, stopSequences, temperature, topP, topK, toolCallbacks,
321+
toolNames, internalToolExecutionEnabled, toolContext, httpHeaders);
322+
}
323+
295324
public static class Builder {
296325

297326
private final AnthropicChatOptions options = new AnthropicChatOptions();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2025-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.anthropic;
18+
19+
import java.util.List;
20+
import java.util.Map;
21+
22+
import static org.assertj.core.api.Assertions.assertThat;
23+
24+
import org.junit.jupiter.api.Test;
25+
26+
import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionRequest.Metadata;
27+
28+
/**
29+
* Tests for {@link AnthropicChatOptions}.
30+
*
31+
* @author Alexandros Pappas
32+
*/
33+
class AnthropicChatOptionsTests {
34+
35+
@Test
36+
void testBuilderWithAllFields() {
37+
AnthropicChatOptions options = AnthropicChatOptions.builder()
38+
.model("test-model")
39+
.maxTokens(100)
40+
.stopSequences(List.of("stop1", "stop2"))
41+
.temperature(0.7)
42+
.topP(0.8)
43+
.topK(50)
44+
.metadata(new Metadata("userId_123"))
45+
.build();
46+
47+
assertThat(options).extracting("model", "maxTokens", "stopSequences", "temperature", "topP", "topK", "metadata")
48+
.containsExactly("test-model", 100, List.of("stop1", "stop2"), 0.7, 0.8, 50, new Metadata("userId_123"));
49+
}
50+
51+
@Test
52+
void testCopy() {
53+
AnthropicChatOptions original = AnthropicChatOptions.builder()
54+
.model("test-model")
55+
.maxTokens(100)
56+
.stopSequences(List.of("stop1", "stop2"))
57+
.temperature(0.7)
58+
.topP(0.8)
59+
.topK(50)
60+
.metadata(new Metadata("userId_123"))
61+
.toolContext(Map.of("key1", "value1"))
62+
.build();
63+
64+
AnthropicChatOptions copied = original.copy();
65+
66+
assertThat(copied).isNotSameAs(original).isEqualTo(original);
67+
// Ensure deep copy
68+
assertThat(copied.getStopSequences()).isNotSameAs(original.getStopSequences());
69+
assertThat(copied.getToolContext()).isNotSameAs(original.getToolContext());
70+
}
71+
72+
@Test
73+
void testSetters() {
74+
AnthropicChatOptions options = new AnthropicChatOptions();
75+
options.setModel("test-model");
76+
options.setMaxTokens(100);
77+
options.setTemperature(0.7);
78+
options.setTopK(50);
79+
options.setTopP(0.8);
80+
options.setStopSequences(List.of("stop1", "stop2"));
81+
options.setMetadata(new Metadata("userId_123"));
82+
83+
assertThat(options.getModel()).isEqualTo("test-model");
84+
assertThat(options.getMaxTokens()).isEqualTo(100);
85+
assertThat(options.getTemperature()).isEqualTo(0.7);
86+
assertThat(options.getTopK()).isEqualTo(50);
87+
assertThat(options.getTopP()).isEqualTo(0.8);
88+
assertThat(options.getStopSequences()).isEqualTo(List.of("stop1", "stop2"));
89+
assertThat(options.getMetadata()).isEqualTo(new Metadata("userId_123"));
90+
}
91+
92+
@Test
93+
void testDefaultValues() {
94+
AnthropicChatOptions options = new AnthropicChatOptions();
95+
assertThat(options.getModel()).isNull();
96+
assertThat(options.getMaxTokens()).isNull();
97+
assertThat(options.getTemperature()).isNull();
98+
assertThat(options.getTopK()).isNull();
99+
assertThat(options.getTopP()).isNull();
100+
assertThat(options.getStopSequences()).isNull();
101+
assertThat(options.getMetadata()).isNull();
102+
}
103+
104+
}

0 commit comments

Comments
 (0)