refactor(core): extract common function callback builder functionality

Extracts shared function callback builder functionality into DefaultCommonCallbackInvokingSpec
base class, reducing code duplication across builder implementations.
Makes FunctionInvokingSpec and MethodInvokingSpec extend CommonCallbackInvokingSpec for better
code organization. Also fixes function/description builder order in Anthropic tests.

- Introduced a common base class for function callback builders to centralize shared logic
- Standardized the order of method chaining for function and description in multiple AI model test classes
- Refactored test cases across various AI model integrations
- Corrected builder method order from .description().function() to .function().description()
  and .description().method() to .method().description()
- Updated multiple test files to consistently use .function() before .description()
- Updated documentation examples to reflect new builder method order
- Modified DefaultFunctionCallbackResolver to maintain new builder method order
- Updated DefaultChatClient and ChatClient test classes to reflect new builder pattern
- Simplified callback specification by removing parent spec reference
- Removed cascading getter logic for description, schema type, and other properties
- Minor adjustments to function callback builder and invoking specs
This commit is contained in:
Christian Tzolov
2024-11-25 11:51:35 +01:00
committed by Ilayaperumal Gopinathan
parent dcc8d5b620
commit 6a195ee9fe
72 changed files with 408 additions and 323 deletions

View File

@@ -276,9 +276,9 @@ class AnthropicChatModelIT {
var promptOptions = AnthropicChatOptions.builder()
.withModel(AnthropicApi.ChatModel.CLAUDE_3_OPUS.getName())
.withFunctionCallbacks(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.")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -304,9 +304,9 @@ class AnthropicChatModelIT {
var promptOptions = AnthropicChatOptions.builder()
.withModel(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getName())
.withFunctionCallbacks(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.")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -250,8 +250,8 @@ class AnthropicChatClientIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultFunctions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.defaultUser(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius."))
@@ -273,8 +273,8 @@ class AnthropicChatClientIT {
Flux<String> response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.stream()

View File

@@ -80,8 +80,8 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.method("getWeatherStatic", String.class, Unit.class)
.description("Get the weather in location")
.targetClass(TestFunctionClass.class)
.build())
.call()
@@ -102,8 +102,8 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("Turn light on in the living room.")
.functions(FunctionCallback.builder()
.description("Turn light on in the living room.")
.method("turnLight", String.class, boolean.class)
.description("Turn light on in the living room.")
.targetObject(targetObject)
.build())
.call()
@@ -125,8 +125,8 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.method("getWeatherNonStatic", String.class, Unit.class)
.description("Get the weather in location")
.targetObject(targetObject)
.build())
.call()
@@ -147,8 +147,8 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.method("getWeatherWithContext", String.class, Unit.class, ToolContext.class)
.description("Get the weather in location")
.targetObject(targetObject)
.build())
.toolContext(Map.of("tool", "value"))
@@ -174,8 +174,8 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
assertThatThrownBy(() -> ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.method("getWeatherNonStatic", String.class, Unit.class)
.description("Get the weather in location")
.targetObject(targetObject)
.build())
.toolContext(Map.of("tool", "value"))
@@ -195,8 +195,8 @@ class AnthropicChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("Turn light on in the living room.")
.functions(FunctionCallback.builder()
.description("Can turn lights on in the Living Room")
.method("turnLivingRoomLightOn")
.description("Can turn lights on in the Living Room")
.targetObject(targetObject)
.build())
.call()

View File

@@ -70,8 +70,8 @@ class AzureOpenAiChatModelFunctionCallIT {
var promptOptions = AzureOpenAiChatOptions.builder()
.withDeploymentName(this.selectedModel)
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the current weather in a given location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the current weather in a given location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -94,8 +94,8 @@ class AzureOpenAiChatModelFunctionCallIT {
var promptOptions = AzureOpenAiChatOptions.builder()
.withDeploymentName(this.selectedModel)
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the current weather in a given location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the current weather in a given location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -116,8 +116,8 @@ class AzureOpenAiChatModelFunctionCallIT {
var promptOptions = AzureOpenAiChatOptions.builder()
.withDeploymentName(this.selectedModel)
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the current weather in a given location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the current weather in a given location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -153,8 +153,8 @@ class AzureOpenAiChatModelFunctionCallIT {
var promptOptions = AzureOpenAiChatOptions.builder()
.withDeploymentName(this.selectedModel)
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the current weather in a given location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the current weather in a given location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -214,10 +214,10 @@ class BedrockConverseChatClientIT {
String response = ChatClient.create(this.chatModel)
.prompt("What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.call()
.content();
// @formatter:on
@@ -234,10 +234,10 @@ class BedrockConverseChatClientIT {
ChatResponse response = ChatClient.create(this.chatModel)
.prompt("What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.call()
.chatResponse();
// @formatter:on
@@ -269,10 +269,10 @@ class BedrockConverseChatClientIT {
String response = ChatClient.create(this.chatModel)
.prompt("What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.advisors(new SimpleLoggerAdvisor())
.call()
.content();
@@ -289,8 +289,8 @@ class BedrockConverseChatClientIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultFunctions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.defaultUser(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius."))
@@ -312,8 +312,8 @@ class BedrockConverseChatClientIT {
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.")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.stream()
@@ -354,8 +354,8 @@ class BedrockConverseChatClientIT {
Flux<String> response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in Paris? Return the temperature in Celsius.")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.stream()

View File

@@ -139,8 +139,8 @@ public class BedrockConverseUsageAggregationTests {
.willReturn(converseResponseFinal);
FunctionCallback functionCallback = FunctionCallback.builder()
.description("Gets the weather in location")
.function("getCurrentWeather", (Request request) -> "15.0°C")
.description("Gets the weather in location")
.inputType(Request.class)
.build();

View File

@@ -255,9 +255,9 @@ class BedrockProxyChatModelIT {
var promptOptions = FunctionCallingOptions.builder()
.withFunctionCallbacks(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.")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -283,9 +283,9 @@ class BedrockProxyChatModelIT {
var promptOptions = FunctionCallingOptions.builder()
.withModel("anthropic.claude-3-5-sonnet-20240620-v1:0")
.withFunctionCallbacks(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.")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -53,8 +53,8 @@ public final class BedrockConverseChatModelMain2 {
PortableFunctionCallingOptions.builder()
.withModel(modelId)
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build());

View File

@@ -51,8 +51,8 @@ public final class BedrockConverseChatModelMain3 {
PortableFunctionCallingOptions.builder()
.withModel(modelId)
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build());

View File

@@ -68,8 +68,8 @@ public class ChatCompletionRequestTests {
MiniMaxChatOptions.builder()
.withModel("PROMPT_MODEL")
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build()),
@@ -95,8 +95,8 @@ public class ChatCompletionRequestTests {
MiniMaxChatOptions.builder()
.withModel("DEFAULT_MODEL")
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build());
@@ -127,8 +127,8 @@ public class ChatCompletionRequestTests {
request = client.createRequest(new Prompt("Test message content",
MiniMaxChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Overridden function description")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.description("Overridden function description")
.inputType(MockWeatherService.Request.class)
.build()))
.build()),

View File

@@ -226,10 +226,10 @@ class MistralAiChatClientIT {
.options(MistralAiChatOptions.builder().withModel(MistralAiApi.ChatModel.SMALL).withToolChoice(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."))
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.call()
.content();
// @formatter:on
@@ -248,8 +248,8 @@ class MistralAiChatClientIT {
String response = ChatClient.builder(this.chatModel)
.defaultOptions(MistralAiChatOptions.builder().withModel(MistralAiApi.ChatModel.SMALL).build())
.defaultFunctions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.defaultUser(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."))
@@ -272,8 +272,8 @@ class MistralAiChatClientIT {
.options(MistralAiChatOptions.builder().withModel(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.")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.stream()

View File

@@ -194,8 +194,8 @@ class MistralAiChatModelIT {
var promptOptions = MistralAiChatOptions.builder()
.withModel(MistralAiApi.ChatModel.SMALL.getValue())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -217,8 +217,8 @@ class MistralAiChatModelIT {
var promptOptions = MistralAiChatOptions.builder()
.withModel(MistralAiApi.ChatModel.SMALL.getValue())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -64,8 +64,8 @@ class MoonshotChatModelFunctionCallingIT {
var promptOptions = MoonshotChatOptions.builder()
.withModel(MoonshotApi.ChatModel.MOONSHOT_V1_8K.getValue())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -87,8 +87,8 @@ class MoonshotChatModelFunctionCallingIT {
var promptOptions = MoonshotChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.build()))
.build();

View File

@@ -64,9 +64,9 @@ class OllamaChatModelFunctionCallingIT extends BaseOllamaIT {
var promptOptions = OllamaOptions.builder()
.withModel(MODEL)
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.function("getCurrentWeather", new MockWeatherService())
.description(
"Find the weather conditions, forecasts, and temperatures for a location, like a city or state.")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -89,9 +89,9 @@ class OllamaChatModelFunctionCallingIT extends BaseOllamaIT {
var promptOptions = OllamaOptions.builder()
.withModel(MODEL)
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.function("getCurrentWeather", new MockWeatherService())
.description(
"Find the weather conditions, forecasts, and temperatures for a location, like a city or state.")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -68,8 +68,8 @@ public class ChatCompletionRequestTests {
OpenAiChatOptions.builder()
.withModel("PROMPT_MODEL")
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build()),
@@ -95,8 +95,8 @@ public class ChatCompletionRequestTests {
OpenAiChatOptions.builder()
.withModel("DEFAULT_MODEL")
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build());
@@ -127,8 +127,8 @@ public class ChatCompletionRequestTests {
request = client.createRequest(new Prompt("Test message content",
OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Overridden function description")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.description("Overridden function description")
.inputType(MockWeatherService.Request.class)
.build()))
.build()),

View File

@@ -85,8 +85,8 @@ class OpenAiChatModelFunctionCallingIT {
functionCallTest(OpenAiChatOptions.builder()
.withModel(OpenAiApi.ChatModel.GPT_4_O.getValue())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build());
@@ -121,8 +121,8 @@ class OpenAiChatModelFunctionCallingIT {
functionCallTest(OpenAiChatOptions.builder()
.withModel(OpenAiApi.ChatModel.GPT_4_O.getValue())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", biFunction)
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.withToolContext(Map.of("sessionId", "123"))
@@ -147,8 +147,8 @@ class OpenAiChatModelFunctionCallingIT {
streamFunctionCallTest(OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of((FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
// .responseConverter(response -> "" + response.temp() + response.unit())
.build())))
@@ -183,8 +183,8 @@ class OpenAiChatModelFunctionCallingIT {
OpenAiChatOptions promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of((FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", biFunction)
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())))
.withToolContext(Map.of("sessionId", "123"))

View File

@@ -330,8 +330,8 @@ public class OpenAiChatModelIT extends AbstractIT {
var promptOptions = OpenAiChatOptions.builder()
.withModel(OpenAiApi.ChatModel.GPT_4_O.getValue())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -355,8 +355,8 @@ public class OpenAiChatModelIT extends AbstractIT {
var promptOptions = OpenAiChatOptions.builder()
// .withModel(OpenAiApi.ChatModel.GPT_4_TURBO_PREVIEW.getValue())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -246,8 +246,8 @@ class OpenAiChatClientIT extends AbstractIT {
void functionCallTest() {
FunctionCallback functionCallback = FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build();
@@ -270,8 +270,8 @@ class OpenAiChatClientIT extends AbstractIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultFunctions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.defaultUser(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris?"))
@@ -291,8 +291,8 @@ class OpenAiChatClientIT extends AbstractIT {
Flux<String> response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris?")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.stream()

View File

@@ -61,8 +61,8 @@ class OpenAiChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.method("getWeatherStatic", String.class, Unit.class)
.description("Get the weather in location")
.targetClass(TestFunctionClass.class)
.build())
.call()
@@ -83,8 +83,8 @@ class OpenAiChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("Turn light on in the living room.")
.functions(FunctionCallback.builder()
.description("Can turn lights on or off by room name")
.method("turnLight", String.class, boolean.class)
.description("Can turn lights on or off by room name")
.targetObject(targetObject)
.build())
.call()
@@ -106,8 +106,8 @@ class OpenAiChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.method("getWeatherNonStatic", String.class, Unit.class)
.description("Get the weather in location")
.targetObject(targetObject)
.build())
.call()
@@ -128,8 +128,8 @@ class OpenAiChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.method("getWeatherWithContext", String.class, Unit.class, ToolContext.class)
.description("Get the weather in location")
.targetObject(targetObject)
.build())
.toolContext(Map.of("tool", "value"))
@@ -152,8 +152,8 @@ class OpenAiChatClientMethodInvokingFunctionCallbackIT {
assertThatThrownBy(() -> ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.method("getWeatherNonStatic", String.class, Unit.class)
.description("Get the weather in location")
.targetObject(targetObject)
.build())
.toolContext(Map.of("tool", "value"))
@@ -173,8 +173,8 @@ class OpenAiChatClientMethodInvokingFunctionCallbackIT {
String response = ChatClient.create(this.chatModel).prompt()
.user("Turn light on in the living room.")
.functions(FunctionCallback.builder()
.description("Can turn lights on in the Living Room")
.method("turnLivingRoomLightOn")
.description("Can turn lights on in the Living Room")
.targetObject(targetObject)
.build())
.call()

View File

@@ -85,8 +85,8 @@ class OpenAiChatClientMultipleFunctionCallsIT extends AbstractIT {
response = chatClientBuilder.build().prompt()
.user(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris?"))
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.call()
@@ -116,8 +116,8 @@ class OpenAiChatClientMultipleFunctionCallsIT extends AbstractIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultFunctions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.defaultUser(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris?"))
@@ -159,8 +159,8 @@ class OpenAiChatClientMultipleFunctionCallsIT extends AbstractIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultFunctions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", biFunction)
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.defaultUser(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris?"))
@@ -203,8 +203,8 @@ class OpenAiChatClientMultipleFunctionCallsIT extends AbstractIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultFunctions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", biFunction)
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.defaultUser(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris?"))
@@ -226,8 +226,8 @@ class OpenAiChatClientMultipleFunctionCallsIT extends AbstractIT {
Flux<String> response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris?")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.stream()

View File

@@ -250,8 +250,8 @@ class GroqWithOpenAiChatModelIT {
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -273,8 +273,8 @@ class GroqWithOpenAiChatModelIT {
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -252,8 +252,8 @@ class MistralWithOpenAiChatModelIT {
var promptOptions = OpenAiChatOptions.builder()
.withModel(modelName)
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -277,8 +277,8 @@ class MistralWithOpenAiChatModelIT {
var promptOptions = OpenAiChatOptions.builder()
.withModel(modelName)
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -247,8 +247,8 @@ class NvidiaWithOpenAiChatModelIT {
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -270,8 +270,8 @@ class NvidiaWithOpenAiChatModelIT {
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -269,8 +269,8 @@ class OllamaWithOpenAiChatModelIT {
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -293,8 +293,8 @@ class OllamaWithOpenAiChatModelIT {
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.build()))
.build();

View File

@@ -257,8 +257,8 @@ class PerplexityWithOpenAiChatModelIT {
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -279,8 +279,8 @@ class PerplexityWithOpenAiChatModelIT {
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -118,8 +118,8 @@ public class CreateGeminiRequestTests {
VertexAiGeminiChatOptions.builder()
.withModel("PROMPT_MODEL")
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build()),
@@ -146,8 +146,8 @@ public class CreateGeminiRequestTests {
VertexAiGeminiChatOptions.builder()
.withModel("DEFAULT_MODEL")
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build());
@@ -179,8 +179,8 @@ public class CreateGeminiRequestTests {
request = client.createGeminiRequest(new Prompt("Test message content",
VertexAiGeminiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Overridden function description")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.description("Overridden function description")
.inputType(MockWeatherService.Request.class)
.build()))
.build()),

View File

@@ -84,9 +84,9 @@ public class VertexAiGeminiChatModelFunctionCallingIT {
var promptOptions = VertexAiGeminiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.function("get_current_weather", new MockWeatherService())
.description("Get the current weather in a given location")
.inputTypeSchema(openApiSchema)
.function("get_current_weather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -109,16 +109,16 @@ public class VertexAiGeminiChatModelFunctionCallingIT {
.withModel(VertexAiGeminiChatModel.ChatModel.GEMINI_1_5_FLASH)
.withFunctionCallbacks(List.of(
FunctionCallback.builder()
.function("get_current_weather", new MockWeatherService())
.schemaType(SchemaType.OPEN_API_SCHEMA)
.description("Get the current weather in a given location.")
.function("get_current_weather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build(),
FunctionCallback.builder()
.function("get_payment_status", new PaymentStatus())
.schemaType(SchemaType.OPEN_API_SCHEMA)
.description(
"Retrieves the payment status for transaction. For example what is the payment status for transaction 700?")
.function("get_payment_status", new PaymentStatus())
.inputType(PaymentInfoRequest.class)
.build()))
.build();
@@ -150,16 +150,16 @@ public class VertexAiGeminiChatModelFunctionCallingIT {
.withModel(VertexAiGeminiChatModel.ChatModel.GEMINI_1_5_FLASH)
.withFunctionCallbacks(List.of(
FunctionCallback.builder()
.function("get_current_weather", new MockWeatherService())
.schemaType(SchemaType.OPEN_API_SCHEMA)
.description("Get the current weather in a given location.")
.function("get_current_weather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build(),
FunctionCallback.builder()
.function("get_payment_status", new PaymentStatus())
.schemaType(SchemaType.OPEN_API_SCHEMA)
.description(
"Retrieves the payment status for transaction. For example what is the payment status for transaction 700?")
.function("get_payment_status", new PaymentStatus())
.inputType(PaymentInfoRequest.class)
.build()))
.build();
@@ -190,9 +190,9 @@ public class VertexAiGeminiChatModelFunctionCallingIT {
var promptOptions = VertexAiGeminiChatOptions.builder()
.withModel(VertexAiGeminiChatModel.ChatModel.GEMINI_1_5_FLASH)
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.function("getCurrentWeather", new MockWeatherService())
.schemaType(SchemaType.OPEN_API_SCHEMA)
.description("Get the current weather in a given location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -68,8 +68,8 @@ public class ChatCompletionRequestTests {
ZhiPuAiChatOptions.builder()
.withModel("PROMPT_MODEL")
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build()),
@@ -95,8 +95,8 @@ public class ChatCompletionRequestTests {
ZhiPuAiChatOptions.builder()
.withModel("DEFAULT_MODEL")
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build());
@@ -127,8 +127,8 @@ public class ChatCompletionRequestTests {
request = client.createRequest(new Prompt("Test message content",
ZhiPuAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Overridden function description")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.description("Overridden function description")
.inputType(MockWeatherService.Request.class)
.build()))
.build()),

View File

@@ -231,8 +231,8 @@ class ZhiPuAiChatModelIT {
var promptOptions = ZhiPuAiChatOptions.builder()
.withModel(ZhiPuAiApi.ChatModel.GLM_4.getValue())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -257,8 +257,8 @@ class ZhiPuAiChatModelIT {
var promptOptions = ZhiPuAiChatOptions.builder()
.withModel(ZhiPuAiApi.ChatModel.GLM_4.getValue())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -878,9 +878,9 @@ public class DefaultChatClient implements ChatClient {
Assert.notNull(function, "function cannot be null");
var fcw = FunctionCallback.builder()
.function(name, function)
.description(description)
.responseConverter(Object::toString)
.function(name, function)
.inputType(inputType)
.build();
this.functionCallbacks.add(fcw);

View File

@@ -0,0 +1,115 @@
package org.springframework.ai.model.function;
import java.util.function.Function;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.springframework.ai.model.function.FunctionCallback.CommonCallbackInvokingSpec;
import org.springframework.ai.model.function.FunctionCallback.SchemaType;
import org.springframework.ai.util.JacksonUtils;
import org.springframework.util.Assert;
public class DefaultCommonCallbackInvokingSpec<B extends CommonCallbackInvokingSpec<B>>
implements CommonCallbackInvokingSpec<B> {
/**
* The description of the function callback. Used to hint the LLM model about the
* tool's purpose and when to use it.
*/
protected String description;
/**
* The schema type to use for the input type schema generation. The default is JSON
* Schema. Note: Vertex AI requires the input type schema to be in Open API schema
*/
protected SchemaType schemaType = SchemaType.JSON_SCHEMA;
/**
* The function to convert the response object to a string. The default is to convert
* the response to a JSON string.
*/
protected Function<Object, String> responseConverter = response -> (response instanceof String) ? "" + response
: this.toJsonString(response);
/**
* (Optional) Instead of generating the input type schema from the input type or
* method argument types, you can provide the schema directly. This will override the
* generated schema.
*/
protected String inputTypeSchema;
protected ObjectMapper objectMapper = JsonMapper.builder()
.addModules(JacksonUtils.instantiateAvailableModules())
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
.build();
private String toJsonString(Object object) {
try {
return this.objectMapper.writeValueAsString(object);
}
catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
@Override
public B description(String description) {
Assert.hasText(description, "Description must not be empty");
this.description = description;
return (B) this;
}
@Override
public B schemaType(SchemaType schemaType) {
Assert.notNull(schemaType, "SchemaType must not be null");
this.schemaType = schemaType;
return (B) this;
}
@Override
public B responseConverter(Function<Object, String> responseConverter) {
Assert.notNull(responseConverter, "ResponseConverter must not be null");
this.responseConverter = responseConverter;
return (B) this;
}
@Override
public B inputTypeSchema(String inputTypeSchema) {
Assert.hasText(inputTypeSchema, "InputTypeSchema must not be empty");
this.inputTypeSchema = inputTypeSchema;
return (B) this;
}
@Override
public B objectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
return (B) this;
}
public String getDescription() {
return this.description;
}
public SchemaType getSchemaType() {
return this.schemaType;
}
public Function<Object, String> getResponseConverter() {
return this.responseConverter;
}
public String getInputTypeSchema() {
return this.inputTypeSchema;
}
public ObjectMapper getObjectMapper() {
return this.objectMapper;
}
}

View File

@@ -23,21 +23,14 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.model.function.FunctionCallback.Builder;
import org.springframework.ai.model.function.FunctionCallback.FunctionInvokingSpec;
import org.springframework.ai.model.function.FunctionCallback.MethodInvokingSpec;
import org.springframework.ai.model.function.FunctionCallback.SchemaType;
import org.springframework.ai.util.JacksonUtils;
import org.springframework.ai.util.ParsingUtils;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.util.Assert;
@@ -54,82 +47,6 @@ public class DefaultFunctionCallbackBuilder implements FunctionCallback.Builder
private final static Logger logger = LoggerFactory.getLogger(DefaultFunctionCallbackBuilder.class);
/**
* The description of the function callback. Used to hint the LLM model about the
* tool's purpose and when to use it.
*/
private String description;
/**
* The schema type to use for the input type schema generation. The default is JSON
* Schema. Note: Vertex AI requires the input type schema to be in Open API schema
*/
private SchemaType schemaType = SchemaType.JSON_SCHEMA;
/**
* The function to convert the response object to a string. The default is to convert
* the response to a JSON string.
*/
private Function<Object, String> responseConverter = response -> (response instanceof String) ? "" + response
: this.toJsonString(response);
/**
* (Optional) Instead of generating the input type schema from the input type or
* method argument types, you can provide the schema directly. This will override the
* generated schema.
*/
private String inputTypeSchema;
private ObjectMapper objectMapper = JsonMapper.builder()
.addModules(JacksonUtils.instantiateAvailableModules())
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
.build();
private String toJsonString(Object object) {
try {
return this.objectMapper.writeValueAsString(object);
}
catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
@Override
public Builder description(String description) {
Assert.hasText(description, "Description must not be empty");
this.description = description;
return this;
}
@Override
public Builder schemaType(SchemaType schemaType) {
Assert.notNull(schemaType, "SchemaType must not be null");
this.schemaType = schemaType;
return this;
}
@Override
public Builder responseConverter(Function<Object, String> responseConverter) {
Assert.notNull(responseConverter, "ResponseConverter must not be null");
this.responseConverter = responseConverter;
return this;
}
@Override
public Builder inputTypeSchema(String inputTypeSchema) {
Assert.hasText(inputTypeSchema, "InputTypeSchema must not be empty");
this.inputTypeSchema = inputTypeSchema;
return this;
}
@Override
public Builder objectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
return this;
}
@Override
public <I, O> FunctionInvokingSpec<I, O> function(String name, Function<I, O> function) {
return new DefaultFunctionInvokingSpec<>(name, function);
@@ -170,7 +87,8 @@ public class DefaultFunctionCallbackBuilder implements FunctionCallback.Builder
return generatedDescription;
}
final class DefaultFunctionInvokingSpec<I, O> implements FunctionInvokingSpec<I, O> {
final class DefaultFunctionInvokingSpec<I, O> extends DefaultCommonCallbackInvokingSpec<FunctionInvokingSpec<I, O>>
implements FunctionInvokingSpec<I, O> {
private final String name;
@@ -213,33 +131,35 @@ public class DefaultFunctionCallbackBuilder implements FunctionCallback.Builder
@Override
public FunctionCallback build() {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
Assert.notNull(this.getObjectMapper(), "ObjectMapper must not be null");
Assert.hasText(this.name, "Name must not be empty");
Assert.notNull(responseConverter, "ResponseConverter must not be null");
Assert.notNull(this.getResponseConverter(), "ResponseConverter must not be null");
Assert.notNull(this.inputType, "InputType must not be null");
if (inputTypeSchema == null) {
if (this.getInputTypeSchema() == null) {
boolean upperCaseTypeValues = schemaType == SchemaType.OPEN_API_SCHEMA;
inputTypeSchema = ModelOptionsUtils.getJsonSchema(this.inputType, upperCaseTypeValues);
this.inputTypeSchema = ModelOptionsUtils.getJsonSchema(this.inputType, upperCaseTypeValues);
}
BiFunction<I, ToolContext, O> finalBiFunction = (this.biFunction != null) ? this.biFunction
: (request, context) -> this.function.apply(request);
return new FunctionInvokingFunctionCallback(this.name, this.getDescription(), inputTypeSchema,
this.inputType, (Function<I, String>) responseConverter, objectMapper, finalBiFunction);
return new FunctionInvokingFunctionCallback(this.name, this.getDescriptionExt(), this.getInputTypeSchema(),
this.inputType, (Function<I, String>) this.getResponseConverter(), this.getObjectMapper(),
finalBiFunction);
}
private String getDescription() {
if (StringUtils.hasText(description)) {
return description;
private String getDescriptionExt() {
if (StringUtils.hasText(this.getDescription())) {
return this.getDescription();
}
return generateDescription(this.name);
}
}
final class DefaultMethodInvokingSpec implements FunctionCallback.MethodInvokingSpec {
final class DefaultMethodInvokingSpec extends DefaultCommonCallbackInvokingSpec<MethodInvokingSpec>
implements FunctionCallback.MethodInvokingSpec {
private String name;
@@ -285,13 +205,13 @@ public class DefaultFunctionCallbackBuilder implements FunctionCallback.Builder
var method = ReflectionUtils.findMethod(this.targetClass, this.methodName, this.argumentTypes);
Assert.notNull(method, "Method: '" + this.methodName + "' with arguments:"
+ Arrays.toString(this.argumentTypes) + " not found!");
return new MethodInvokingFunctionCallback(this.targetObject, method, this.getDescription(), objectMapper,
this.name, responseConverter);
return new MethodInvokingFunctionCallback(this.targetObject, method, this.getDescriptionExt(),
this.getObjectMapper(), this.name, this.getResponseConverter());
}
private String getDescription() {
if (StringUtils.hasText(description)) {
return description;
private String getDescriptionExt() {
if (StringUtils.hasText(this.getDescription())) {
return this.getDescription();
}
return generateDescription(StringUtils.hasText(this.name) ? this.name : this.methodName);

View File

@@ -117,25 +117,25 @@ public class DefaultFunctionCallbackResolver implements ApplicationContextAware,
if (KotlinDetector.isKotlinPresent()) {
if (KotlinDelegate.isKotlinFunction(functionType.toClass())) {
return FunctionCallback.builder()
.function(beanName, KotlinDelegate.wrapKotlinFunction(bean))
.schemaType(this.schemaType)
.description(functionDescription)
.function(beanName, KotlinDelegate.wrapKotlinFunction(bean))
.inputType(ParameterizedTypeReference.forType(functionInputType.getType()))
.build();
}
if (KotlinDelegate.isKotlinBiFunction(functionType.toClass())) {
return FunctionCallback.builder()
.function(beanName, KotlinDelegate.wrapKotlinBiFunction(bean))
.description(functionDescription)
.schemaType(this.schemaType)
.function(beanName, KotlinDelegate.wrapKotlinBiFunction(bean))
.inputType(ParameterizedTypeReference.forType(functionInputType.getType()))
.build();
}
if (KotlinDelegate.isKotlinSupplier(functionType.toClass())) {
return FunctionCallback.builder()
.function(beanName, KotlinDelegate.wrapKotlinSupplier(bean))
.description(functionDescription)
.schemaType(this.schemaType)
.function(beanName, KotlinDelegate.wrapKotlinSupplier(bean))
.inputType(ParameterizedTypeReference.forType(functionInputType.getType()))
.build();
}
@@ -143,33 +143,33 @@ public class DefaultFunctionCallbackResolver implements ApplicationContextAware,
if (bean instanceof Function<?, ?> function) {
return FunctionCallback.builder()
.function(beanName, function)
.schemaType(this.schemaType)
.description(functionDescription)
.function(beanName, function)
.inputType(ParameterizedTypeReference.forType(functionInputType.getType()))
.build();
}
if (bean instanceof BiFunction<?, ?, ?>) {
return FunctionCallback.builder()
.function(beanName, (BiFunction<?, ToolContext, ?>) bean)
.description(functionDescription)
.schemaType(this.schemaType)
.function(beanName, (BiFunction<?, ToolContext, ?>) bean)
.inputType(ParameterizedTypeReference.forType(functionInputType.getType()))
.build();
}
if (bean instanceof Supplier<?> supplier) {
return FunctionCallback.builder()
.function(beanName, supplier)
.description(functionDescription)
.schemaType(this.schemaType)
.function(beanName, supplier)
.inputType(ParameterizedTypeReference.forType(functionInputType.getType()))
.build();
}
if (bean instanceof Consumer<?> consumer) {
return FunctionCallback.builder()
.function(beanName, consumer)
.description(functionDescription)
.schemaType(this.schemaType)
.function(beanName, consumer)
.inputType(ParameterizedTypeReference.forType(functionInputType.getType()))
.build();
}

View File

@@ -118,37 +118,6 @@ public interface FunctionCallback {
*/
interface Builder {
/**
* Function description. This description is used by the model do decide if the
* function should be called or not.
*/
Builder description(String description);
/**
* Specifies what {@link SchemaType} is used by the AI model to validate the
* function input arguments. Most models use JSON Schema, except Vertex AI that
* uses OpenAPI types.
*/
Builder schemaType(SchemaType schemaType);
/**
* Function response converter. The default implementation converts the output
* into String before sending it to the Model. Provide a custom function
* responseConverter implementation to override this.
*/
Builder responseConverter(Function<Object, String> responseConverter);
/**
* You can provide the Input Type Schema directly. In this case it won't be
* generated from the inputType.
*/
Builder inputTypeSchema(String inputTypeSchema);
/**
* Custom object mapper for JSON operations.
*/
Builder objectMapper(ObjectMapper objectMapper);
/**
* Builds a {@link Function} invoking {@link FunctionCallback} instance.
*/
@@ -176,13 +145,48 @@ public interface FunctionCallback {
}
interface CommonCallbackInvokingSpec<B extends CommonCallbackInvokingSpec<B>> {
/**
* Function description. This description is used by the model to decide if the
* function should be called or not.
*/
B description(String description);
/**
* Specifies what {@link SchemaType} is used by the AI model to validate the
* function input arguments. Most models use JSON Schema, except Vertex AI that
* uses OpenAPI types.
*/
B schemaType(SchemaType schemaType);
/**
* Function response converter. The default implementation converts the output
* into String before sending it to the Model. Provide a custom function
* responseConverter implementation to override this.
*/
B responseConverter(Function<Object, String> responseConverter);
/**
* You can provide the Input Type Schema directly. In this case it won't be
* generated from the inputType.
*/
B inputTypeSchema(String inputTypeSchema);
/**
* Custom object mapper for JSON operations.
*/
B objectMapper(ObjectMapper objectMapper);
}
/**
* {@link Function} invoking builder interface.
*
* @param <I> Function input type.
* @param <O> Function output type.
*/
interface FunctionInvokingSpec<I, O> {
interface FunctionInvokingSpec<I, O> extends CommonCallbackInvokingSpec<FunctionInvokingSpec<I, O>> {
/**
* Function input type. The input type is used to validate the function input
@@ -207,7 +211,7 @@ public interface FunctionCallback {
/**
* Method invoking builder interface.
*/
interface MethodInvokingSpec {
interface MethodInvokingSpec extends CommonCallbackInvokingSpec<MethodInvokingSpec> {
/**
* Optional function name. If not provided the method name is used as the

View File

@@ -80,8 +80,8 @@ public class ChatBuilderTests {
String func = "func";
FunctionCallback cb = FunctionCallback.builder()
.description("cb")
.function("cb", i -> i)
.description("cb")
.inputType(Integer.class)
.build();

View File

@@ -219,8 +219,8 @@ public class ChatClientTest {
.param("param2", "value2"))
.defaultFunctions("fun1", "fun2")
.defaultFunctions(FunctionCallback.builder()
.description("fun3description")
.function("fun3", mockFunction)
.description("fun3description")
.inputType(String.class)
.build())
.defaultUser(u -> u.text("Default user text {uparam1}, {uparam2}")
@@ -350,8 +350,8 @@ public class ChatClientTest {
.param("param2", "value2"))
.defaultFunctions("fun1", "fun2")
.defaultFunctions(FunctionCallback.builder()
.description("fun3description")
.function("fun3", mockFunction)
.description("fun3description")
.inputType(String.class)
.build())
.defaultUser(u -> u.text("Default user text {uparam1}, {uparam2}")

View File

@@ -1357,8 +1357,8 @@ class DefaultChatClientTests {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
assertThatThrownBy(() -> spec.functions(FunctionCallback.builder()
.description("description")
.function(null, input -> "hello")
.description("description")
.inputType(String.class)
.build())).isInstanceOf(IllegalArgumentException.class).hasMessage("Name must not be empty");
}
@@ -1368,8 +1368,8 @@ class DefaultChatClientTests {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
assertThatThrownBy(() -> spec.functions(FunctionCallback.builder()
.description("description")
.function("", input -> "hello")
.description("description")
.inputType(String.class)
.build())).isInstanceOf(IllegalArgumentException.class).hasMessage("Name must not be empty");
}
@@ -1379,8 +1379,8 @@ class DefaultChatClientTests {
ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();
ChatClient.ChatClientRequestSpec spec = chatClient.prompt();
assertThatThrownBy(() -> spec.functions(FunctionCallback.builder()
.function("name", input -> "hello")
.description(null)
.function("", input -> "hello")
.inputType(String.class)
.build())).isInstanceOf(IllegalArgumentException.class).hasMessage("Description must not be empty");
}

View File

@@ -35,51 +35,54 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
*/
class DefaultFunctionCallbackBuilderTests {
// Common
// Function
@Test
void whenDescriptionIsNullThenThrow() {
assertThatThrownBy(() -> FunctionCallback.builder().description(null))
void whenFunctionDescriptionIsNullThenThrow() {
assertThatThrownBy(
() -> FunctionCallback.builder().function("functionName", input -> "output").description(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Description must not be empty");
}
@Test
void whenDescriptionIsEmptyThenThrow() {
assertThatThrownBy(() -> FunctionCallback.builder().description(""))
void whenFunctionDescriptionIsEmptyThenThrow() {
assertThatThrownBy(() -> FunctionCallback.builder().function("functionName", input -> "output").description(""))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Description must not be empty");
}
@Test
void whenInputTypeSchemaIsNullThenThrow() {
assertThatThrownBy(() -> FunctionCallback.builder().inputTypeSchema(null))
void whenFunctionInputTypeSchemaIsNullThenThrow() {
assertThatThrownBy(
() -> FunctionCallback.builder().function("functionName", input -> "output").inputTypeSchema(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("InputTypeSchema must not be empty");
}
@Test
void whenInputTypeSchemaIsEmptyThenThrow() {
assertThatThrownBy(() -> FunctionCallback.builder().inputTypeSchema(""))
void whenFunctionInputTypeSchemaIsEmptyThenThrow() {
assertThatThrownBy(
() -> FunctionCallback.builder().function("functionName", input -> "output").inputTypeSchema(""))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("InputTypeSchema must not be empty");
}
@Test
void whenSchemaTypeIsNullThenThrow() {
assertThatThrownBy(() -> FunctionCallback.builder().schemaType(null))
void whenFunctionSchemaTypeIsNullThenThrow() {
assertThatThrownBy(
() -> FunctionCallback.builder().function("functionName", input -> "output").schemaType(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("SchemaType must not be null");
}
@Test
void whenResponseConverterIsNullThenThrow() {
assertThatThrownBy(() -> FunctionCallback.builder().responseConverter(null))
void whenFunctionResponseConverterIsNullThenThrow() {
assertThatThrownBy(
() -> FunctionCallback.builder().function("functionName", input -> "output").responseConverter(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("ResponseConverter must not be null");
}
// Function
@Test
void whenFunctionNameIsNullThenThrow2() {
assertThatThrownBy(() -> FunctionCallback.builder().function(null, (Function) null))
@@ -111,8 +114,8 @@ class DefaultFunctionCallbackBuilderTests {
@Test
void whenFunctionWithInputTypeThenReturn() {
FunctionCallback functionCallback = FunctionCallback.builder()
.description("description")
.function("functionName", input -> "output")
.description("description")
.inputType(String.class)
.build();
assertThat(functionCallback).isNotNull();
@@ -179,6 +182,49 @@ class DefaultFunctionCallbackBuilderTests {
}
// Method
@Test
void whenMethodDescriptionIsNullThenThrow() {
assertThatThrownBy(() -> FunctionCallback.builder().method("methodName").description(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Description must not be empty");
}
@Test
void whenMethodDescriptionIsEmptyThenThrow() {
assertThatThrownBy(() -> FunctionCallback.builder().method("methodName").description(""))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Description must not be empty");
}
@Test
void whenMethodInputTypeSchemaIsNullThenThrow() {
assertThatThrownBy(() -> FunctionCallback.builder().method("methodName").inputTypeSchema(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("InputTypeSchema must not be empty");
}
@Test
void whenMethodInputTypeSchemaIsEmptyThenThrow() {
assertThatThrownBy(() -> FunctionCallback.builder().method("methodName").inputTypeSchema(""))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("InputTypeSchema must not be empty");
}
@Test
void whenMethodSchemaTypeIsNullThenThrow() {
assertThatThrownBy(() -> FunctionCallback.builder().method("methodName").schemaType(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("SchemaType must not be null");
}
@Test
void whenMethodResponseConverterIsNullThenThrow() {
assertThatThrownBy(() -> FunctionCallback.builder().method("methodName").responseConverter(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("ResponseConverter must not be null");
}
@Test
void whenMethodNameIsNullThenThrow() {
assertThatThrownBy(() -> FunctionCallback.builder().method(null)).isInstanceOf(IllegalArgumentException.class)

View File

@@ -56,9 +56,9 @@ public class MethodInvokingFunctionCallbackTests {
public void staticMethod() throws NoSuchMethodException, SecurityException {
var functionCallback = FunctionCallback.builder()
.method("myStaticMethod", String.class, Unit.class, int.class, MyRecord.class, List.class)
.description("weather at location")
.objectMapper(new ObjectMapper())
.method("myStaticMethod", String.class, Unit.class, int.class, MyRecord.class, List.class)
.targetClass(TestClassWithFunctionMethods.class)
.build();
@@ -80,8 +80,8 @@ public class MethodInvokingFunctionCallbackTests {
var object = new TestClassWithFunctionMethods();
var functionCallback = FunctionCallback.builder()
.description("weather at location")
.method("myNonStaticMethod", String.class, Unit.class, int.class, MyRecord.class, List.class)
.description("weather at location")
.targetObject(object)
.build();
@@ -101,9 +101,9 @@ public class MethodInvokingFunctionCallbackTests {
public void noArgsNoReturnMethod() throws NoSuchMethodException, SecurityException {
var functionCallback = FunctionCallback.builder()
.method("argumentLessReturnVoid")
.description("weather at location")
.objectMapper(new ObjectMapper())
.method("argumentLessReturnVoid")
.targetClass(TestClassWithFunctionMethods.class)
.build();

View File

@@ -109,8 +109,8 @@ var options = FunctionCallingOptions.builder()
.withTemperature(0.6)
.withMaxTokens(300)
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location. Return temperature in 36°F or 36°C format. Use multi-turn if needed.")
.function("getCurrentWeather", new WeatherService())
.description("Get the weather in location. Return temperature in 36°F or 36°C format. Use multi-turn if needed.")
.inputType(WeatherService.Request.class)
.build()))
.build();

View File

@@ -176,8 +176,8 @@ UserMessage userMessage = new UserMessage("What's the weather like in Paris?");
var promptOptions = AnthropicChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.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
.build())) // function code
.build();

View File

@@ -124,8 +124,8 @@ static class Config {
public FunctionCallback weatherFunctionInfo() {
return FunctionCallback.builder()
.description("Get the current weather in a given location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name
.description("Get the current weather in a given location") // (2) function description
.inputType(MockWeatherService.Request.class) // (3) function input type
.build();
}
@@ -181,8 +181,8 @@ UserMessage userMessage = new UserMessage("What's the weather like in San Franci
var promptOptions = AzureOpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the current weather in a given location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.description("Get the current weather in a given location") // (2) function description
.inputType(MockWeatherService.Request.class) // (3) function input type
.build()))
.build();

View File

@@ -128,8 +128,8 @@ static class Config {
public FunctionCallback weatherFunctionInfo() {
return FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.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
.build();
}
@@ -186,8 +186,8 @@ UserMessage userMessage = new UserMessage("What's the weather like in San Franci
var promptOptions = MiniMaxChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.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
.build())) // function code
.build();

View File

@@ -126,8 +126,8 @@ static class Config {
public FunctionCallback weatherFunctionInfo() {
return FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.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
.build();
}
@@ -174,8 +174,8 @@ UserMessage userMessage = new UserMessage("What's the weather like in Paris?");
var promptOptions = MistralAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.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
.build())) // function code
.build();

View File

@@ -128,8 +128,8 @@ static class Config {
public FunctionCallback weatherFunctionInfo() {
return FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.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
.build();
}
@@ -186,8 +186,8 @@ UserMessage userMessage = new UserMessage("What's the weather like in San Franci
var promptOptions = MoonshotChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name
.description("Get the weather in location") // (2) function description
.inputType(MockWeatherService.Request.class) // (3) function signature
.build())) // function code
.build();

View File

@@ -130,8 +130,8 @@ static class Config {
public FunctionCallback weatherFunctionInfo() {
return FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name
.description("Get the weather in location") // (2) function description
.inputType(MockWeatherService.Request.class) // (3) function signature
.build();
}
@@ -187,8 +187,8 @@ UserMessage userMessage = new UserMessage("What's the weather like in San Franci
var promptOptions = OllamaOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.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
.build())) // function code
.build();

View File

@@ -123,8 +123,8 @@ static class Config {
public FunctionCallback weatherFunctionInfo() {
return FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.description("Get the weather in location") // (2) function description
.inputType(MockWeatherService.Request.class) // (3) function input type
.build();
}
@@ -180,8 +180,8 @@ UserMessage userMessage = new UserMessage("What's the weather like in San Franci
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.description("Get the weather in location") // (2) function description
.inputType(MockWeatherService.Request.class) // (3) function input type
.build())) // function code
.build();

View File

@@ -130,9 +130,9 @@ static class Config {
public FunctionCallback weatherFunctionInfo() {
return FunctionCallback.builder()
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.description("Get the current weather in a given location") // (2) function description
.schemaType(SchemaType.OPEN_API_SCHEMA) // (3) schema type. Compulsory for Gemini function calling.
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.inputType(MockWeatherService.Request.class) // (4) input type
.build();
}
@@ -189,9 +189,9 @@ UserMessage userMessage = new UserMessage("What's the weather like in San Franci
var promptOptions = VertexAiGeminiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.function("CurrentWeather", new MockWeatherService())
.schemaType(SchemaType.OPEN_API_SCHEMA) // IMPORTANT!!
.description("Get the weather in location")
.function("CurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -128,8 +128,8 @@ static class Config {
public FunctionCallback weatherFunctionInfo() {
return FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.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
.build();
}
@@ -186,8 +186,8 @@ UserMessage userMessage = new UserMessage("What's the weather like in San Franci
var promptOptions = ZhiPuAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.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
.build())) // function code
.build();

View File

@@ -34,8 +34,8 @@ NOTE: You can use lambda expressions or method references to define the function
[source,java]
----
FunctionCallback callback = FunctionCallback.builder()
.description("Process a new order")
.function("processOrder", (Order order) -> processOrderLogic(order))
.description("Process a new order")
.inputType(Order.class)
.build();
----
@@ -47,9 +47,9 @@ Using Function with input type <I> and additional xref:api/functions.adoc#Tool-C
[source,java]
----
FunctionCallback callback = FunctionCallback.builder()
.description("Process a new order with context")
.function("processOrder", (Order order, ToolContext context) ->
processOrderWithContext(order, context))
.description("Process a new order with context")
.inputType(Order.class)
.build();
----
@@ -61,8 +61,8 @@ Use `java.util.Supplier<O>` or `java.util.function.Function<Void, O>` to define
[source,java]
----
FunctionCallback.builder()
.description("Turns light on in the living room")
.function("turnsLight", () -> state.put("Light", "ON"))
.description("Turns light on in the living room")
.inputType(Void.class)
.build();
----
@@ -76,10 +76,10 @@ Use `java.util.Consumer<I>` or `java.util.function.Function<I, Void>` to define
record LightInfo(String roomName, boolean isOn) {}
FunctionCallback.builder()
.description("Turns light on/off in a selected room")
.function("turnsLight", (LightInfo lightInfo) -> {
logger.info("Turning light to [" + lightInfo.isOn + "] in " + lightInfo.roomName());
})
.description("Turns light on/off in a selected room")
.inputType(LightInfo.class)
.build();
----
@@ -97,11 +97,11 @@ record TrainSearchSchedule(String from, String to, String date) {}
record TrainSearchScheduleResponse(String from, String to, String date, String trainNumber) {}
FunctionCallback.builder()
.description("Schedule a train reservation")
.function("trainSchedule", (TrainSearchRequest<TrainSearchSchedule> request) -> {
logger.info("Schedule: " + request.data().from() + " to " + request.data().to());
return new TrainSearchScheduleResponse(request.data().from(), request. data().to(), "", "123");
})
.description("Schedule a train reservation")
.inputType(new ParameterizedTypeReference<TrainSearchRequest<TrainSearchSchedule>>() {})
.build();
----
@@ -132,8 +132,8 @@ public class WeatherService {
}
FunctionCallback callback = FunctionCallback.builder()
.description("Get weather information for a city")
.method("getWeather", String.class, TemperatureUnit.class)
.description("Get weather information for a city")
.targetClass(WeatherService.class)
.build();
----
@@ -156,8 +156,8 @@ DeviceController controller = new DeviceController();
String response = ChatClient.create(chatModel).prompt()
.user("Turn on the living room lights")
.functions(FunctionCallback.builder()
.description("Control device state")
.method("setDeviceState", String.class,boolean.class,ToolContext.class)
.description("Control device state")
.targetObject(controller)
.build())
.toolContext(Map.of("location", "home"))

View File

@@ -206,8 +206,8 @@ static class Config {
public FunctionCallback weatherFunctionInfo() {
return FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.description("Get the weather in location") // (2) function description
.inputType(MockWeatherService.Request.class) // (3) input type to build the JSON schema
.build();
}
@@ -226,8 +226,8 @@ class Config {
fun weatherFunctionInfo(): FunctionCallback {
return FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", MockWeatherService()) // (1) function name and instance
.description("Get the weather in location") // (2) function description
// (3) Required due to Kotlin SAM conversion being an opaque lambda
.inputType<MockWeatherService.Request>()
.build();
@@ -287,8 +287,8 @@ ChatClient chatClient = ...
ChatResponse response = this.chatClient.prompt("What's the weather like in San Francisco, Tokyo, and Paris?")
.functions(FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("currentWeather", (Request request) -> new Response(30.0, Unit.C)) // (1) function name and instance
.description("Get the weather in location") // (2) function description
.inputType(MockWeatherService.Request.class) // (3) input type to build the JSON schema
.build())
.call()
@@ -320,8 +320,8 @@ You need the `FunctionCallback.Builder` to create `MethodInvokingFunctionCallbac
----
// Create using builder pattern
FunctionCallback methodInvokingCallback = FunctionCallback.builder()
.description("Function calling description") // Hints the AI to know when to call this method
.method("MethodName", Class<?>...argumentTypes) // The method to invoke and its argument types
.description("Function calling description") // Hints the AI to know when to call this method
.targetObject(targetObject) // Required instance methods for static methods use targetClass
.build();
----
@@ -342,8 +342,8 @@ public class WeatherService {
// Usage
FunctionCallback callback = FunctionCallback.builder()
.description("Get weather information for a city")
.method("getWeather", String.class, TemperatureUnit.class)
.description("Get weather information for a city")
.targetClass(WeatherService.class)
.build();
----
@@ -364,8 +364,8 @@ DeviceController controller = new DeviceController();
String response = ChatClient.create(chatModel).prompt()
.user("Turn on the living room lights")
.functions(FunctionCallback.builder()
.description("Control device state")
.method("setDeviceState", String.class,boolean.class,ToolContext.class)
.description("Control device state")
.targetObject(controller)
.build())
.toolContext(Map.of("location", "home"))
@@ -423,8 +423,8 @@ BiFunction<MockWeatherService.Request, ToolContext, MockWeatherService.Response>
ChatResponse response = chatClient.prompt("What's the weather like in San Francisco, Tokyo, and Paris?")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", this.weatherFunction)
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.toolContext(Map.of("sessionId", "1234", "userId", "5678"))
@@ -453,8 +453,8 @@ DeviceController controller = new DeviceController();
String response = ChatClient.create(chatModel).prompt()
.user("Turn on the living room lights")
.functions(FunctionCallback.builder()
.description("Control device state")
.method("setDeviceState", String.class,boolean.class,ToolContext.class)
.description("Control device state")
.targetObject(controller)
.build())
.toolContext(Map.of("location", "home"))

View File

@@ -59,8 +59,8 @@ public class FunctionCallWithPromptFunctionIT {
var promptOptions = AnthropicChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location. Return temperature in 36°F or 36°C format.")
.function("CurrentWeatherService", new MockWeatherService())
.description("Get the weather in location. Return temperature in 36°F or 36°C format.")
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -80,8 +80,8 @@ public class FunctionCallWithFunctionWrapperIT {
public FunctionCallback weatherFunctionInfo() {
return FunctionCallback.builder()
.description("Get the current weather in a given location")
.function("WeatherInfo", new MockWeatherService())
.description("Get the current weather in a given location")
.inputType(MockWeatherService.Request.class)
.build();
}

View File

@@ -62,8 +62,8 @@ public class FunctionCallWithPromptFunctionIT {
var promptOptions = AzureOpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("CurrentWeatherService", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -58,8 +58,8 @@ public class FunctionCallWithPromptFunctionIT {
var promptOptions = FunctionCallingOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location. Return temperature in 36°F or 36°C format.")
.function("CurrentWeatherService", new MockWeatherService())
.description("Get the weather in location. Return temperature in 36°F or 36°C format.")
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -64,8 +64,8 @@ public class FunctionCallbackInPromptIT {
var promptOptions = MiniMaxChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("CurrentWeatherService", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -90,8 +90,8 @@ public class FunctionCallbackInPromptIT {
var promptOptions = MiniMaxChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("CurrentWeatherService", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -111,8 +111,8 @@ public class MiniMaxFunctionCallbackIT {
public FunctionCallback weatherFunctionInfo() {
return FunctionCallback.builder()
.description("Get the weather in location")
.function("WeatherInfo", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build();
}

View File

@@ -65,9 +65,9 @@ public class PaymentStatusPromptIT {
var promptOptions = MistralAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get payment status of a transaction")
.function("retrievePaymentStatus",
(Transaction transaction) -> new Status(DATA.get(transaction).status()))
.description("Get payment status of a transaction")
.inputType(Transaction.class)
.build()))
.build();

View File

@@ -74,8 +74,8 @@ public class WeatherServicePromptIT {
var promptOptions = MistralAiChatOptions.builder()
.withToolChoice(ToolChoice.AUTO)
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the current weather in requested location")
.function("CurrentWeatherService", new MyWeatherService())
.description("Get the current weather in requested location")
.inputType(MyWeatherService.Request.class)
.build()))
.build();
@@ -100,8 +100,8 @@ public class WeatherServicePromptIT {
PortableFunctionCallingOptions functionOptions = FunctionCallingOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the current weather in requested location")
.function("CurrentWeatherService", new MyWeatherService())
.description("Get the current weather in requested location")
.inputType(MyWeatherService.Request.class)
.build()))

View File

@@ -65,8 +65,8 @@ public class FunctionCallbackInPromptIT {
var promptOptions = MoonshotChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("CurrentWeatherService", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -91,8 +91,8 @@ public class FunctionCallbackInPromptIT {
var promptOptions = MoonshotChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("CurrentWeatherService", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -114,8 +114,8 @@ public class MoonshotFunctionCallbackIT {
public FunctionCallback weatherFunctionInfo() {
return FunctionCallback.builder()
.description("Get the weather in location")
.function("WeatherInfo", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build();
}

View File

@@ -72,9 +72,9 @@ public class FunctionCallbackInPromptIT extends BaseOllamaIT {
var promptOptions = OllamaOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.function("CurrentWeatherService", new MockWeatherService())
.description(
"Find the weather conditions, forecasts, and temperatures for a location, like a city or state.")
.function("CurrentWeatherService", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -99,9 +99,9 @@ public class FunctionCallbackInPromptIT extends BaseOllamaIT {
var promptOptions = OllamaOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.function("CurrentWeatherService", new MockWeatherService())
.description(
"Find the weather conditions, forecasts, and temperatures for a location, like a city or state.")
.function("CurrentWeatherService", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -140,9 +140,9 @@ public class OllamaFunctionCallbackIT extends BaseOllamaIT {
public FunctionCallback weatherFunctionInfo() {
return FunctionCallback.builder()
.function("WeatherInfo", new MockWeatherService())
.description(
"Find the weather conditions, forecasts, and temperatures for a location, like a city or state.")
.function("WeatherInfo", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build();
}

View File

@@ -61,8 +61,8 @@ public class FunctionCallbackInPrompt2IT {
String content = ChatClient.builder(chatModel).build().prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris?")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("CurrentWeatherService", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.call().content();
@@ -89,11 +89,11 @@ public class FunctionCallbackInPrompt2IT {
String content = ChatClient.builder(chatModel).build().prompt()
.user("Turn the light on in the kitchen and in the living room!")
.functions(FunctionCallback.builder()
.description("Turn light on or off in a room")
.function("turnLight", (LightInfo lightInfo) -> {
logger.info("Turning light to [" + lightInfo.isOn + "] in " + lightInfo.roomName());
state.put(lightInfo.roomName(), lightInfo.isOn());
})
.description("Turn light on or off in a room")
.inputType(LightInfo.class)
.build())
.call().content();
@@ -115,8 +115,8 @@ public class FunctionCallbackInPrompt2IT {
String content = ChatClient.builder(chatModel).build().prompt()
.user("What's the weather like in Amsterdam?")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("CurrentWeatherService", input -> "18 degrees Celsius")
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build())
.call().content();

View File

@@ -63,8 +63,8 @@ public class FunctionCallbackInPromptIT {
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("CurrentWeatherService", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -92,8 +92,8 @@ public class FunctionCallbackInPromptIT {
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("CurrentWeatherService", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -96,8 +96,8 @@ public class OpenAiFunctionCallback2IT {
public FunctionCallback weatherFunctionInfo() {
return FunctionCallback.builder()
.description("Get the weather in location")
.function("WeatherInfo", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build();
}

View File

@@ -107,8 +107,8 @@ public class OpenAiFunctionCallbackIT {
public FunctionCallback weatherFunctionInfo() {
return FunctionCallback.builder()
.description("Get the weather in location")
.function("WeatherInfo", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build();
}

View File

@@ -80,9 +80,9 @@ public class FunctionCallWithFunctionWrapperIT {
public FunctionCallback weatherFunctionInfo() {
return FunctionCallback.builder()
.function("WeatherInfo", new MockWeatherService())
.description("Get the current weather in a given location")
.schemaType(SchemaType.OPEN_API_SCHEMA)
.function("WeatherInfo", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build();
}

View File

@@ -69,9 +69,9 @@ public class FunctionCallWithPromptFunctionIT {
var promptOptions = VertexAiGeminiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.function("CurrentWeatherService", new MockWeatherService())
.schemaType(SchemaType.OPEN_API_SCHEMA) // IMPORTANT!!
.description("Get the weather in location")
.function("CurrentWeatherService", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -65,8 +65,8 @@ public class FunctionCallbackInPromptIT {
var promptOptions = ZhiPuAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("CurrentWeatherService", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
// .responseConverter(response -> "" + response.temp() +
// response.unit())
@@ -93,8 +93,8 @@ public class FunctionCallbackInPromptIT {
var promptOptions = ZhiPuAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("CurrentWeatherService", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -112,8 +112,8 @@ public class ZhipuAiFunctionCallbackIT {
public FunctionCallback weatherFunctionInfo() {
return FunctionCallback.builder()
.description("Get the weather in location")
.function("WeatherInfo", new MockWeatherService())
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
// .responseConverter(response -> "" + response.temp() + response.unit())
.build();

View File

@@ -106,10 +106,10 @@ class FunctionCallbackKotlinIT : BaseOllamaIT() {
@Bean
open fun weatherFunctionInfo(): FunctionCallback {
return FunctionCallback.builder()
.function("WeatherInfo", MockKotlinWeatherService())
.description(
"Find the weather conditions, forecasts, and temperatures for a location, like a city or state."
)
.function("WeatherInfo", MockKotlinWeatherService())
.inputType(KotlinRequest::class.java)
.build()
}