Refactor AnthropicChatOptions builder methods

- Refactor the builder methods to remove `with` as the prefix.
- Introduce new methods with updated naming conventions.
- Deprecate the existing `with*` methods to maintain backward compatibility.
- Update AnthropicChatOptions builder documentation
This commit is contained in:
Alexandros Pappas
2024-12-16 18:36:51 +01:00
committed by Ilayaperumal Gopinathan
parent de29df7c58
commit 1d38fd11fd
12 changed files with 175 additions and 67 deletions

View File

@@ -128,9 +128,9 @@ public class AnthropicChatModel extends AbstractToolCallSupport implements ChatM
public AnthropicChatModel(AnthropicApi anthropicApi) {
this(anthropicApi,
AnthropicChatOptions.builder()
.withModel(DEFAULT_MODEL_NAME)
.withMaxTokens(DEFAULT_MAX_TOKENS)
.withTemperature(DEFAULT_TEMPERATURE)
.model(DEFAULT_MODEL_NAME)
.maxTokens(DEFAULT_MAX_TOKENS)
.temperature(DEFAULT_TEMPERATURE)
.build());
}

View File

@@ -38,6 +38,7 @@ import org.springframework.util.Assert;
*
* @author Christian Tzolov
* @author Thomas Vitale
* @author Alexandros Pappas
* @since 1.0.0
*/
@JsonInclude(Include.NON_NULL)
@@ -89,17 +90,17 @@ public class AnthropicChatOptions implements FunctionCallingOptions {
}
public static AnthropicChatOptions fromOptions(AnthropicChatOptions fromOptions) {
return builder().withModel(fromOptions.getModel())
.withMaxTokens(fromOptions.getMaxTokens())
.withMetadata(fromOptions.getMetadata())
.withStopSequences(fromOptions.getStopSequences())
.withTemperature(fromOptions.getTemperature())
.withTopP(fromOptions.getTopP())
.withTopK(fromOptions.getTopK())
.withFunctionCallbacks(fromOptions.getFunctionCallbacks())
.withFunctions(fromOptions.getFunctions())
.withProxyToolCalls(fromOptions.getProxyToolCalls())
.withToolContext(fromOptions.getToolContext())
return builder().model(fromOptions.getModel())
.maxTokens(fromOptions.getMaxTokens())
.metadata(fromOptions.getMetadata())
.stopSequences(fromOptions.getStopSequences())
.temperature(fromOptions.getTemperature())
.topP(fromOptions.getTopP())
.topK(fromOptions.getTopK())
.functionCallbacks(fromOptions.getFunctionCallbacks())
.functions(fromOptions.getFunctions())
.proxyToolCalls(fromOptions.getProxyToolCalls())
.toolContext(fromOptions.getToolContext())
.build();
}
@@ -227,69 +228,69 @@ public class AnthropicChatOptions implements FunctionCallingOptions {
private final AnthropicChatOptions options = new AnthropicChatOptions();
public Builder withModel(String model) {
public Builder model(String model) {
this.options.model = model;
return this;
}
public Builder withModel(AnthropicApi.ChatModel model) {
public Builder model(AnthropicApi.ChatModel model) {
this.options.model = model.getValue();
return this;
}
public Builder withMaxTokens(Integer maxTokens) {
public Builder maxTokens(Integer maxTokens) {
this.options.maxTokens = maxTokens;
return this;
}
public Builder withMetadata(ChatCompletionRequest.Metadata metadata) {
public Builder metadata(ChatCompletionRequest.Metadata metadata) {
this.options.metadata = metadata;
return this;
}
public Builder withStopSequences(List<String> stopSequences) {
public Builder stopSequences(List<String> stopSequences) {
this.options.stopSequences = stopSequences;
return this;
}
public Builder withTemperature(Double temperature) {
public Builder temperature(Double temperature) {
this.options.temperature = temperature;
return this;
}
public Builder withTopP(Double topP) {
public Builder topP(Double topP) {
this.options.topP = topP;
return this;
}
public Builder withTopK(Integer topK) {
public Builder topK(Integer topK) {
this.options.topK = topK;
return this;
}
public Builder withFunctionCallbacks(List<FunctionCallback> functionCallbacks) {
public Builder functionCallbacks(List<FunctionCallback> functionCallbacks) {
this.options.functionCallbacks = functionCallbacks;
return this;
}
public Builder withFunctions(Set<String> functionNames) {
public Builder functions(Set<String> functionNames) {
Assert.notNull(functionNames, "Function names must not be null");
this.options.functions = functionNames;
return this;
}
public Builder withFunction(String functionName) {
public Builder function(String functionName) {
Assert.hasText(functionName, "Function name must not be empty");
this.options.functions.add(functionName);
return this;
}
public Builder withProxyToolCalls(Boolean proxyToolCalls) {
public Builder proxyToolCalls(Boolean proxyToolCalls) {
this.options.proxyToolCalls = proxyToolCalls;
return this;
}
public Builder withToolContext(Map<String, Object> toolContext) {
public Builder toolContext(Map<String, Object> toolContext) {
if (this.options.toolContext == null) {
this.options.toolContext = toolContext;
}
@@ -299,6 +300,110 @@ public class AnthropicChatOptions implements FunctionCallingOptions {
return this;
}
/**
* @deprecated use {@link #model(String)} instead.
*/
@Deprecated(forRemoval = true, since = "1.0.0-M5")
public Builder withModel(String model) {
return model(model);
}
/**
* @deprecated use {@link #model(AnthropicApi.ChatModel)} instead.
*/
@Deprecated(forRemoval = true, since = "1.0.0-M5")
public Builder withModel(AnthropicApi.ChatModel model) {
return model(model);
}
/**
* @deprecated use {@link #maxTokens(Integer)} instead.
*/
@Deprecated(forRemoval = true, since = "1.0.0-M5")
public Builder withMaxTokens(Integer maxTokens) {
return maxTokens(maxTokens);
}
/**
* @deprecated use {@link #metadata(ChatCompletionRequest.Metadata)} instead.
*/
@Deprecated(forRemoval = true, since = "1.0.0-M5")
public Builder withMetadata(ChatCompletionRequest.Metadata metadata) {
return metadata(metadata);
}
/**
* @deprecated use {@link #stopSequences(List)} instead.
*/
@Deprecated(forRemoval = true, since = "1.0.0-M5")
public Builder withStopSequences(List<String> stopSequences) {
return stopSequences(stopSequences);
}
/**
* @deprecated use {@link #temperature(Double)} instead.
*/
@Deprecated(forRemoval = true, since = "1.0.0-M5")
public Builder withTemperature(Double temperature) {
return temperature(temperature);
}
/**
* @deprecated use {@link #topP(Double)} instead.
*/
@Deprecated(forRemoval = true, since = "1.0.0-M5")
public Builder withTopP(Double topP) {
return topP(topP);
}
/**
* @deprecated use {@link #topK(Integer)} instead.
*/
@Deprecated(forRemoval = true, since = "1.0.0-M5")
public Builder withTopK(Integer topK) {
return topK(topK);
}
/**
* @deprecated use {@link #functionCallbacks(List)} instead.
*/
@Deprecated(forRemoval = true, since = "1.0.0-M5")
public Builder withFunctionCallbacks(List<FunctionCallback> functionCallbacks) {
return functionCallbacks(functionCallbacks);
}
/**
* @deprecated use {@link #functions(Set)} instead.
*/
@Deprecated(forRemoval = true, since = "1.0.0-M5")
public Builder withFunctions(Set<String> functionNames) {
return functions(functionNames);
}
/**
* @deprecated use {@link #function(String)} instead.
*/
@Deprecated(forRemoval = true, since = "1.0.0-M5")
public Builder withFunction(String functionName) {
return function(functionName);
}
/**
* @deprecated use {@link #proxyToolCalls(Boolean)} instead.
*/
@Deprecated(forRemoval = true, since = "1.0.0-M5")
public Builder withProxyToolCalls(Boolean proxyToolCalls) {
return proxyToolCalls(proxyToolCalls);
}
/**
* @deprecated use {@link #toolContext(Map)} instead.
*/
@Deprecated(forRemoval = true, since = "1.0.0-M5")
public Builder withToolContext(Map<String, Object> toolContext) {
return toolContext(toolContext);
}
public AnthropicChatOptions build() {
return this.options;
}

View File

@@ -97,7 +97,7 @@ class AnthropicChatModelIT {
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(this.systemResource);
Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", "Bob", "voice", "pirate"));
Prompt prompt = new Prompt(List.of(userMessage, systemMessage),
AnthropicChatOptions.builder().withModel(modelName).build());
AnthropicChatOptions.builder().model(modelName).build());
ChatResponse response = this.chatModel.call(prompt);
assertThat(response.getResults()).hasSize(1);
assertThat(response.getMetadata().getUsage().getGenerationTokens()).isGreaterThan(0);
@@ -118,7 +118,7 @@ class AnthropicChatModelIT {
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(this.systemResource);
Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", "Bob", "voice", "pirate"));
Prompt prompt = new Prompt(List.of(userMessage, systemMessage),
AnthropicChatOptions.builder().withModel("claude-3-sonnet-20240229").build());
AnthropicChatOptions.builder().model("claude-3-sonnet-20240229").build());
ChatResponse response = this.chatModel.call(prompt);
assertThat(response.getResult().getOutput().getText()).containsAnyOf("Blackbeard", "Bartholomew");
@@ -132,7 +132,7 @@ class AnthropicChatModelIT {
@Test
void streamingWithTokenUsage() {
var promptOptions = AnthropicChatOptions.builder().withTemperature(0.0).build();
var promptOptions = AnthropicChatOptions.builder().temperature(0.0).build();
var prompt = new Prompt("List two colors of the Polish flag. Be brief.", promptOptions);
var streamingTokenUsage = this.chatModel.stream(prompt).blockLast().getMetadata().getUsage();
@@ -273,8 +273,8 @@ class AnthropicChatModelIT {
List<Message> messages = new ArrayList<>(List.of(userMessage));
var promptOptions = AnthropicChatOptions.builder()
.withModel(AnthropicApi.ChatModel.CLAUDE_3_OPUS.getName())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.model(AnthropicApi.ChatModel.CLAUDE_3_OPUS.getName())
.functionCallbacks(List.of(FunctionCallback.builder()
.function("getCurrentWeather", new MockWeatherService())
.description(
"Get the weather in location. Return temperature in 36°F or 36°C format. Use multi-turn if needed.")
@@ -306,8 +306,8 @@ class AnthropicChatModelIT {
List<Message> messages = new ArrayList<>(List.of(userMessage));
var promptOptions = AnthropicChatOptions.builder()
.withModel(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getName())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.model(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getName())
.functionCallbacks(List.of(FunctionCallback.builder()
.function("getCurrentWeather", new MockWeatherService())
.description(
"Get the weather in location. Return temperature in 36°F or 36°C format. Use multi-turn if needed.")
@@ -362,7 +362,7 @@ class AnthropicChatModelIT {
String model = AnthropicApi.ChatModel.CLAUDE_2_1.getName();
// @formatter:off
ChatResponse response = ChatClient.create(this.chatModel).prompt()
.options(AnthropicChatOptions.builder().withModel(model).build())
.options(AnthropicChatOptions.builder().model(model).build())
.user("Tell me about 3 famous pirates from the Golden Age of Piracy and what they did")
.call()
.chatResponse();
@@ -377,7 +377,7 @@ class AnthropicChatModelIT {
String model = AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getName();
// @formatter:off
ChatResponse response = ChatClient.create(this.chatModel).prompt()
.options(AnthropicChatOptions.builder().withModel(model).build())
.options(AnthropicChatOptions.builder().model(model).build())
.user("Tell me about 3 famous pirates from the Golden Age of Piracy and what they did")
.stream()
.chatResponse()

View File

@@ -48,6 +48,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Integration tests for observation instrumentation in {@link AnthropicChatModel}.
*
* @author Thomas Vitale
* @author Alexandros Pappas
*/
@SpringBootTest(classes = AnthropicChatModelObservationIT.Config.class,
properties = "spring.ai.retry.on-http-codes=429")
@@ -68,12 +69,12 @@ public class AnthropicChatModelObservationIT {
@Test
void observationForChatOperation() {
var options = AnthropicChatOptions.builder()
.withModel(AnthropicApi.ChatModel.CLAUDE_3_HAIKU.getValue())
.withMaxTokens(2048)
.withStopSequences(List.of("this-is-the-end"))
.withTemperature(0.7)
.withTopK(1)
.withTopP(1.0)
.model(AnthropicApi.ChatModel.CLAUDE_3_HAIKU.getValue())
.maxTokens(2048)
.stopSequences(List.of("this-is-the-end"))
.temperature(0.7)
.topK(1)
.topP(1.0)
.build();
Prompt prompt = new Prompt("Why does a raven look like a desk?", options);
@@ -90,12 +91,12 @@ public class AnthropicChatModelObservationIT {
@Test
void observationForStreamingChatOperation() {
var options = AnthropicChatOptions.builder()
.withModel(AnthropicApi.ChatModel.CLAUDE_3_HAIKU.getValue())
.withMaxTokens(2048)
.withStopSequences(List.of("this-is-the-end"))
.withTemperature(0.7)
.withTopK(1)
.withTopP(1.0)
.model(AnthropicApi.ChatModel.CLAUDE_3_HAIKU.getValue())
.maxTokens(2048)
.stopSequences(List.of("this-is-the-end"))
.temperature(0.7)
.topK(1)
.topP(1.0)
.build();
Prompt prompt = new Prompt("Why does a raven look like a desk?", options);

View File

@@ -25,6 +25,7 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Christian Tzolov
* @author Alexandros Pappas
*/
public class ChatCompletionRequestTests {
@@ -32,7 +33,7 @@ public class ChatCompletionRequestTests {
public void createRequestWithChatOptions() {
var client = new AnthropicChatModel(new AnthropicApi("TEST"),
AnthropicChatOptions.builder().withModel("DEFAULT_MODEL").withTemperature(66.6).build());
AnthropicChatOptions.builder().model("DEFAULT_MODEL").temperature(66.6).build());
var request = client.createRequest(new Prompt("Test message content"), false);
@@ -43,7 +44,7 @@ public class ChatCompletionRequestTests {
assertThat(request.temperature()).isEqualTo(66.6);
request = client.createRequest(new Prompt("Test message content",
AnthropicChatOptions.builder().withModel("PROMPT_MODEL").withTemperature(99.9).build()), true);
AnthropicChatOptions.builder().model("PROMPT_MODEL").temperature(99.9).build()), true);
assertThat(request.messages()).hasSize(1);
assertThat(request.stream()).isTrue();

View File

@@ -294,7 +294,7 @@ class AnthropicChatClientIT {
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.options(AnthropicChatOptions.builder().withModel(modelName).build())
.options(AnthropicChatOptions.builder().model(modelName).build())
.user(u -> u.text("Explain what do you see on this picture?")
.media(MimeTypeUtils.IMAGE_PNG, new ClassPathResource("/test.png")))
.call()
@@ -318,7 +318,7 @@ class AnthropicChatClientIT {
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
// TODO consider adding model(...) method to ChatClient as a shortcut to
.options(AnthropicChatOptions.builder().withModel(modelName).build())
.options(AnthropicChatOptions.builder().model(modelName).build())
.user(u -> u.text("Explain what do you see on this picture?").media(MimeTypeUtils.IMAGE_PNG, url))
.call()
.content();
@@ -334,7 +334,7 @@ class AnthropicChatClientIT {
// @formatter:off
Flux<String> response = ChatClient.create(this.chatModel).prompt()
.options(AnthropicChatOptions.builder().withModel(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET)
.options(AnthropicChatOptions.builder().model(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET)
.build())
.user(u -> u.text("Explain what do you see on this picture?")
.media(MimeTypeUtils.IMAGE_PNG, new ClassPathResource("/test.png")))

View File

@@ -130,8 +130,8 @@ ChatResponse response = chatModel.call(
new Prompt(
"Generate the names of 5 famous pirates.",
AnthropicChatOptions.builder()
.withModel("claude-2.1")
.withTemperature(0.4)
.model("claude-2.1")
.temperature(0.4)
.build()
));
----
@@ -274,9 +274,9 @@ var anthropicApi = new AnthropicApi(System.getenv("ANTHROPIC_API_KEY"));
var chatModel = new AnthropicChatModel(this.anthropicApi,
AnthropicChatOptions.builder()
.withModel("claude-3-opus-20240229")
.withTemperature(0.4)
.withMaxTokens(200)
.model("claude-3-opus-20240229")
.temperature(0.4)
.maxTokens(200)
.build());
ChatResponse response = this.chatModel.call(

View File

@@ -155,7 +155,7 @@ AnthropicChatModel chatModel = ...
UserMessage userMessage = new UserMessage("What's the weather like in Paris?");
ChatResponse response = this.chatModel.call(new Prompt(List.of(this.userMessage),
AnthropicChatOptions.builder().withFunction("CurrentWeather").build())); // (1) Enable the function
AnthropicChatOptions.builder().function("CurrentWeather").build())); // (1) Enable the function
logger.info("Response: {}", response);
----
@@ -175,7 +175,7 @@ AnthropicChatModel chatModel = ...
UserMessage userMessage = new UserMessage("What's the weather like in Paris?");
var promptOptions = AnthropicChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.functionCallbacks(List.of(FunctionCallback.builder()
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.description("Get the weather in location") // (2) function description
.inputType(MockWeatherService.Request.class) // (3) function signature

View File

@@ -25,6 +25,7 @@ import org.springframework.boot.context.properties.NestedConfigurationProperty;
* Anthropic Chat autoconfiguration properties.
*
* @author Christian Tzolov
* @author Alexandros Pappas
* @since 1.0.0
*/
@ConfigurationProperties(AnthropicChatProperties.CONFIG_PREFIX)
@@ -44,9 +45,9 @@ public class AnthropicChatProperties {
*/
@NestedConfigurationProperty
private AnthropicChatOptions options = AnthropicChatOptions.builder()
.withModel(AnthropicChatModel.DEFAULT_MODEL_NAME)
.withMaxTokens(AnthropicChatModel.DEFAULT_MAX_TOKENS)
.withTemperature(AnthropicChatModel.DEFAULT_TEMPERATURE)
.model(AnthropicChatModel.DEFAULT_MODEL_NAME)
.maxTokens(AnthropicChatModel.DEFAULT_MAX_TOKENS)
.temperature(AnthropicChatModel.DEFAULT_TEMPERATURE)
.build();
public AnthropicChatOptions getOptions() {

View File

@@ -64,7 +64,7 @@ public class AnthropicAutoConfigurationIT {
"spring.ai.anthropic.chat.options.model=" + AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getValue())
.run(context -> {
AnthropicChatModel chatModel = context.getBean(AnthropicChatModel.class);
var optoins = AnthropicChatOptions.builder().withMaxTokens(8192).build();
var optoins = AnthropicChatOptions.builder().maxTokens(8192).build();
var response = chatModel.call(new Prompt("Tell me a joke", optoins));
assertThat(response.getResult().getOutput().getText()).isNotEmpty();
logger.info("Response: " + response);

View File

@@ -66,14 +66,14 @@ class FunctionCallWithFunctionBeanIT {
"What's the weather like in San Francisco, in Paris, France and in Tokyo, Japan? Return the temperature in Celsius.");
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage),
AnthropicChatOptions.builder().withFunction("weatherFunction").build()));
AnthropicChatOptions.builder().function("weatherFunction").build()));
logger.info("Response: {}", response);
assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15");
response = chatModel.call(new Prompt(List.of(userMessage),
AnthropicChatOptions.builder().withFunction("weatherFunction3").build()));
AnthropicChatOptions.builder().function("weatherFunction3").build()));
logger.info("Response: {}", response);

View File

@@ -58,7 +58,7 @@ public class FunctionCallWithPromptFunctionIT {
"What's the weather like in San Francisco, in Paris and in Tokyo? Return the temperature in Celsius.");
var promptOptions = AnthropicChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.functionCallbacks(List.of(FunctionCallback.builder()
.function("CurrentWeatherService", new MockWeatherService())
.description("Get the weather in location. Return temperature in 36°F or 36°C format.")
.inputType(MockWeatherService.Request.class)