Revamp function callback builder API

Introduces a simplified, type-safe builder pattern for function callbacks to
improve developer experience and code reliability. The new hierarchical API
separates concerns between direct function invocation and method reflection,
while providing better compile-time safety.

This change deprecates the older FunctionCallbackWrapper in favor of a more
intuitive FunctionCallback.Builder that better handles generic types via
ParameterizedTypeReference. It also adds automatic function description
generation as a fallback when none is provided, though explicit descriptions
are still recommended.

The update standardizes function callback handling across all AI model
implementations (OpenAI, Ollama, Minimax, etc.) and improves response
handling with configurable converters.

Core API Enhancements:

- New Builder Interface: Replaced FunctionCallbackWrapper.builder() with
   FunctionCallback.builder(), introducing a hierarchical approach that improves
   customization and type safety.
- Specialized Builders: Introduced FunctionInvokerBuilder for direct Function/BiFunction
   implementations and MethodInvokerBuilder for reflection-based invocations.
- Generic Type Support: Added ParameterizedTypeReference for better handling of generic parameters.
- Unified Method Definition: Merged method() and argumentTypes() into a single method() call
   for simplicity and type safety.
- Automatic Descriptions: Implemented auto-generation of function descriptions, with warnings
   to encourage explicit descriptions.
- Configurable Response Converters: Enhanced response handling with support for custom
   converters, reducing unnecessary JSON conversions.

Architecture Improvements:

- Established common Builder interface for shared properties
- Separated function object handling from constructor
- Added method-specific configuration (name, arg types, target)
- Added JSON schema generation support for ResolvableType
- Moved to standardized schema types across AI providers
- Set OPEN_API_SCHEMA as default for Vertex AI Gemini

Builder Pattern Standardization:

- Standardized builder method ordering across implementations
- Moved function() call after description() for consistency
- Improved function callback configuration with unified patterns
- Enhanced error handling and validation in DefaultFunctionCallbackBuilder

Deprecations:

- FunctionCallbackWrapper.Builder replaced by DefaultFunctionCallbackBuilder
- Removed CustomizedTypeReference in favor of ParameterizedTypeReference
- Deprecated older ChatClient API methods for function handling

Testing & Documentation:

- Updated all AI model implementations (OpenAI, Ollama, Minimax, Moonshot, ZhiPuAI)
- Added comprehensive integration tests for static/instance methods
- Added integration tests for auto-generated descriptions
- Updated documentation to reflect new builder pattern usage
- Added Kotlin extension for inputType() support

Co-authored-by: Sébastien Deleuze <sebastien.deleuze@broadcom.com>
This commit is contained in:
Christian Tzolov
2024-11-13 12:58:52 +01:00
committed by Mark Pollack
parent 72c84fe8b8
commit fb0d99dc37
78 changed files with 1704 additions and 918 deletions

View File

@@ -108,9 +108,10 @@ var options = FunctionCallingOptions.builder()
.withModel("anthropic.claude-3-5-sonnet-20240620-v1:0")
.withTemperature(0.6)
.withMaxTokens(300)
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new WeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location. Return temperature in 36°F or 36°C format. Use multi-turn if needed.")
.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())
.inputType(WeatherService.Request.class)
.build()))
.build();

View File

@@ -18,7 +18,7 @@ Your function can in turn invoke other 3rd party services to provide the results
Spring AI makes this as easy as defining a `@Bean` definition that returns a `java.util.Function` and supplying the bean name as an option when invoking the `ChatModel`.
Under the hood, Spring wraps your POJO (the function) with the appropriate adapter code that enables interaction with the AI Model, saving you from writing tedious boilerplate code.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java[FunctionCallbackWrapper.java] utility class to simplify the implementation and registration of Java callback functions.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion Builder utility class to simplify the implementation and registration of Java callback functions.
== How it works
@@ -70,7 +70,7 @@ We start with describing the most POJO friendly options.
In this approach you define `@Beans` in your application context as you would any other Spring managed object.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallbackWrapper` wrapper that adds the logic for it being invoked via the AI model.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallback` that adds the logic for it being invoked via the AI model.
The name of the `@Bean` is passed as a `ChatOption`.
@@ -115,9 +115,9 @@ It is a best practice to annotate the request object with information such that
The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/anthropic/tool/FunctionCallWithFunctionBeanIT.java.java[FunctionCallWithFunctionBeanIT.java] demonstrates this approach.
==== FunctionCallback Wrapper
==== FunctionCallback
Another way to register a function is to create a `FunctionCallbackWrapper` wrapper like this:
Another way to register a function is to create a `FunctionCallback` instance like this:
[source,java]
----
@@ -127,9 +127,10 @@ static class Config {
@Bean
public FunctionCallback weatherFunctionInfo() {
return FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("CurrentWeather") // (1) function name
.withDescription("Get the weather in location") // (2) function description
return FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.inputType(MockWeatherService.Request.class) // (3) function signature
.build();
}
...
@@ -137,11 +138,11 @@ static class Config {
----
It wraps the 3rd party `MockWeatherService` function and registers it as a `CurrentWeather` function with the `AnthropicChatModel`.
It also provides a description (2) and an optional response converter (3) to convert the response into a text as expected by the model.
It also provides a description (2) and input type (3) used to generate the JSON schema for the function call.
NOTE: By default, the response converter does a JSON serialization of the Response object.
NOTE: The `FunctionCallbackWrapper` internally resolves the function call signature based on the `MockWeatherService.Request` class.
NOTE: The `FunctionCallback` internally resolves the function call signature based on the `MockWeatherService.Request` class.
=== Specifying functions in Chat Options
@@ -174,10 +175,11 @@ AnthropicChatModel chatModel = ...
UserMessage userMessage = new UserMessage("What's the weather like in Paris?");
var promptOptions = AnthropicChatOptions.builder()
.withFunctionCallbacks(List.of(new FunctionCallbackWrapper<>(
"CurrentWeather", // name
"Get the weather in location", // function description
new MockWeatherService()))) // function code
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.inputType(MockWeatherService.Request.class) // (3) function signature
.build())) // function code
.build();
ChatResponse response = this.chatModel.call(new Prompt(List.of(this.userMessage), this.promptOptions));

View File

@@ -17,7 +17,7 @@ Your function can in turn invoke other 3rd party services to provide the results
Spring AI makes this as easy as defining a `@Bean` definition that returns a `java.util.Function` and supplying the bean name as an option when invoking the `ChatModel`.
Under the hood, Spring wraps your POJO (the function) with the appropriate adapter code that enables interaction with the AI Model, saving you from writing tedious boilerplate code.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java[FunctionCallbackWrapper.java] utility class to simplify the implementation and registration of Java callback functions.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion Builder utility class to simplify the implementation and registration of Java callback functions.
== How it works
@@ -68,7 +68,7 @@ We start with describing the most POJO friendly options.
In this approach you define `@Beans` in your application context as you would any other Spring managed object.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallbackWrapper` wrapper that adds the logic for it being invoked via the AI model.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallback` instance that adds the logic for it being invoked via the AI model.
The name of the `@Bean` is passed as a `ChatOption`.
@@ -113,7 +113,7 @@ The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring
==== FunctionCallback Wrapper
Another way to register a function is to create a `FunctionCallbackWrapper` wrapper like this:
Another way to register a function is to create a `FunctionCallback` instance like this:
[source,java]
----
@@ -123,9 +123,10 @@ static class Config {
@Bean
public FunctionCallback weatherFunctionInfo() {
return FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("CurrentWeather") // (1) function name
.withDescription("Get the current weather in a given location") // (2) function description
return FunctionCallback.builder()
.description("Get the current weather in a given location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name
.inputType(MockWeatherService.Request.class) // (3) function input type
.build();
}
...
@@ -136,7 +137,7 @@ It wraps the 3rd party `MockWeatherService` function and registers it as a `Curr
NOTE: The default response converter does a JSON serialization of the Response object.
NOTE: The `FunctionCallbackWrapper` internally resolves the function call signature based on the `MockWeatherService.Request` class and internally generates an JSON schema for the function call.
NOTE: The `FunctionCallback` internally resolves the function call signature based on the `MockWeatherService.Request` class and internally generates an JSON schema for the function call.
=== Specifying functions in Chat Options
@@ -179,10 +180,11 @@ AzureOpenAiChatModel chatModel = ...
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris? Use Multi-turn function calling.");
var promptOptions = AzureOpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("CurrentWeather")
.withDescription("Get the weather in location")
.build()))
.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
.inputType(MockWeatherService.Request.class) // (3) function input type
.build()))
.build();
ChatResponse response = this.chatModel.call(new Prompt(List.of(this.userMessage), this.promptOptions));

View File

@@ -14,7 +14,7 @@ As a developer, you need to implement a function that takes the function call ar
Spring AI makes this as easy as defining a `@Bean` definition that returns a `java.util.Function` and supplying the bean name as an option when invoking the `ChatModel`.
Under the hood, Spring wraps your POJO (the function) with the appropriate adapter code that enables interaction with the AI Model, saving you from writing tedious boilerplate code.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java[FunctionCallbackWrapper.java] utility class to simplify the implementation and registration of Java callback functions.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion Builder utility class to simplify the implementation and registration of Java callback functions.
// Additionally, the Auto-Configuration provides a way to auto-register any Function<I, O> beans definition as function calling candidates in the `ChatModel`.
@@ -71,7 +71,7 @@ We start with describing the most POJO friendly options.
In this approach you define `@Beans` in your application context as you would any other Spring managed object.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallbackWrapper` wrapper that adds the logic for it being invoked via the AI model.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallback` instance that adds the logic for it being invoked via the AI model.
The name of the `@Bean` is passed as a `ChatOption`.
@@ -117,7 +117,7 @@ The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring
==== FunctionCallback Wrapper
Another way register a function is to create `FunctionCallbackWrapper` wrapper like this:
Another way register a function is to create `FunctionCallback` instance like this:
[source,java]
----
@@ -127,9 +127,10 @@ static class Config {
@Bean
public FunctionCallback weatherFunctionInfo() {
return FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("CurrentWeather") // (1) function name
.withDescription("Get the weather in location") // (2) function description
return FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.inputType(MockWeatherService.Request.class) // (3) function signature
.build();
}
...
@@ -137,11 +138,11 @@ static class Config {
----
It wraps the 3rd party, `MockWeatherService` function and registers it as a `CurrentWeather` function with the `MiniMaxChatModel`.
It also provides a description (2) and an optional response converter (3) to convert the response into a text as expected by the model.
It also provides a description (2) and the function signature (3) to let the model know what arguments the function expects.
NOTE: By default, the response converter does a JSON serialization of the Response object.
NOTE: The `FunctionCallbackWrapper` internally resolves the function call signature based on the `MockWeatherService.Request` class.
NOTE: The `FunctionCallback` internally resolves the function call signature based on the `MockWeatherService.Request` class.
=== Specifying functions in Chat Options
@@ -170,7 +171,7 @@ Here is the current weather for the requested cities:
- Paris, France: 15.0°C
----
The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/minimax/tool/FunctionCallbackWrapperIT.java[FunctionCallbackWrapperIT.java] test demo this approach.
The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/minimax/tool/MiniMaxFunctionCallbackIT.java[MiniMaxFunctionCallbackIT.java] test demo this approach.
=== Register/Call Functions with Prompt Options
@@ -184,10 +185,11 @@ MiniMaxChatModel chatModel = ...
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
var promptOptions = MiniMaxChatOptions.builder()
.withFunctionCallbacks(List.of(new FunctionCallbackWrapper<>(
"CurrentWeather", // name
"Get the weather in location", // function description
new MockWeatherService()))) // function code
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.inputType(MockWeatherService.Request.class) // (3) function signature
.build())) // function code
.build();
ChatResponse response = this.chatModel.call(new Prompt(List.of(this.userMessage), this.promptOptions));
@@ -198,29 +200,3 @@ NOTE: The in-prompt registered functions are enabled by default for the duration
This approach allows to dynamically chose different functions to be called based on the user input.
The https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/minimax/FunctionCallbackInPromptIT.java[FunctionCallbackInPromptIT.java] integration test provides a complete example of how to register a function with the `MiniMaxChatModel` and use it in a prompt request.
//
// === Register Functions with Default Options
//
// You can programmatically register functions with the `MiniMaxChatModel` using the `MiniMaxChatOptions#withFunctionCallbacks`:
//
// [source,java]
// ----
//
// MiniMaxApi miniMaxApi = new MiniMaxApi(apiKey);
//
// var defaultOptions = MiniMaxChatOptions.builder()
// .withFunctionCallbacks(List.of(new FunctionCallbackWrapper<>(
// "CurrentWeather", // name
// "Get the weather in location", // function description
// new MockWeatherService()))) // function code
// .build();
//
// MiniMaxChatModel chatModel = new MiniMaxChatModel(miniMaxApi, defaultOptions);
//
// UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
//
// ChatResponse response = chatModel.call(new Prompt(List.of(userMessage),
// MiniMaxChatOptions.builder().withFunction("CurrentWeather").build())); // Enable the function
// ----
//
// NOTE: Functions are registered when MiniMaxChatModel is created, by you must enable in the Prompt the functions to be used in the request.

View File

@@ -16,7 +16,7 @@ Your function can in turn invoke other 3rd party services to provide the results
Spring AI makes this as easy as defining a `@Bean` definition that returns a `java.util.Function` and supplying the bean name as an option when invoking the `ChatModel`.
Under the hood, Spring wraps your POJO (the function) with the appropriate adapter code that enables interaction with the AI Model, saving you from writing tedious boilerplate code.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java[FunctionCallbackWrapper.java] utility class to simplify the implementation and registration of Java callback functions.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion Builder utility class to simplify the implementation and registration of Java callback functions.
== How it works
@@ -68,7 +68,7 @@ We start by describing the most POJO-friendly options.
In this approach, you define a `@Bean` in your application context as you would any other Spring managed object.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallbackWrapper` that adds the logic for it being invoked via the AI model.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallback` that adds the logic for it being invoked via the AI model.
The name of the `@Bean` is passed as a `ChatOption`.
[source,java]
@@ -115,7 +115,7 @@ Mistral AI is almost identical to OpenAI in this regard.
==== FunctionCallback Wrapper
Another way to register a function is to create a `FunctionCallbackWrapper` like this:
Another way to register a function is to create a `FunctionCallback` like this:
[source,java]
----
@@ -125,9 +125,10 @@ static class Config {
@Bean
public FunctionCallback weatherFunctionInfo() {
return FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("CurrentWeather") // (1) function name
.withDescription("Get the weather in location") // (2) function description
return FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.inputType(MockWeatherService.Request.class) // (3) function signature
.build();
}
@@ -135,11 +136,11 @@ static class Config {
----
It wraps the 3rd party `MockWeatherService` function and registers it as a `CurrentWeather` function with the `MistralAiChatModel`.
It also provides a description (2) and an optional response converter to convert the response into a text as expected by the model.
It also provides a description (2) and the function signature (3) to let the model know what arguments the function expects.
NOTE: By default, the response converter performs a JSON serialization of the Response object.
NOTE: The `FunctionCallbackWrapper` internally resolves the function call signature based on the `MockWeatherService.Request` class.
NOTE: The `FunctionCallback` internally resolves the function call signature based on the `MockWeatherService.Request` class.
=== Specifying functions in Chat Options
@@ -172,10 +173,11 @@ MistralAiChatModel chatModel = ...
UserMessage userMessage = new UserMessage("What's the weather like in Paris?");
var promptOptions = MistralAiChatOptions.builder()
.withFunctionCallbacks(List.of(new FunctionCallbackWrapper<>(
"CurrentWeather", // name
"Get the weather in location", // function description
new MockWeatherService()))) // function code
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.inputType(MockWeatherService.Request.class) // (3) function signature
.build())) // function code
.build();
ChatResponse response = this.chatModel.call(new Prompt(this.userMessage, this.promptOptions));

View File

@@ -14,7 +14,7 @@ As a developer, you need to implement a function that takes the function call ar
Spring AI makes this as easy as defining a `@Bean` definition that returns a `java.util.Function` and supplying the bean name as an option when invoking the `ChatModel`.
Under the hood, Spring wraps your POJO (the function) with the appropriate adapter code that enables interaction with the AI Model, saving you from writing tedious boilerplate code.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java[FunctionCallbackWrapper.java] utility class to simplify the implementation and registration of Java callback functions.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion Builder utility class to simplify the implementation and registration of Java callback functions.
// Additionally, the Auto-Configuration provides a way to auto-register any Function<I, O> beans definition as function calling candidates in the `ChatModel`.
@@ -71,7 +71,7 @@ We start with describing the most POJO friendly options.
In this approach you define `@Beans` in your application context as you would any other Spring managed object.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallbackWrapper` wrapper that adds the logic for it being invoked via the AI model.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallback` instance that adds the logic for it being invoked via the AI model.
The name of the `@Bean` is passed as a `ChatOption`.
@@ -117,7 +117,7 @@ The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring
==== FunctionCallback Wrapper
Another way register a function is to create `FunctionCallbackWrapper` wrapper like this:
Another way register a function is to create `FunctionCallback` instance like this:
[source,java]
----
@@ -127,9 +127,10 @@ static class Config {
@Bean
public FunctionCallback weatherFunctionInfo() {
return FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("CurrentWeather") // (1) function name
.withDescription("Get the weather in location") // (2) function description
return FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.inputType(MockWeatherService.Request.class) // (3) function signature
.build();
}
...
@@ -137,11 +138,11 @@ static class Config {
----
It wraps the 3rd party, `MockWeatherService` function and registers it as a `CurrentWeather` function with the `MoonshotChatModel`.
It also provides a description (2) and an optional response converter (3) to convert the response into a text as expected by the model.
It also provides a description (2) and the function signature (3) to let the model know what arguments the function expects.
NOTE: By default, the response converter does a JSON serialization of the Response object.
NOTE: The `FunctionCallbackWrapper` internally resolves the function call signature based on the `MockWeatherService.Request` class.
NOTE: The `FunctionCallback` internally resolves the function call signature based on the `MockWeatherService.Request` class.
=== Specifying functions in Chat Options
@@ -170,7 +171,7 @@ Here is the current weather for the requested cities:
- Paris, France: 15.0°C
----
The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/moonshot/tool/FunctionCallbackWrapperIT.java[FunctionCallbackWrapperIT.java] test demo this approach.
The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/moonshot/tool/MoonshotFunctionCallbackIT.java[MoonshotFunctionCallbackIT.java] test demo this approach.
=== Register/Call Functions with Prompt Options
@@ -184,10 +185,11 @@ MoonshotChatModel chatModel = ...
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
var promptOptions = MoonshotChatOptions.builder()
.withFunctionCallbacks(List.of(new FunctionCallbackWrapper<>(
"CurrentWeather", // name
"Get the weather in location", // function description
new MockWeatherService()))) // function code
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name
.inputType(MockWeatherService.Request.class) // (3) function signature
.build())) // function code
.build();
ChatResponse response = this.chatModel.call(new Prompt(List.of(this.userMessage), this.promptOptions));
@@ -198,29 +200,3 @@ NOTE: The in-prompt registered functions are enabled by default for the duration
This approach allows to dynamically chose different functions to be called based on the user input.
The https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/moonshot/tool/FunctionCallbackInPromptIT.java[FunctionCallbackInPromptIT.java] integration test provides a complete example of how to register a function with the `MoonshotChatModel` and use it in a prompt request.
//
// === Register Functions with Default Options
//
// You can programmatically register functions with the `MoonshotChatModel` using the `MoonshotChatOptions#withFunctionCallbacks`:
//
// [source,java]
// ----
//
// MoonshotApi moonshotApi = new MoonshotApi(apiKey);
//
// var defaultOptions = MoonshotChatOptions.builder()
// .withFunctionCallbacks(List.of(new FunctionCallbackWrapper<>(
// "CurrentWeather", // name
// "Get the weather in location", // function description
// new MockWeatherService()))) // function code
// .build();
//
// MoonshotChatModel chatModel = new MoonshotChatModel(moonshotApi, defaultOptions);
//
// UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
//
// ChatResponse response = chatModel.call(new Prompt(List.of(userMessage),
// MoonshotChatOptions.builder().withFunction("CurrentWeather").build())); // Enable the function
// ----
//
// NOTE: Functions are registered when MoonshotChatModel is created, by you must enable in the Prompt the functions to be used in the request.

View File

@@ -23,7 +23,7 @@ Your function can in turn invoke other 3rd party services to provide the results
Spring AI makes this as easy as defining a `@Bean` definition that returns a `java.util.Function` and supplying the bean name as an option when invoking the `ChatModel`.
Under the hood, Spring wraps your POJO (the function) with the appropriate adapter code that enables interaction with the AI Model, saving you from writing tedious boilerplate code.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java[FunctionCallbackWrapper.java] utility class to simplify the implementation and registration of Java callback functions.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion Builder utility class to simplify the implementation and registration of Java callback functions.
== How it works
@@ -78,7 +78,7 @@ We start by describing the most POJO-friendly options.
In this approach, you define a `@Bean` in your application context as you would any other Spring managed object.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallbackWrapper` that adds the logic for it being invoked via the AI model.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallback` that adds the logic for it being invoked via the AI model.
The name of the `@Bean` is passed as a `ChatOption`.
[source,java]
@@ -117,9 +117,9 @@ public record Request(String location, Unit unit) {}
It is a best practice to annotate the request object with information such that the generated JSON schema of that function is as descriptive as possible to help the AI model pick the correct function to invoke.
==== FunctionCallbackWrapper
==== FunctionCallback
Another way to register a function is to create a `FunctionCallbackWrapper` like this:
Another way to register a function is to create a `FunctionCallback` like this:
[source,java]
----
@@ -129,9 +129,10 @@ static class Config {
@Bean
public FunctionCallback weatherFunctionInfo() {
return FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("CurrentWeather") // (1) function name
.withDescription("Get the weather in location") // (2) function description
return FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name
.inputType(MockWeatherService.Request.class) // (3) function signature
.build();
}
@@ -139,11 +140,11 @@ static class Config {
----
It wraps the 3rd party `MockWeatherService` function and registers it as a `CurrentWeather` function with the `OllamaChatModel`.
It also provides a description (2) and an optional response converter to convert the response into a text as expected by the model.
It also provides a description (2) and the function signature (3) to let the model know what arguments the function expects.
NOTE: By default, the response converter performs a JSON serialization of the Response object.
NOTE: The `FunctionCallbackWrapper` internally resolves the function call signature based on the `MockWeatherService.Request` class.
NOTE: The `FunctionCallback` internally resolves the function call signature based on the `MockWeatherService.Request` class.
=== Specifying functions in Chat Options
@@ -172,7 +173,7 @@ Here is the current weather for the requested cities:
- Paris, France: 15.0°C
----
The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/ollama/tool/FunctionCallbackWrapperIT.java[FunctionCallbackWrapperIT.java] test demo this approach.
The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/ollama/tool/OllamaFunctionCallbackIT.java[OllamaFunctionCallbackIT.java] test demo this approach.
=== Register/Call Functions with Prompt Options
@@ -185,10 +186,11 @@ OllamaChatModel chatModel = ...
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
var promptOptions = OllamaOptions.builder()
.withFunctionCallbacks(List.of(new FunctionCallbackWrapper<>(
"CurrentWeather", // name
"Get the weather in location", // function description
new MockWeatherService()))) // function code
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.inputType(MockWeatherService.Request.class) // (3) function signature
.build())) // function code
.build();
ChatResponse response = this.chatModel.call(new Prompt(this.userMessage, this.promptOptions));

View File

@@ -14,7 +14,7 @@ As a developer, you need to implement a function that takes the function call ar
Spring AI makes this as easy as defining a `@Bean` definition that returns a `java.util.Function` and supplying the bean name as an option when invoking the `ChatModel`.
Under the hood, Spring wraps your POJO (the function) with the appropriate adapter code that enables interaction with the AI Model, saving you from writing tedious boilerplate code.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java[FunctionCallbackWrapper.java] utility class to simplify the implementation and registration of Java callback functions.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion Builder utility class to simplify the implementation and registration of Java callback functions.
// Additionally, the Auto-Configuration provides a way to auto-register any Function<I, O> beans definition as function calling candidates in the `ChatModel`.
@@ -69,7 +69,7 @@ We start by describing the most POJO-friendly options.
In this approach, you define a `@Bean` in your application context as you would any other Spring managed object.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallbackWrapper` that adds the logic for it being invoked via the AI model.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallback` that adds the logic for it being invoked via the AI model.
The name of the `@Bean` is passed as a `ChatOption`.
[source,java]
@@ -112,7 +112,7 @@ The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring
==== FunctionCallback Wrapper
Another way to register a function is to create a `FunctionCallbackWrapper` like this:
Another way to register a function is to create a `FunctionCallback` like this:
[source,java]
----
@@ -122,9 +122,10 @@ static class Config {
@Bean
public FunctionCallback weatherFunctionInfo() {
return FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("CurrentWeather") // (1) function name
.withDescription("Get the weather in location") // (2) function description
return FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.inputType(MockWeatherService.Request.class) // (3) function input type
.build();
}
@@ -132,11 +133,11 @@ static class Config {
----
It wraps the 3rd party `MockWeatherService` function and registers it as a `CurrentWeather` function with the `OpenAiChatModel`.
It also provides a description (2) and an optional response converter to convert the response into a text as expected by the model.
It also provides a description (2) and an input type (3) used to generate the JSON schema for the function call.
NOTE: By default, the response converter performs a JSON serialization of the Response object.
NOTE: The `FunctionCallbackWrapper` internally resolves the function call signature based on the `MockWeatherService.Request` class.
NOTE: The `FunctionCallback` internally resolves the function call signature based on the `MockWeatherService.Request` class.
=== Specifying functions in Chat Options
@@ -165,7 +166,7 @@ Here is the current weather for the requested cities:
- Paris, France: 15.0°C
----
The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/tool/FunctionCallbackWrapperIT.java[FunctionCallbackWrapperIT.java] test demo this approach.
The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/tool/OpenAiFunctionCallbackIT.java[OpenAiFunctionCallbackIT.java] test demo this approach.
=== Register/Call Functions with Prompt Options
@@ -178,10 +179,11 @@ OpenAiChatModel chatModel = ...
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(new FunctionCallbackWrapper<>(
"CurrentWeather", // name
"Get the weather in location", // function description
new MockWeatherService()))) // function code
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.inputType(MockWeatherService.Request.class) // (3) function input type
.build())) // function code
.build();
ChatResponse response = this.chatModel.call(new Prompt(this.userMessage, this.promptOptions));
@@ -192,32 +194,6 @@ NOTE: The in-prompt registered functions are enabled by default for the duration
This approach allows to choose dynamically different functions to be called based on the user input.
The https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/tool/FunctionCallbackInPromptIT.java[FunctionCallbackInPromptIT.java] integration test provides a complete example of how to register a function with the `OpenAiChatModel` and use it in a prompt request.
//
// === Register Functions with Default Options
//
// You can programmatically register functions with the `OpenAiChatModel` using the `OpenAiChatOptions#withFunctionCallbacks`:
//
// [source,java]
// ----
//
// OpenAiApi openaiApi = new OpenAiApi(apiKey);
//
// var defaultOptions = OpenAiChatOptions.builder()
// .withFunctionCallbacks(List.of(new FunctionCallbackWrapper<>(
// "CurrentWeather", // name
// "Get the weather in location", // function description
// new MockWeatherService()))) // function code
// .build();
//
// OpenAiChatModel chatModel = new OpenAiChatModel(openaiApi, defaultOptions);
//
// UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
//
// ChatResponse response = chatModel.call(new Prompt(List.of(userMessage),
// OpenAiChatOptions.builder().withFunction("CurrentWeather").build())); // Enable the function
// ----
//
// NOTE: Functions are registered when OpenAiChatModel is created, by you must enable in the Prompt the functions to be used in the request.
=== Tool Context Support
@@ -254,9 +230,10 @@ BiFunction<MockWeatherService.Request, ToolContext, MockWeatherService.Response>
OpenAiChatOptions options = OpenAiChatOptions.builder()
.withModel(OpenAiApi.ChatModel.GPT_4_O.getValue())
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(this.weatherFunction)
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.function("getCurrentWeather", this.weatherFunction)
.description("Get the weather in location")
.inputType(MockWeatherService.Request.class)
.build()))
.withToolContext(Map.of("sessionId", "123", "userId", "user456"))
.build();

View File

@@ -21,7 +21,7 @@ Your function can in turn invoke other 3rd party services to provide the results
Spring AI makes this as easy as defining a `@Bean` definition that returns a `java.util.Function` and supplying the bean name as an option when invoking the `ChatModel`.
Under the hood, Spring wraps your POJO (the function) with the appropriate adapter code that enables interaction with the AI Model, saving you from writing tedious boilerplate code.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java[FunctionCallbackWrapper.java] utility class to simplify the implementation and registration of Java callback functions.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion Builder utility class to simplify the implementation and registration of Java callback functions.
// Additionally, the Auto-Configuration provides a way to auto-register any Function<I, O> beans definition as function calling candidates in the `ChatModel`.
@@ -74,7 +74,7 @@ We start with describing the most POJO friendly options.
In this approach you define `@Beans` in your application context as you would any other Spring managed object.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallbackWrapper` wrapper that adds the logic for it being invoked via the AI model.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallback` instance that adds the logic for it being invoked via the AI model.
The name of the `@Bean` is passed as a `ChatOption`.
@@ -119,7 +119,7 @@ The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring
==== FunctionCallback Wrapper
Another way to register a function is to create a `FunctionCallbackWrapper` wrapper like this:
Another way to register a function is to create a `FunctionCallback` instance like this:
[source,java]
----
@@ -129,10 +129,11 @@ static class Config {
@Bean
public FunctionCallback weatherFunctionInfo() {
return FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("CurrentWeather") // (1) function name
.withDescription("Get the current weather in a given location") // (2) function description
.withSchemaType(SchemaType.OPEN_API_SCHEMA) // (3) schema type. Compulsory for Gemini function calling.
return FunctionCallback.builder()
.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();
}
...
@@ -140,11 +141,11 @@ static class Config {
----
It wraps the 3rd party `MockWeatherService` function and registers it as a `CurrentWeather` function with the `VertexAiGeminiChatModel`.
It also provides a description (2) and sets the Schema type to Open API type (3).
It also provides a description (2), the Schema type to Open API type (3) and input type (4) used to generate the Open API schema for the function call.
NOTE: The default response converter does a JSON serialization of the Response object.
NOTE: The `FunctionCallbackWrapper` internally resolves the function call signature based on the `MockWeatherService.Request` class and internally generates an Open API schema for the function call.
NOTE: The `FunctionCallback` internally resolves the function call signature based on the `MockWeatherService.Request` class and internally generates an Open API schema for the function call.
=== Specifying functions in Chat Options
@@ -187,10 +188,11 @@ VertexAiGeminiChatModel chatModel = ...
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris? Use Multi-turn function calling.");
var promptOptions = VertexAiGeminiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("CurrentWeather")
.withSchemaType(SchemaType.OPEN_API_SCHEMA) // IMPORTANT!!
.withDescription("Get the weather in location")
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.schemaType(SchemaType.OPEN_API_SCHEMA) // IMPORTANT!!
.description("Get the weather in location")
.function("CurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -14,7 +14,7 @@ As a developer, you need to implement a function that takes the function call ar
Spring AI makes this as easy as defining a `@Bean` definition that returns a `java.util.Function` and supplying the bean name as an option when invoking the `ChatModel`.
Under the hood, Spring wraps your POJO (the function) with the appropriate adapter code that enables interaction with the AI Model, saving you from writing tedious boilerplate code.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java[FunctionCallbackWrapper.java] utility class to simplify the implementation and registration of Java callback functions.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion Builder utility class to simplify the implementation and registration of Java callback functions.
// Additionally, the Auto-Configuration provides a way to auto-register any Function<I, O> beans definition as function calling candidates in the `ChatModel`.
@@ -71,7 +71,7 @@ We start with describing the most POJO friendly options.
In this approach you define `@Beans` in your application context as you would any other Spring managed object.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallbackWrapper` wrapper that adds the logic for it being invoked via the AI model.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallback` instance that adds the logic for it being invoked via the AI model.
The name of the `@Bean` is passed as a `ChatOption`.
@@ -117,7 +117,7 @@ The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring
==== FunctionCallback Wrapper
Another way register a function is to create `FunctionCallbackWrapper` wrapper like this:
Another way register a function is to create `FunctionCallback` instance like this:
[source,java]
----
@@ -127,9 +127,10 @@ static class Config {
@Bean
public FunctionCallback weatherFunctionInfo() {
return FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("CurrentWeather") // (1) function name
.withDescription("Get the weather in location") // (2) function description
return FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.inputType(MockWeatherService.Request.class) // (3) function signature
.build();
}
...
@@ -137,11 +138,11 @@ static class Config {
----
It wraps the 3rd party, `MockWeatherService` function and registers it as a `CurrentWeather` function with the `ZhiPuAiChatModel`.
It also provides a description (2) and an optional response converter (3) to convert the response into a text as expected by the model.
It also provides a description (2) and the input type (3) used to generate the JSON schema for the function call.
NOTE: By default, the response converter does a JSON serialization of the Response object.
NOTE: The `FunctionCallbackWrapper` internally resolves the function call signature based on the `MockWeatherService.Request` class.
NOTE: The `FunctionCallback` internally resolves the function call signature based on the `MockWeatherService.Request` class.
=== Specifying functions in Chat Options
@@ -170,7 +171,7 @@ Here is the current weather for the requested cities:
- Paris, France: 15.0°C
----
The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/zhipuai/tool/FunctionCallbackWrapperIT.java[FunctionCallbackWrapperIT.java] test demo this approach.
The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/zhipuai/tool/ZhipuAiFunctionCallbackIT.java[ZhipuAiFunctionCallbackIT.java] test demo this approach.
=== Register/Call Functions with Prompt Options
@@ -184,10 +185,11 @@ ZhiPuAiChatModel chatModel = ...
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
var promptOptions = ZhiPuAiChatOptions.builder()
.withFunctionCallbacks(List.of(new FunctionCallbackWrapper<>(
"CurrentWeather", // name
"Get the weather in location", // function description
new MockWeatherService()))) // function code
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.inputType(MockWeatherService.Request.class) // (3) function signature
.build())) // function code
.build();
ChatResponse response = this.chatModel.call(new Prompt(List.of(this.userMessage), this.promptOptions));
@@ -198,29 +200,3 @@ NOTE: The in-prompt registered functions are enabled by default for the duration
This approach allows to dynamically chose different functions to be called based on the user input.
The https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/zhipuai/tool/FunctionCallbackInPromptIT.java[FunctionCallbackInPromptIT.java] integration test provides a complete example of how to register a function with the `ZhiPuAiChatModel` and use it in a prompt request.
//
// === Register Functions with Default Options
//
// You can programmatically register functions with the `ZhiPuAiChatModel using the `ZhiPuAiChatOptions#withFunctionCallbacks`:
//
// [source,java]
// ----
//
// ZhiPuAiApi zhiPuAiApi = new ZhiPuAiApi(apiKey);
//
// var defaultOptions = ZhiPuAiChatOptions.builder()
// .withFunctionCallbacks(List.of(new FunctionCallbackWrapper<>(
// "CurrentWeather", // name
// "Get the weather in location", // function description
// new MockWeatherService()))) // function code
// .build();
//
// ZhiPuAiChatModel chatModel = new ZhiPuAiChatModel(zhiPuAiApi, defaultOptions);
//
// UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
//
// ChatResponse response = chatModel.call(new Prompt(List.of(userMessage),
// ZhiPuAiChatOptions.builder().withFunction("CurrentWeather").build())); // Enable the function
// ----
//
// NOTE: Functions are registered when ZhiPuAiChatModel is created, by you must enable in the Prompt the functions to be used in the request.

View File

@@ -31,7 +31,7 @@ As a developer, you need to implement a function that takes the function call ar
Spring AI makes this as easy as defining a `@Bean` definition that returns a `java.util.Function` and supplying the bean name as an option when invoking the `ChatClient`.
Under the hood, Spring wraps your POJO (the function) with the appropriate adapter code that enables interaction with the AI Model, saving you from writing tedious boilerplate code.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java[FunctionCallbackWrapper.java] utility class to simplify the implementation and registration of Java callback functions.
The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion Builder utility class to simplify the implementation and registration of Java callback functions.
== How it works
@@ -101,7 +101,7 @@ We start by describing the most POJO-friendly options.
In this approach, you define a `@Bean` in your application context as you would any other Spring managed object.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallbackWrapper` that adds the logic for it being invoked via the AI model.
Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallback` that adds the logic for it being invoked via the AI model.
The name of the `@Bean` is used function name.
--
@@ -182,9 +182,9 @@ data class Request(val location: String, val unit: Unit)
It is a best practice to annotate the request object with information such that the generated JSON schema of that function is as descriptive as possible to help the AI model pick the correct function to invoke.
==== FunctionCallback Wrapper
==== FunctionCallback
Another way to register a function is to create a `FunctionCallbackWrapper` like this:
Another way to register a function is to create a `FunctionCallback` like this:
--
[tabs]
@@ -199,9 +199,10 @@ static class Config {
@Bean
public FunctionCallback weatherFunctionInfo() {
return FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("CurrentWeather") // (1) function name
.withDescription("Get the weather in location") // (2) function description
return FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.inputType(MockWeatherService.Request.class) // (3) input type to build the JSON schema
.build();
}
}
@@ -218,11 +219,11 @@ class Config {
@Bean
fun weatherFunctionInfo(): FunctionCallback {
return FunctionCallbackWrapper.builder(MockWeatherService())
.withName("CurrentWeather") // (1) function name
.withDescription("Get the weather in location") // (2) function description
// (3) Required due to Kotlin SAM conversion beeing an opaque lambda
.withInputType<MockWeatherService.Request>()
return FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", MockWeatherService()) // (1) function name and instance
// (3) Required due to Kotlin SAM conversion being an opaque lambda
.inputType<MockWeatherService.Request>()
.build();
}
}
@@ -236,7 +237,7 @@ It also provides a description (2) and an optional response converter to convert
NOTE: By default, the response converter performs a JSON serialization of the Response object.
NOTE: The `FunctionCallbackWrapper` internally resolves the function call signature based on the `MockWeatherService.Request` class.
NOTE: The `FunctionCallback.Builder` internally resolves the function call signature based on the `MockWeatherService.Request` class.
=== Enable functions by bean name
@@ -274,10 +275,11 @@ In addition to the auto-configuration, you can register callback functions, dyna
ChatClient chatClient = ...
ChatResponse response = this.chatClient.prompt("What's the weather like in San Francisco, Tokyo, and Paris?")
.functions(new FunctionCallbackWrapper<>(
"CurrentWeather", // name
"Get the weather in location", // function description
new MockWeatherService()))
.functions(FunctionCallback.builder()
.description("Get the weather in location") // (2) function description
.function("CurrentWeather", new MockWeatherService()) // (1) function name and instance
.inputType(MockWeatherService.Request.class) // (3) input type to build the JSON schema
.build())
.call()
.chatResponse();
----
@@ -288,7 +290,7 @@ This approach allows to choose dynamically different functions to be called base
The https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/tool/FunctionCallbackInPromptIT.java[FunctionCallbackInPromptIT.java] integration test provides a complete example of how to register a function with the `ChatClient` and use it in a prompt request.
=== Register functions: MethodFunctionCallback
=== Register functions: Method Invoking FunctionCallback
The `MethodFunctionCallback` enables method invocation through reflection while automatically handling JSON schema generation and parameter conversion.
It's particularly useful for integrating Java methods as callable functions within AI model interactions.
@@ -301,16 +303,16 @@ The `MethodFunctionCallback` implements the `FunctionCallback` interface and pro
- Any parameter/return types (primitives, objects, collections)
- Special handling for `ToolContext` parameters
The basic MethodFunctionCallback configuration looks like this:
You need the `FunctionCallback.Builder` to create `MethodFunctionCallback` like this:
[source,java]
----
// Create using builder pattern
MethodFunctionCallback callback = MethodFunctionCallback.builder()
.functionObject(targetObject) // Required for instance methods
.method(method) // Required: The method to invoke
FunctionCallback callback = FunctionCallback.builder()
.description("Method description") // Required: Helps AI understand the function
.mapper(objectMapper) // Optional: Custom ObjectMapper
.objectMapper(objectMapper) // Optional: Custom ObjectMapper
.method("MethodName", Class<?>...argumentTypes) // Required: The method to invoke and its argument types
.targetObject(targetObject) // Required only for instance methods
.build();
----
@@ -329,12 +331,10 @@ public class WeatherService {
}
// Usage
Method method = ReflectionUtils.findMethod(
WeatherService.class, "getWeather", String.class, TemperatureUnit.class);
MethodFunctionCallback callback = MethodFunctionCallback.builder()
.method(method)
FunctionCallback callback = FunctionCallback.builder()
.description("Get weather information for a city")
.method("getWeather", String.class, TemperatureUnit.class)
.targetClass(WeatherService.class)
.build();
----
Instance Method with ToolContext::
@@ -350,15 +350,13 @@ public class DeviceController {
// Usage
DeviceController controller = new DeviceController();
Method method = ReflectionUtils.findMethod(
DeviceController.class, "setDeviceState", String.class, boolean.class, ToolContext.class);
String response = ChatClient.create(chatModel).prompt()
.user("Turn on the living room lights")
.functions(MethodFunctionCallback.builder()
.functionObject(controller)
.method(method)
.functions(FunctionCallback.builder()
.description("Control device state")
.method("setDeviceState", String.class,boolean.class,ToolContext.class)
.targetObject(controller)
.build())
.toolContext(Map.of("location", "home"))
.call()
@@ -368,7 +366,7 @@ String response = ChatClient.create(chatModel).prompt()
======
The https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientMethodFunctionCallbackIT.java[OpenAiChatClientMethodFunctionCallbackIT]
integration test provides additional examples of how to use the MethodFunctionCallback.
integration test provides additional examples of how to use the FunctionCallback.Builder to create method invocation FunctionCallbacks.
=== Tool Context
@@ -404,9 +402,10 @@ BiFunction<MockWeatherService.Request, ToolContext, MockWeatherService.Response>
ChatResponse response = chatClient.prompt("What's the weather like in San Francisco, Tokyo, and Paris?")
.functions(FunctionCallbackWrapper.builder(this.weatherFunction)
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", this.weatherFunction)
.inputType(MockWeatherService.Request.class)
.build())
.toolContext(Map.of("sessionId", "1234", "userId", "5678"))
.call()