Skip to content

Commit 4e67dbc

Browse files
committed
feat: Enhance Chat Options
Key changes include: * **AbstractChatOptions:** Introduced an abstract base class, reducing code duplication. * **DefaultChatOptions:** A concrete implementation of `ChatOptions` built on top of `AbstractChatOptions` * **Equals and HashCode:** Implemented `equals()` and `hashCode()` methods in all ChatOptions classes and the `DefaultChatOptions` class * **Test Updates:** Comprehensive test updates were made across all affected modules to verify the new Builder pattern, copy functionality, and the behavior of the `equals()` and `hashCode()` methods. Signed-off-by: Alexandros Pappas <[email protected]>
1 parent a528253 commit 4e67dbc

File tree

24 files changed

+2014
-748
lines changed

24 files changed

+2014
-748
lines changed

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

Lines changed: 45 additions & 39 deletions
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;
@@ -31,10 +32,12 @@
3132

3233
import org.springframework.ai.anthropic.api.AnthropicApi;
3334
import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionRequest;
35+
import org.springframework.ai.chat.prompt.AbstractChatOptions;
3436
import org.springframework.ai.model.function.FunctionCallback;
3537
import org.springframework.ai.model.tool.ToolCallingChatOptions;
3638
import org.springframework.ai.tool.ToolCallback;
3739
import org.springframework.lang.Nullable;
40+
import org.springframework.ai.model.tool.ToolCallingChatOptions;
3841
import org.springframework.util.Assert;
3942

4043
/**
@@ -46,16 +49,11 @@
4649
* @since 1.0.0
4750
*/
4851
@JsonInclude(Include.NON_NULL)
49-
public class AnthropicChatOptions implements ToolCallingChatOptions {
52+
public class AnthropicChatOptions extends AbstractChatOptions implements ToolCallingChatOptions {
5053

5154
// @formatter:off
52-
private @JsonProperty("model") String model;
53-
private @JsonProperty("max_tokens") Integer maxTokens;
55+
5456
private @JsonProperty("metadata") ChatCompletionRequest.Metadata metadata;
55-
private @JsonProperty("stop_sequences") List<String> stopSequences;
56-
private @JsonProperty("temperature") Double temperature;
57-
private @JsonProperty("top_p") Double topP;
58-
private @JsonProperty("top_k") Integer topK;
5957

6058
/**
6159
* Collection of {@link ToolCallback}s to be used for tool calling in the chat
@@ -90,31 +88,22 @@ public static AnthropicChatOptions fromOptions(AnthropicChatOptions fromOptions)
9088
return builder().model(fromOptions.getModel())
9189
.maxTokens(fromOptions.getMaxTokens())
9290
.metadata(fromOptions.getMetadata())
93-
.stopSequences(fromOptions.getStopSequences())
91+
.stopSequences(
92+
fromOptions.getStopSequences() != null ? new ArrayList<>(fromOptions.getStopSequences()) : null)
9493
.temperature(fromOptions.getTemperature())
9594
.topP(fromOptions.getTopP())
9695
.topK(fromOptions.getTopK())
9796
.toolCallbacks(fromOptions.getToolCallbacks())
9897
.toolNames(fromOptions.getToolNames())
9998
.internalToolExecutionEnabled(fromOptions.isInternalToolExecutionEnabled())
100-
.toolContext(fromOptions.getToolContext())
99+
.toolContext(fromOptions.getToolContext() != null ? new HashMap<>(fromOptions.getToolContext()) : null)
101100
.build();
102101
}
103102

104-
@Override
105-
public String getModel() {
106-
return this.model;
107-
}
108-
109103
public void setModel(String model) {
110104
this.model = model;
111105
}
112106

113-
@Override
114-
public Integer getMaxTokens() {
115-
return this.maxTokens;
116-
}
117-
118107
public void setMaxTokens(Integer maxTokens) {
119108
this.maxTokens = maxTokens;
120109
}
@@ -127,38 +116,18 @@ public void setMetadata(ChatCompletionRequest.Metadata metadata) {
127116
this.metadata = metadata;
128117
}
129118

130-
@Override
131-
public List<String> getStopSequences() {
132-
return this.stopSequences;
133-
}
134-
135119
public void setStopSequences(List<String> stopSequences) {
136120
this.stopSequences = stopSequences;
137121
}
138122

139-
@Override
140-
public Double getTemperature() {
141-
return this.temperature;
142-
}
143-
144123
public void setTemperature(Double temperature) {
145124
this.temperature = temperature;
146125
}
147126

148-
@Override
149-
public Double getTopP() {
150-
return this.topP;
151-
}
152-
153127
public void setTopP(Double topP) {
154128
this.topP = topP;
155129
}
156130

157-
@Override
158-
public Integer getTopK() {
159-
return this.topK;
160-
}
161-
162131
public void setTopK(Integer topK) {
163132
this.topK = topK;
164133
}
@@ -275,6 +244,43 @@ public AnthropicChatOptions copy() {
275244
return fromOptions(this);
276245
}
277246

247+
@Override
248+
public boolean equals(Object o) {
249+
if (this == o) {
250+
return true;
251+
}
252+
if (!(o instanceof AnthropicChatOptions that)) {
253+
return false;
254+
}
255+
return Objects.equals(this.model, that.model) && Objects.equals(this.maxTokens, that.maxTokens)
256+
&& Objects.equals(this.metadata, that.metadata)
257+
&& Objects.equals(this.stopSequences, that.stopSequences)
258+
&& Objects.equals(this.temperature, that.temperature) && Objects.equals(this.topP, that.topP)
259+
&& Objects.equals(this.topK, that.topK)
260+
&& Objects.equals(this.functionCallbacks, that.functionCallbacks)
261+
&& Objects.equals(this.functions, that.functions)
262+
&& Objects.equals(this.proxyToolCalls, that.proxyToolCalls)
263+
&& Objects.equals(this.toolContext, that.toolContext);
264+
}
265+
266+
@Override
267+
public int hashCode() {
268+
final int prime = 31;
269+
int result = 1;
270+
result = prime * result + (this.model != null ? this.model.hashCode() : 0);
271+
result = prime * result + (this.maxTokens != null ? this.maxTokens.hashCode() : 0);
272+
result = prime * result + (this.metadata != null ? this.metadata.hashCode() : 0);
273+
result = prime * result + (this.stopSequences != null ? this.stopSequences.hashCode() : 0);
274+
result = prime * result + (this.temperature != null ? this.temperature.hashCode() : 0);
275+
result = prime * result + (this.topP != null ? this.topP.hashCode() : 0);
276+
result = prime * result + (this.topK != null ? this.topK.hashCode() : 0);
277+
result = prime * result + (this.functionCallbacks != null ? this.functionCallbacks.hashCode() : 0);
278+
result = prime * result + (this.functions != null ? this.functions.hashCode() : 0);
279+
result = prime * result + (this.proxyToolCalls != null ? this.proxyToolCalls.hashCode() : 0);
280+
result = prime * result + (this.toolContext != null ? this.toolContext.hashCode() : 0);
281+
return result;
282+
}
283+
278284
public static class Builder {
279285

280286
private final AnthropicChatOptions options = new AnthropicChatOptions();
Lines changed: 104 additions & 0 deletions
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)