diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java index ba66a5b95f3..4fc02f1323f 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java @@ -22,6 +22,7 @@ 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 static AnthropicChatOptions fromOptions(AnthropicChatOptions fromOptions) 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 void setHttpHeaders(Map httpHeaders) { } @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(); diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java new file mode 100644 index 00000000000..fbb8409dffc --- /dev/null +++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java @@ -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(); + } + +}