refactor(client): rename overloaded tools methods in prompt builder

Renames the ambiguous overloaded `tools` methods in `ChatClient.PromptRequestSpec`
to `toolNames` and `toolCallbacks` respectively. This improves clarity
and prevents potential issues with method dispatching based on argument types.

Updates relevant code examples and adds documentation to upgrade notes.

Signed-off-by: Mark Pollack <mark.pollack@broadcom.com>
This commit is contained in:
Mark Pollack
2025-04-30 10:42:13 -04:00
parent ec95eeb250
commit 091fca2d16
23 changed files with 125 additions and 118 deletions

View File

@@ -60,7 +60,7 @@ public class FunctionCallbackInPrompt2IT {
String content = ChatClient.builder(chatModel).build().prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris?")
.tools(FunctionToolCallback
.toolCallbacks(FunctionToolCallback
.builder("CurrentWeatherService", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
@@ -88,7 +88,7 @@ public class FunctionCallbackInPrompt2IT {
// @formatter:off
String content = ChatClient.builder(chatModel).build().prompt()
.user("Turn the light on in the kitchen and in the living room!")
.tools(FunctionToolCallback
.toolCallbacks(FunctionToolCallback
.builder("turnLight", (LightInfo lightInfo) -> {
logger.info("Turning light to [" + lightInfo.isOn + "] in " + lightInfo.roomName());
state.put(lightInfo.roomName(), lightInfo.isOn());
@@ -114,7 +114,7 @@ public class FunctionCallbackInPrompt2IT {
// @formatter:off
String content = ChatClient.builder(chatModel).build().prompt()
.user("What's the weather like in Amsterdam?")
.tools(FunctionToolCallback
.toolCallbacks(FunctionToolCallback
.builder("CurrentWeatherService", input -> "18 degrees Celsius")
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
@@ -138,7 +138,7 @@ public class FunctionCallbackInPrompt2IT {
// @formatter:off
String content = ChatClient.builder(chatModel).build().prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris?")
.tools(FunctionToolCallback
.toolCallbacks(FunctionToolCallback
.builder("CurrentWeatherService", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)

View File

@@ -174,7 +174,7 @@ class FunctionCallbackWithPlainFunctionBeanIT {
ChatClient chatClient = ChatClient.builder(chatModel).build();
String content = chatClient.prompt("What's the weather like in San Francisco, Tokyo, and Paris?")
.tools("weatherFunctionWithContext")
.toolNames("weatherFunctionWithContext")
.toolContext(Map.of("sessionId", "123"))
.call()
.content();
@@ -206,7 +206,7 @@ class FunctionCallbackWithPlainFunctionBeanIT {
ChatClient chatClient = ChatClient.builder(chatModel).build();
String content = chatClient.prompt("What's the weather like in San Francisco, Tokyo, and Paris?")
.tools("weatherFunctionWithClassBiFunction")
.toolNames("weatherFunctionWithClassBiFunction")
.toolContext(Map.of("sessionId", "123"))
.call()
.content();

View File

@@ -55,7 +55,7 @@ public class OpenAiFunctionCallback2IT {
// @formatter:off
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools("WeatherInfo")
.defaultToolNames("WeatherInfo")
.defaultUser(u -> u.text("What's the weather like in {cities}?"))
.build();
@@ -78,7 +78,7 @@ public class OpenAiFunctionCallback2IT {
// @formatter:off
String content = ChatClient.builder(chatModel).build().prompt()
.tools("WeatherInfo")
.toolNames("WeatherInfo")
.user("What's the weather like in San Francisco, Tokyo, and Paris?")
.stream().content()
.collectList().block().stream().collect(Collectors.joining());

View File

@@ -23,7 +23,6 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.junit.jupiter.params.ParameterizedTest;
@@ -213,7 +212,7 @@ class AnthropicChatClientIT {
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.tools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.toolCallbacks(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.call()
@@ -231,7 +230,7 @@ class AnthropicChatClientIT {
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.tools(FunctionToolCallback.builder("getCurrentWeatherInLocation", new MockWeatherService())
.toolCallbacks(FunctionToolCallback.builder("getCurrentWeatherInLocation", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.call()
@@ -248,7 +247,7 @@ class AnthropicChatClientIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultTools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.defaultToolCallbacks(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
@@ -270,7 +269,7 @@ class AnthropicChatClientIT {
// @formatter:off
Flux<String> response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.tools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.toolCallbacks(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())

View File

@@ -67,7 +67,7 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.tools(MethodToolCallback.builder()
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod).build())
.toolMethod(toolMethod)
.build())
@@ -89,7 +89,7 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.tools(MethodToolCallback.builder()
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.description("Get the weather in location")
.build())
@@ -116,7 +116,7 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("Turn light on in the living room.")
.tools(MethodToolCallback.builder()
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(turnLightMethod)
.description("Turn light on in the living room.")
.build())
@@ -144,7 +144,7 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.tools(MethodToolCallback.builder()
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.description("Get the weather in location")
.build())
@@ -171,7 +171,7 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.tools(MethodToolCallback.builder()
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.description("Get the weather in location")
.build())
@@ -202,7 +202,7 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
assertThatThrownBy(() -> ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.tools(MethodToolCallback.builder()
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.description("Get the weather in location")
.build())
@@ -227,7 +227,7 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("Turn light on in the living room.")
.tools(MethodToolCallback.builder()
.toolCallbacks(MethodToolCallback.builder()
.toolMethod(toolMethod)
.toolDefinition(ToolDefinition.builder(toolMethod)
.description("Can turn lights on in the Living Room")

View File

@@ -212,7 +212,7 @@ class BedrockConverseChatClientIT {
// @formatter:off
String response = ChatClient.create(this.chatModel)
.prompt("What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.")
.tools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.toolCallbacks(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
@@ -231,7 +231,7 @@ class BedrockConverseChatClientIT {
// @formatter:off
ChatResponse response = ChatClient.create(this.chatModel)
.prompt("What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.")
.tools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.toolCallbacks(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
@@ -265,7 +265,7 @@ class BedrockConverseChatClientIT {
// @formatter:off
String response = ChatClient.create(this.chatModel)
.prompt("What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.")
.tools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.toolCallbacks(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
@@ -284,7 +284,7 @@ class BedrockConverseChatClientIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultTools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.defaultToolCallbacks(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
@@ -306,7 +306,7 @@ class BedrockConverseChatClientIT {
// @formatter:off
Flux<ChatResponse> response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.")
.tools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.toolCallbacks(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
@@ -347,7 +347,7 @@ class BedrockConverseChatClientIT {
// @formatter:off
Flux<String> response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in Paris? Return the temperature in Celsius.")
.tools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.toolCallbacks(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())

View File

@@ -141,7 +141,7 @@ public class BedrockNovaChatClientIT {
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.tools(FunctionToolCallback.builder("getCurrentWeather", (WeatherRequest request) -> {
.toolCallbacks(FunctionToolCallback.builder("getCurrentWeather", (WeatherRequest request) -> {
if (request.location().contains("Paris")) {
return new WeatherResponse(15, request.unit());
}

View File

@@ -228,7 +228,7 @@ class MistralAiChatClientIT {
String response = ChatClient.create(this.chatModel).prompt()
.options(MistralAiChatOptions.builder().model(MistralAiApi.ChatModel.SMALL).toolChoice(ToolChoice.AUTO).build())
.user(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris? Use parallel function calling if required. Response should be in Celsius."))
.tools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.toolCallbacks(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
@@ -249,7 +249,7 @@ class MistralAiChatClientIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultOptions(MistralAiChatOptions.builder().model(MistralAiApi.ChatModel.SMALL).build())
.defaultTools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.defaultToolCallbacks(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
@@ -272,7 +272,7 @@ class MistralAiChatClientIT {
Flux<String> response = ChatClient.create(this.chatModel).prompt()
.options(MistralAiChatOptions.builder().model(MistralAiApi.ChatModel.SMALL).build())
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use parallel function calling if required. Response should be in Celsius.")
.tools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.toolCallbacks(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())

View File

@@ -69,7 +69,7 @@ class OpenAiChatModelFunctionCallingIT {
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("Turn the light on in the living room")
.tools(FunctionToolCallback.builder("turnsLightOnInTheLivingRoom", () -> state.put("Light", "ON"))
.toolCallbacks(FunctionToolCallback.builder("turnsLightOnInTheLivingRoom", () -> state.put("Light", "ON"))
.build())
.call()
.content();

View File

@@ -82,7 +82,7 @@ public class OpenAiPaymentTransactionIT {
public void transactionPaymentStatuses(String functionName) {
List<TransactionStatusResponse> content = this.chatClient.prompt()
.advisors(new LoggingAdvisor())
.tools(functionName)
.toolNames(functionName)
.user("""
What is the status of my payment transactions 001, 002 and 003?
""")
@@ -113,7 +113,7 @@ public class OpenAiPaymentTransactionIT {
Flux<String> flux = this.chatClient.prompt()
.advisors(new LoggingAdvisor())
.tools(functionName)
.toolNames(functionName)
.user(u -> u.text("""
What is the status of my payment transactions 001, 002 and 003?

View File

@@ -251,7 +251,7 @@ class OpenAiChatClientIT extends AbstractIT {
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris?"))
.tools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.toolCallbacks(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
@@ -269,7 +269,7 @@ class OpenAiChatClientIT extends AbstractIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultTools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.defaultToolCallbacks(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
@@ -289,7 +289,7 @@ class OpenAiChatClientIT extends AbstractIT {
// @formatter:off
Flux<String> response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris?")
.tools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.toolCallbacks(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())

View File

@@ -66,7 +66,7 @@ class OpenAiChatClientMethodInvokingFunctionCallbackIT {
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.tools(MethodToolCallback.builder()
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.description("Get the weather in location")
.build())
@@ -91,7 +91,7 @@ class OpenAiChatClientMethodInvokingFunctionCallbackIT {
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("Turn light on in the living room.")
.tools(MethodToolCallback.builder()
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.description("Can turn lights on or off by room name")
.build())
@@ -119,7 +119,7 @@ class OpenAiChatClientMethodInvokingFunctionCallbackIT {
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.tools(MethodToolCallback.builder()
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.description("Get the weather in location")
.build())
@@ -146,7 +146,7 @@ class OpenAiChatClientMethodInvokingFunctionCallbackIT {
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.tools(MethodToolCallback.builder()
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.description("Get the weather in location")
.build())
@@ -175,7 +175,7 @@ class OpenAiChatClientMethodInvokingFunctionCallbackIT {
// @formatter:off
assertThatThrownBy(() -> ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.tools(MethodToolCallback.builder()
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.description("Get the weather in location")
.build())
@@ -199,7 +199,7 @@ class OpenAiChatClientMethodInvokingFunctionCallbackIT {
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("Turn light on in the living room.")
.tools(MethodToolCallback.builder()
.toolCallbacks(MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(toolMethod)
.description("Can turn lights on in the Living Room")
.build())

View File

@@ -84,7 +84,7 @@ class OpenAiChatClientMultipleFunctionCallsIT extends AbstractIT {
// @formatter:off
response = chatClientBuilder.build().prompt()
.user(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris?"))
.tools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.toolCallbacks(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
@@ -114,7 +114,7 @@ class OpenAiChatClientMultipleFunctionCallsIT extends AbstractIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultTools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.defaultToolCallbacks(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
@@ -156,7 +156,7 @@ class OpenAiChatClientMultipleFunctionCallsIT extends AbstractIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultTools(FunctionToolCallback.builder("getCurrentWeather", biFunction)
.defaultToolCallbacks(FunctionToolCallback.builder("getCurrentWeather", biFunction)
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
@@ -199,7 +199,7 @@ class OpenAiChatClientMultipleFunctionCallsIT extends AbstractIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultTools(FunctionToolCallback.builder("getCurrentWeather", biFunction)
.defaultToolCallbacks(FunctionToolCallback.builder("getCurrentWeather", biFunction)
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
@@ -221,7 +221,7 @@ class OpenAiChatClientMultipleFunctionCallsIT extends AbstractIT {
// @formatter:off
Flux<String> response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris?")
.tools(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.toolCallbacks(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
@@ -250,7 +250,7 @@ class OpenAiChatClientMultipleFunctionCallsIT extends AbstractIT {
String content = chatClient.prompt()
.user("What's the weather like in Shanghai?")
.tools(FunctionToolCallback.builder("currentTemp", function)
.toolCallbacks(FunctionToolCallback.builder("currentTemp", function)
.description("get current temp")
.inputType(MyFunction.Req.class)
.build())

View File

@@ -76,7 +76,7 @@ public class VertexAiGeminiPaymentTransactionIT {
// @formatter:off
String content = this.chatClient.prompt()
.advisors(new LoggingAdvisor())
.tools("paymentStatus")
.toolNames("paymentStatus")
.user("""
What is the status of my payment transactions 001, 002 and 003?
If requred invoke the function per transaction.
@@ -93,7 +93,7 @@ public class VertexAiGeminiPaymentTransactionIT {
Flux<String> streamContent = this.chatClient.prompt()
.advisors(new LoggingAdvisor())
.tools("paymentStatus")
.toolNames("paymentStatus")
.user("""
What is the status of my payment transactions 001, 002 and 003?
If requred invoke the function per transaction.

View File

@@ -76,7 +76,7 @@ public class VertexAiGeminiPaymentTransactionMethodIT {
@Test
public void paymentStatuses() {
String content = this.chatClient.prompt().advisors(new LoggingAdvisor()).tools("paymentStatus").user("""
String content = this.chatClient.prompt().advisors(new LoggingAdvisor()).toolNames("paymentStatus").user("""
What is the status of my payment transactions 001, 002 and 003?
If requred invoke the function per transaction.
""").call().content();
@@ -91,7 +91,7 @@ public class VertexAiGeminiPaymentTransactionMethodIT {
Flux<String> streamContent = this.chatClient.prompt()
.advisors(new LoggingAdvisor())
.tools("paymentStatus")
.toolNames("paymentStatus")
.user("""
What is the status of my payment transactions 001, 002 and 003?
If requred invoke the function per transaction.

View File

@@ -220,15 +220,15 @@ public interface ChatClient {
<T extends ChatOptions> ChatClientRequestSpec options(T options);
ChatClientRequestSpec tools(String... toolNames);
ChatClientRequestSpec tools(ToolCallback... toolCallbacks);
ChatClientRequestSpec tools(List<ToolCallback> toolCallbacks);
ChatClientRequestSpec toolNames(String... toolNames);
ChatClientRequestSpec tools(Object... toolObjects);
ChatClientRequestSpec tools(ToolCallbackProvider... toolCallbackProviders);
ChatClientRequestSpec toolCallbacks(ToolCallback... toolCallbacks);
ChatClientRequestSpec toolCallbacks(List<ToolCallback> toolCallbacks);
ChatClientRequestSpec toolCallbacks(ToolCallbackProvider... toolCallbackProviders);
ChatClientRequestSpec toolContext(Map<String, Object> toolContext);
@@ -287,15 +287,15 @@ public interface ChatClient {
Builder defaultTemplateRenderer(TemplateRenderer templateRenderer);
Builder defaultTools(String... toolNames);
Builder defaultTools(ToolCallback... toolCallbacks);
Builder defaultTools(List<ToolCallback> toolCallbacks);
Builder defaultToolNames(String... toolNames);
Builder defaultTools(Object... toolObjects);
Builder defaultTools(ToolCallbackProvider... toolCallbackProviders);
Builder defaultToolCallbacks(ToolCallback... toolCallbacks);
Builder defaultToolCallbacks(List<ToolCallback> toolCallbacks);
Builder defaultToolCallbacks(ToolCallbackProvider... toolCallbackProviders);
Builder defaultToolContext(Map<String, Object> toolContext);

View File

@@ -768,7 +768,7 @@ public class DefaultChatClient implements ChatClient {
public Builder mutate() {
DefaultChatClientBuilder builder = (DefaultChatClientBuilder) ChatClient
.builder(this.chatModel, this.observationRegistry, this.observationConvention)
.defaultTools(StringUtils.toStringArray(this.toolNames));
.defaultToolNames(StringUtils.toStringArray(this.toolNames));
if (StringUtils.hasText(this.userText)) {
builder.defaultUser(
@@ -834,7 +834,7 @@ public class DefaultChatClient implements ChatClient {
}
@Override
public ChatClientRequestSpec tools(String... toolNames) {
public ChatClientRequestSpec toolNames(String... toolNames) {
Assert.notNull(toolNames, "toolNames cannot be null");
Assert.noNullElements(toolNames, "toolNames cannot contain null elements");
this.toolNames.addAll(List.of(toolNames));
@@ -842,7 +842,7 @@ public class DefaultChatClient implements ChatClient {
}
@Override
public ChatClientRequestSpec tools(ToolCallback... toolCallbacks) {
public ChatClientRequestSpec toolCallbacks(ToolCallback... toolCallbacks) {
Assert.notNull(toolCallbacks, "toolCallbacks cannot be null");
Assert.noNullElements(toolCallbacks, "toolCallbacks cannot contain null elements");
this.toolCallbacks.addAll(List.of(toolCallbacks));
@@ -850,7 +850,7 @@ public class DefaultChatClient implements ChatClient {
}
@Override
public ChatClientRequestSpec tools(List<ToolCallback> toolCallbacks) {
public ChatClientRequestSpec toolCallbacks(List<ToolCallback> toolCallbacks) {
Assert.notNull(toolCallbacks, "toolCallbacks cannot be null");
Assert.noNullElements(toolCallbacks, "toolCallbacks cannot contain null elements");
this.toolCallbacks.addAll(toolCallbacks);
@@ -866,7 +866,7 @@ public class DefaultChatClient implements ChatClient {
}
@Override
public ChatClientRequestSpec tools(ToolCallbackProvider... toolCallbackProviders) {
public ChatClientRequestSpec toolCallbacks(ToolCallbackProvider... toolCallbackProviders) {
Assert.notNull(toolCallbackProviders, "toolCallbackProviders cannot be null");
Assert.noNullElements(toolCallbackProviders, "toolCallbackProviders cannot contain null elements");
for (ToolCallbackProvider toolCallbackProvider : toolCallbackProviders) {

View File

@@ -151,20 +151,20 @@ public class DefaultChatClientBuilder implements Builder {
}
@Override
public Builder defaultTools(String... toolNames) {
this.defaultRequest.tools(toolNames);
public Builder defaultToolNames(String... toolNames) {
this.defaultRequest.toolNames(toolNames);
return this;
}
@Override
public Builder defaultTools(ToolCallback... toolCallbacks) {
this.defaultRequest.tools(toolCallbacks);
public Builder defaultToolCallbacks(ToolCallback... toolCallbacks) {
this.defaultRequest.toolCallbacks(toolCallbacks);
return this;
}
@Override
public Builder defaultTools(List<ToolCallback> toolCallbacks) {
this.defaultRequest.tools(toolCallbacks);
public Builder defaultToolCallbacks(List<ToolCallback> toolCallbacks) {
this.defaultRequest.toolCallbacks(toolCallbacks);
return this;
}
@@ -175,14 +175,15 @@ public class DefaultChatClientBuilder implements Builder {
}
@Override
public Builder defaultTools(ToolCallbackProvider... toolCallbackProviders) {
this.defaultRequest.tools(toolCallbackProviders);
public Builder defaultToolCallbacks(ToolCallbackProvider... toolCallbackProviders) {
this.defaultRequest.toolCallbacks(toolCallbackProviders);
return this;
}
@Deprecated // Use defaultTools()
public <I, O> Builder defaultFunction(String name, String description, java.util.function.Function<I, O> function) {
this.defaultRequest.tools(FunctionToolCallback.builder(name, function).description(description).build());
this.defaultRequest
.toolCallbacks(FunctionToolCallback.builder(name, function).description(description).build());
return this;
}
@@ -203,7 +204,7 @@ public class DefaultChatClientBuilder implements Builder {
void addToolCallbacks(List<ToolCallback> toolCallbacks) {
Assert.notNull(toolCallbacks, "toolCallbacks cannot be null");
this.defaultRequest.tools(toolCallbacks.toArray(ToolCallback[]::new));
this.defaultRequest.toolCallbacks(toolCallbacks.toArray(ToolCallback[]::new));
}
void addToolContext(Map<String, Object> toolContext) {

View File

@@ -216,8 +216,8 @@ public class ChatClientTest {
.defaultSystem(s -> s.text("Default system text {param1}, {param2}")
.param("param1", "value1")
.param("param2", "value2"))
.defaultTools("fun1", "fun2")
.defaultTools(FunctionToolCallback.builder("fun3", mockFunction)
.defaultToolNames("fun1", "fun2")
.defaultToolCallbacks(FunctionToolCallback.builder("fun3", mockFunction)
.description("fun3description")
.inputType(String.class)
.build())
@@ -276,7 +276,7 @@ public class ChatClientTest {
// @formatter:off
chatClient = chatClient.mutate()
.defaultSystem("Mutated default system text {param1}, {param2}")
.defaultTools("fun4")
.defaultToolNames("fun4")
.defaultUser("Mutated default user text {uparam1}, {uparam2}")
.build();
// @formatter:on
@@ -346,8 +346,8 @@ public class ChatClientTest {
.defaultSystem(s -> s.text("Default system text {param1}, {param2}")
.param("param1", "value1")
.param("param2", "value2"))
.defaultTools("fun1", "fun2")
.defaultTools(FunctionToolCallback.builder("fun3", mockFunction)
.defaultToolNames("fun1", "fun2")
.defaultToolCallbacks(FunctionToolCallback.builder("fun3", mockFunction)
.description("fun3description")
.inputType(String.class)
.build())
@@ -363,7 +363,7 @@ public class ChatClientTest {
.system("New default system text {param1}, {param2}")
.user(u -> u.param("uparam1", "userValue1")
.param("uparam2", "userValue2"))
.tools("fun5")
.toolNames("fun5")
.mutate().build() // mutate and build new prompt
.prompt().call().content();
// @formatter:on
@@ -394,7 +394,7 @@ public class ChatClientTest {
.system("New default system text {param1}, {param2}")
.user(u -> u.param("uparam1", "userValue1")
.param("uparam2", "userValue2"))
.tools("fun5")
.toolNames("fun5")
.mutate().build() // mutate and build new prompt
.prompt().stream().content());
// @formatter:on
@@ -523,7 +523,7 @@ public class ChatClientTest {
// @formatter:off
ChatClient client = ChatClient.builder(this.chatModel)
.defaultSystem("System text")
.defaultTools("function1")
.defaultToolNames("function1")
.build();
String response = client.prompt()

View File

@@ -1457,7 +1457,7 @@ class DefaultChatClientTests {
void whenToolNamesElementIsNullThenThrow() {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
assertThatThrownBy(() -> spec.tools("myTool", null)).isInstanceOf(IllegalArgumentException.class)
assertThatThrownBy(() -> spec.toolNames("myTool", null)).isInstanceOf(IllegalArgumentException.class)
.hasMessage("toolNames cannot contain null elements");
}
@@ -1466,7 +1466,7 @@ class DefaultChatClientTests {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
String toolName = "myTool";
spec = spec.tools(toolName);
spec = spec.toolNames(toolName);
DefaultChatClient.DefaultChatClientRequestSpec defaultSpec = (DefaultChatClient.DefaultChatClientRequestSpec) spec;
assertThat(defaultSpec.getToolNames()).contains(toolName);
}
@@ -1475,7 +1475,7 @@ class DefaultChatClientTests {
void whenToolCallbacksElementIsNullThenThrow() {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
assertThatThrownBy(() -> spec.tools(mock(ToolCallback.class), null))
assertThatThrownBy(() -> spec.toolCallbacks(mock(ToolCallback.class), null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("toolCallbacks cannot contain null elements");
}
@@ -1485,7 +1485,7 @@ class DefaultChatClientTests {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
ToolCallback toolCallback = mock(ToolCallback.class);
spec = spec.tools(toolCallback);
spec = spec.toolCallbacks(toolCallback);
DefaultChatClient.DefaultChatClientRequestSpec defaultSpec = (DefaultChatClient.DefaultChatClientRequestSpec) spec;
assertThat(defaultSpec.getToolCallbacks()).contains(toolCallback);
}
@@ -1494,7 +1494,7 @@ class DefaultChatClientTests {
void whenFunctionNameIsNullThenThrow() {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
assertThatThrownBy(() -> spec.tools(FunctionToolCallback.builder(null, input -> "hello")
assertThatThrownBy(() -> spec.toolCallbacks(FunctionToolCallback.builder(null, input -> "hello")
.description("description")
.inputType(String.class)
.build())).isInstanceOf(IllegalArgumentException.class).hasMessage("name cannot be null or empty");
@@ -1504,7 +1504,7 @@ class DefaultChatClientTests {
void whenFunctionNameIsEmptyThenThrow() {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
assertThatThrownBy(() -> spec.tools(FunctionToolCallback.builder("", input -> "hello")
assertThatThrownBy(() -> spec.toolCallbacks(FunctionToolCallback.builder("", input -> "hello")
.description("description")
.inputType(String.class)
.build())).isInstanceOf(IllegalArgumentException.class).hasMessage("name cannot be null or empty");
@@ -1515,7 +1515,7 @@ class DefaultChatClientTests {
void whenFunctionDescriptionIsNullThenThrow() {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
assertThatThrownBy(() -> spec.tools(FunctionToolCallback.builder("name", input -> "hello")
assertThatThrownBy(() -> spec.toolCallbacks(FunctionToolCallback.builder("name", input -> "hello")
.description(null)
.inputType(String.class)
.build())).isInstanceOf(IllegalArgumentException.class).hasMessage("Description must not be empty");
@@ -1526,7 +1526,7 @@ class DefaultChatClientTests {
void whenFunctionDescriptionIsEmptyThenThrow() {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
assertThatThrownBy(() -> spec.tools(
assertThatThrownBy(() -> spec.toolCallbacks(
FunctionToolCallback.builder("name", input -> "hello").description("").inputType(String.class).build()))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Description must not be empty");
@@ -1536,7 +1536,7 @@ class DefaultChatClientTests {
void whenFunctionThenReturn() {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
spec = spec.tools(FunctionToolCallback.builder("name", input -> "hello")
spec = spec.toolCallbacks(FunctionToolCallback.builder("name", input -> "hello")
.inputType(String.class)
.description("description")
.build());
@@ -1549,7 +1549,7 @@ class DefaultChatClientTests {
void whenFunctionAndInputTypeThenReturn() {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
spec = spec.tools(FunctionToolCallback.builder("name", input -> "hello")
spec = spec.toolCallbacks(FunctionToolCallback.builder("name", input -> "hello")
.inputType(String.class)
.description("description")
.build());
@@ -1562,8 +1562,8 @@ class DefaultChatClientTests {
void whenBiFunctionNameIsNullThenThrow() {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
assertThatThrownBy(() -> spec
.tools(FunctionToolCallback.builder(null, (input, ctx) -> "hello").description("description").build()))
assertThatThrownBy(() -> spec.toolCallbacks(
FunctionToolCallback.builder(null, (input, ctx) -> "hello").description("description").build()))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("name cannot be null or empty");
}
@@ -1572,8 +1572,8 @@ class DefaultChatClientTests {
void whenBiFunctionNameIsEmptyThenThrow() {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
assertThatThrownBy(() -> spec
.tools(FunctionToolCallback.builder("", (input, ctx) -> "hello").description("description").build()))
assertThatThrownBy(() -> spec.toolCallbacks(
FunctionToolCallback.builder("", (input, ctx) -> "hello").description("description").build()))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("name cannot be null or empty");
}
@@ -1583,7 +1583,7 @@ class DefaultChatClientTests {
void whenBiFunctionDescriptionIsNullThenThrow() {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
assertThatThrownBy(() -> spec.tools(FunctionToolCallback.builder("name", (input, ctx) -> "hello")
assertThatThrownBy(() -> spec.toolCallbacks(FunctionToolCallback.builder("name", (input, ctx) -> "hello")
.inputType(String.class)
.description(null)
.build())).isInstanceOf(IllegalArgumentException.class).hasMessage("Description must not be empty");
@@ -1594,8 +1594,8 @@ class DefaultChatClientTests {
void whenBiFunctionDescriptionIsEmptyThenThrow() {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
assertThatThrownBy(
() -> spec.tools(FunctionToolCallback.builder("name", (input, ctx) -> "hello").description("").build()))
assertThatThrownBy(() -> spec
.toolCallbacks(FunctionToolCallback.builder("name", (input, ctx) -> "hello").description("").build()))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Description must not be empty");
}
@@ -1604,7 +1604,7 @@ class DefaultChatClientTests {
void whenBiFunctionThenReturn() {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
spec = spec.tools(FunctionToolCallback.builder("name", (input, ctx) -> "hello")
spec = spec.toolCallbacks(FunctionToolCallback.builder("name", (input, ctx) -> "hello")
.description("description")
.inputType(String.class)
.build());
@@ -1617,7 +1617,7 @@ class DefaultChatClientTests {
void whenFunctionBeanNamesElementIsNullThenThrow() {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
assertThatThrownBy(() -> spec.tools("myFunction", null)).isInstanceOf(IllegalArgumentException.class)
assertThatThrownBy(() -> spec.toolNames("myFunction", null)).isInstanceOf(IllegalArgumentException.class)
.hasMessage("toolNames cannot contain null elements");
}
@@ -1626,7 +1626,7 @@ class DefaultChatClientTests {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
String functionBeanName = "myFunction";
spec = spec.tools(functionBeanName);
spec = spec.toolNames(functionBeanName);
DefaultChatClient.DefaultChatClientRequestSpec defaultSpec = (DefaultChatClient.DefaultChatClientRequestSpec) spec;
assertThat(defaultSpec.getToolNames()).contains(functionBeanName);
}
@@ -1635,7 +1635,7 @@ class DefaultChatClientTests {
void whenFunctionToolCallbacksElementIsNullThenThrow() {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
assertThatThrownBy(() -> spec.tools(mock(FunctionToolCallback.class), null))
assertThatThrownBy(() -> spec.toolCallbacks(mock(FunctionToolCallback.class), null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("toolCallbacks cannot contain null elements");
}
@@ -1645,7 +1645,7 @@ class DefaultChatClientTests {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
FunctionToolCallback functionToolCallback = mock(FunctionToolCallback.class);
spec = spec.tools(functionToolCallback);
spec = spec.toolCallbacks(functionToolCallback);
DefaultChatClient.DefaultChatClientRequestSpec defaultSpec = (DefaultChatClient.DefaultChatClientRequestSpec) spec;
assertThat(defaultSpec.getToolCallbacks()).contains(functionToolCallback);
}

View File

@@ -124,6 +124,13 @@ Prompt augmentedPrompt = originalPrompt.augmentUserMessage(userMessage ->
This approach offers more control when you need to conditionally change parts of the `UserMessage` or work with its media and metadata, rather than just replacing the text content.
* The overloaded `tools` methods in the `ChatClient` prompt builder API have been renamed for clarity and to avoid ambiguity in method dispatching based on argument types.
* `ChatClient.PromptRequestSpec#tools(String... toolNames)` has been renamed to `ChatClient.PromptRequestSpec#toolNames(String... toolNames)`. Use this method to specify the names of tool functions (registered elsewhere, e.g., via `@Bean` definitions with `@Description`) that the model is allowed to call.
* `ChatClient.PromptRequestSpec#tools(ToolCallback... toolCallbacks)` has been renamed to `ChatClient.PromptRequestSpec#toolCallbacks(ToolCallback... toolCallbacks)`. Use this method to provide inline `ToolCallback` instances, which include the function implementation, name, description, and input type definition.
This change addresses potential confusion where the Java compiler might not select the intended overload based on the provided arguments.
=== Prompt Templating and Advisors
Several classes and methods related to prompt creation and advisor customization have been deprecated in favor of more flexible approaches using the builder pattern and the `TemplateRenderer` interface.

View File

@@ -66,7 +66,7 @@ public class FunctionToolCallbackTests {
.build()
.prompt()
.user("Welcome the users to the library")
.tools(Tools.WELCOME)
.toolNames(Tools.WELCOME)
.call()
.content();
assertThat(content).isNotEmpty();
@@ -78,7 +78,7 @@ public class FunctionToolCallbackTests {
.build()
.prompt()
.user("Welcome the users to the library")
.tools(FunctionToolCallback.builder("sayWelcome",
.toolCallbacks(FunctionToolCallback.builder("sayWelcome",
(Consumer<Object>) input -> logger.info("CALLBACK - Welcoming users to the library"))
.description("Welcome users to the library")
.inputType(Void.class)
@@ -94,7 +94,7 @@ public class FunctionToolCallbackTests {
.build()
.prompt()
.user("Welcome %s to the library".formatted("James Bond"))
.tools(Tools.WELCOME_USER)
.toolNames(Tools.WELCOME_USER)
.call()
.content();
assertThat(content).isNotEmpty();
@@ -106,7 +106,7 @@ public class FunctionToolCallbackTests {
.build()
.prompt()
.user("Welcome %s to the library".formatted("James Bond"))
.tools(FunctionToolCallback.builder("welcomeUser",
.toolCallbacks(FunctionToolCallback.builder("welcomeUser",
(Consumer<Object>) user -> logger.info("CALLBACK - Welcoming {} to the library", ((User) user).name()))
.description("Welcome a specific user to the library")
.inputType(User.class)
@@ -122,7 +122,7 @@ public class FunctionToolCallbackTests {
.build()
.prompt()
.user("What books written by %s are available in the library?".formatted("J.R.R. Tolkien"))
.tools(Tools.BOOKS_BY_AUTHOR)
.toolNames(Tools.BOOKS_BY_AUTHOR)
.call()
.content();
assertThat(content).isNotEmpty()
@@ -141,7 +141,7 @@ public class FunctionToolCallbackTests {
.build()
.prompt()
.user("What books written by %s are available in the library?".formatted("J.R.R. Tolkien"))
.tools(FunctionToolCallback.builder("availableBooksByAuthor", function)
.toolCallbacks(FunctionToolCallback.builder("availableBooksByAuthor", function)
.description("Get the list of books written by the given author available in the library")
.inputType(Author.class)
.build())
@@ -159,7 +159,7 @@ public class FunctionToolCallbackTests {
.build()
.prompt()
.user("What authors wrote the books %s and %s available in the library?".formatted("The Hobbit", "The Lion, the Witch and the Wardrobe"))
.tools(Tools.AUTHORS_BY_BOOKS)
.toolNames(Tools.AUTHORS_BY_BOOKS)
.call()
.content();
assertThat(content).isNotEmpty().contains("J.R.R. Tolkien").contains("C.S. Lewis");
@@ -175,7 +175,7 @@ public class FunctionToolCallbackTests {
.build()
.prompt()
.user("What authors wrote the books %s and %s available in the library?".formatted("The Hobbit", "The Lion, the Witch and the Wardrobe"))
.tools(FunctionToolCallback.builder("authorsByAvailableBooks", function)
.toolCallbacks(FunctionToolCallback.builder("authorsByAvailableBooks", function)
.description("Get the list of authors who wrote the given books available in the library")
.inputType(Books.class)
.build())

View File

@@ -112,7 +112,7 @@ public class MethodToolCallbackTests {
.prompt()
.user("What authors wrote the books %s and %s available in the library?".formatted("The Hobbit",
"The Lion, the Witch and the Wardrobe"))
.tools(ToolCallbacks.from(this.tools))
.toolCallbacks(ToolCallbacks.from(this.tools))
.call()
.content();
assertThat(content).isNotEmpty().contains("J.R.R. Tolkien").contains("C.S. Lewis");