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

@@ -48,7 +48,7 @@ import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.ai.converter.ListOutputConverter;
import org.springframework.ai.converter.MapOutputConverter;
import org.springframework.ai.model.Media;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringBootConfiguration;
@@ -256,10 +256,11 @@ class AnthropicChatModelIT {
var promptOptions = AnthropicChatOptions.builder()
.withModel(AnthropicApi.ChatModel.CLAUDE_3_OPUS.getName())
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription(
.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 MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -283,10 +284,11 @@ class AnthropicChatModelIT {
var promptOptions = AnthropicChatOptions.builder()
.withModel(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getName())
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription(
.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 MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -42,6 +42,7 @@ import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.ai.converter.ListOutputConverter;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
@@ -210,8 +211,30 @@ class AnthropicChatClientIT {
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius."))
.function("getCurrentWeather", "Get the weather in location", new MockWeatherService())
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.functions(FunctionCallback.builder()
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.call()
.content();
// @formatter:on
logger.info("Response: {}", response);
assertThat(response).contains("30", "10", "15");
}
@Test
void functionCallWithGeneratedDescription() {
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.functions(FunctionCallback.builder()
.function("getCurrentWeatherInLocation", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.call()
.content();
// @formatter:on
@@ -226,7 +249,11 @@ class AnthropicChatClientIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultFunction("getCurrentWeather", "Get the weather in location", new MockWeatherService())
.defaultFunctions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.defaultUser(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius."))
.build()
.prompt()
@@ -245,7 +272,11 @@ class AnthropicChatClientIT {
// @formatter:off
Flux<String> response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.function("getCurrentWeather", "Get the weather in location", new MockWeatherService())
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.stream()
.content();
// @formatter:on

View File

@@ -29,11 +29,10 @@ import org.springframework.ai.anthropic.AnthropicTestConfiguration;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.model.function.MethodFunctionCallback;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
@@ -41,9 +40,10 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
@SpringBootTest(classes = AnthropicTestConfiguration.class, properties = "spring.ai.retry.on-http-codes=429")
@EnabledIfEnvironmentVariable(named = "ANTHROPIC_API_KEY", matches = ".+")
@ActiveProfiles("logging-test")
class AnthropicChatClientMethodFunctionCallbackIT {
class AnthropicChatClientMethodInvokingFunctionCallbackIT {
private static final Logger logger = LoggerFactory.getLogger(AnthropicChatClientMethodFunctionCallbackIT.class);
private static final Logger logger = LoggerFactory
.getLogger(AnthropicChatClientMethodInvokingFunctionCallbackIT.class);
public static Map<String, Object> arguments = new ConcurrentHashMap<>();
@@ -53,15 +53,34 @@ class AnthropicChatClientMethodFunctionCallbackIT {
}
@Test
void methodGetWeatherStatic() {
void methodGetWeatherGeneratedDescription() {
var method = ReflectionUtils.findMethod(TestFunctionClass.class, "getWeatherStatic", String.class, Unit.class);
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.functions(MethodFunctionCallback.builder()
.method(method)
.functions(FunctionCallback.builder()
.method("getWeatherInLocation", String.class, Unit.class)
.targetClass(TestFunctionClass.class)
.build())
.call()
.content();
// @formatter:on
logger.info("Response: {}", response);
assertThat(response).contains("30", "10", "15");
}
@Test
void methodGetWeatherStatic() {
// @formatter:off
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)
.targetClass(TestFunctionClass.class)
.build())
.call()
.content();
@@ -77,15 +96,13 @@ class AnthropicChatClientMethodFunctionCallbackIT {
TestFunctionClass targetObject = new TestFunctionClass();
var method = ReflectionUtils.findMethod(TestFunctionClass.class, "turnLight", String.class, boolean.class);
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("Turn light on in the living room.")
.functions(MethodFunctionCallback.builder()
.functionObject(targetObject)
.method(method)
.description("Can turn lights on or off by room name")
.functions(FunctionCallback.builder()
.description("Turn light on in the living room.")
.method("turnLight", String.class, boolean.class)
.targetObject(targetObject)
.build())
.call()
.content();
@@ -102,16 +119,13 @@ class AnthropicChatClientMethodFunctionCallbackIT {
TestFunctionClass targetObject = new TestFunctionClass();
var method = ReflectionUtils.findMethod(TestFunctionClass.class, "getWeatherNonStatic", String.class,
Unit.class);
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.functions(MethodFunctionCallback.builder()
.functionObject(targetObject)
.method(method)
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.method("getWeatherNonStatic",String.class, Unit.class)
.targetObject(targetObject)
.build())
.call()
.content();
@@ -127,17 +141,14 @@ class AnthropicChatClientMethodFunctionCallbackIT {
TestFunctionClass targetObject = new TestFunctionClass();
var method = ReflectionUtils.findMethod(TestFunctionClass.class, "getWeatherWithContext", String.class,
Unit.class, ToolContext.class);
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.functions(MethodFunctionCallback.builder()
.functionObject(targetObject)
.method(method)
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.build())
.method("getWeatherWithContext", String.class, Unit.class, ToolContext.class)
.targetObject(targetObject)
.build())
.toolContext(Map.of("tool", "value"))
.call()
.content();
@@ -154,17 +165,14 @@ class AnthropicChatClientMethodFunctionCallbackIT {
TestFunctionClass targetObject = new TestFunctionClass();
var method = ReflectionUtils.findMethod(TestFunctionClass.class, "getWeatherNonStatic", String.class,
Unit.class);
// @formatter:off
assertThatThrownBy(() -> ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.functions(MethodFunctionCallback.builder()
.functionObject(targetObject)
.method(method)
.description("Get the weather in location")
.build())
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.method("getWeatherNonStatic", String.class, Unit.class)
.targetObject(targetObject)
.build())
.toolContext(Map.of("tool", "value"))
.call()
.content())
@@ -178,15 +186,13 @@ class AnthropicChatClientMethodFunctionCallbackIT {
TestFunctionClass targetObject = new TestFunctionClass();
var method = ReflectionUtils.findMethod(TestFunctionClass.class, "turnLivingRoomLightOn");
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("Turn light on in the living room.")
.functions(MethodFunctionCallback.builder()
.functionObject(targetObject)
.method(method)
.functions(FunctionCallback.builder()
.description("Can turn lights on in the Living Room")
.method("turnLivingRoomLightOn")
.targetObject(targetObject)
.build())
.call()
.content();
@@ -215,6 +221,10 @@ class AnthropicChatClientMethodFunctionCallbackIT {
arguments.put("method called", "argumentLessReturnVoid");
}
public static String getWeatherInLocation(String city, Unit unit) {
return getWeatherStatic(city, unit);
}
public static String getWeatherStatic(String city, Unit unit) {
logger.info("City: " + city + " Unit: " + unit);

View File

@@ -39,7 +39,7 @@ import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
@@ -70,10 +70,10 @@ class AzureOpenAiChatModelFunctionCallIT {
var promptOptions = AzureOpenAiChatOptions.builder()
.withDeploymentName(this.selectedModel)
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the current weather in a given location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the current weather in a given location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -94,10 +94,10 @@ class AzureOpenAiChatModelFunctionCallIT {
var promptOptions = AzureOpenAiChatOptions.builder()
.withDeploymentName(this.selectedModel)
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the current weather in a given location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the current weather in a given location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -116,10 +116,10 @@ class AzureOpenAiChatModelFunctionCallIT {
var promptOptions = AzureOpenAiChatOptions.builder()
.withDeploymentName(this.selectedModel)
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the current weather in a given location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the current weather in a given location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -153,10 +153,10 @@ class AzureOpenAiChatModelFunctionCallIT {
var promptOptions = AzureOpenAiChatOptions.builder()
.withDeploymentName(this.selectedModel)
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the current weather in a given location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the current weather in a given location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -37,6 +37,7 @@ import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.ai.converter.ListOutputConverter;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -212,7 +213,11 @@ class BedrockConverseChatClientIT {
// @formatter:off
String response = ChatClient.create(this.chatModel)
.prompt("What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.")
.function("getCurrentWeather", "Get the weather in location", new MockWeatherService())
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.call()
.content();
// @formatter:on
@@ -228,7 +233,11 @@ class BedrockConverseChatClientIT {
// @formatter:off
String response = ChatClient.create(this.chatModel)
.prompt("What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.")
.function("getCurrentWeather", "Get the weather in location", new MockWeatherService())
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.advisors(new SimpleLoggerAdvisor())
.call()
.content();
@@ -244,7 +253,11 @@ class BedrockConverseChatClientIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultFunction("getCurrentWeather", "Get the weather in location", new MockWeatherService())
.defaultFunctions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.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."))
.build()
.prompt()
@@ -263,7 +276,11 @@ class BedrockConverseChatClientIT {
// @formatter:off
Flux<String> response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius.")
.function("getCurrentWeather", "Get the weather in location", new MockWeatherService())
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.stream()
.content();
// @formatter:on
@@ -280,7 +297,11 @@ class BedrockConverseChatClientIT {
// @formatter:off
Flux<String> response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in Paris? Return the temperature in Celsius.")
.function("getCurrentWeather", "Get the weather in location", new MockWeatherService())
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.stream()
.content();
// @formatter:on

View File

@@ -47,7 +47,7 @@ import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.ai.converter.ListOutputConverter;
import org.springframework.ai.converter.MapOutputConverter;
import org.springframework.ai.model.Media;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -254,10 +254,11 @@ class BedrockProxyChatModelIT {
List<Message> messages = new ArrayList<>(List.of(userMessage));
var promptOptions = FunctionCallingOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription(
.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 MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -281,10 +282,11 @@ class BedrockProxyChatModelIT {
var promptOptions = FunctionCallingOptions.builder()
.withModel("anthropic.claude-3-5-sonnet-20240620-v1:0")
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription(
.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 MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -26,7 +26,7 @@ import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamOutput
import org.springframework.ai.bedrock.converse.BedrockProxyChatModel;
import org.springframework.ai.bedrock.converse.MockWeatherService;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
/**
@@ -52,9 +52,10 @@ public final class BedrockConverseChatModelMain2 {
"What's the weather like in Paris? Return the temperature in Celsius.",
PortableFunctionCallingOptions.builder()
.withModel(modelId)
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build());
@@ -68,11 +69,6 @@ public final class BedrockConverseChatModelMain2 {
Flux<ConverseStreamOutput> responses = chatModel.converseStream(streamRequest);
List<ConverseStreamOutput> responseList = responses.collectList().block();
System.out.println(responseList);
// Flux<ChatResponse> responses2 = ConverseApiUtils.toChatResponse(responses);
// List<ChatResponse> responseList2 = responses2.collectList().block();
// System.out.println(responseList2);
}
}

View File

@@ -23,7 +23,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.minimax.api.MiniMaxApi;
import org.springframework.ai.minimax.api.MockWeatherService;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallback;
import static org.assertj.core.api.Assertions.assertThat;
@@ -67,10 +67,10 @@ public class ChatCompletionRequestTests {
var request = client.createRequest(new Prompt("Test message content",
MiniMaxChatOptions.builder()
.withModel("PROMPT_MODEL")
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName(TOOL_FUNCTION_NAME)
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build()),
false);
@@ -94,10 +94,10 @@ public class ChatCompletionRequestTests {
var client = new MiniMaxChatModel(new MiniMaxApi("TEST"),
MiniMaxChatOptions.builder()
.withModel("DEFAULT_MODEL")
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName(TOOL_FUNCTION_NAME)
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build());
@@ -126,9 +126,10 @@ public class ChatCompletionRequestTests {
// Override the default options function with one from the prompt
request = client.createRequest(new Prompt("Test message content",
MiniMaxChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName(TOOL_FUNCTION_NAME)
.withDescription("Overridden function description")
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Overridden function description")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build()),
false);

View File

@@ -34,6 +34,7 @@ import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.ai.converter.ListOutputConverter;
import org.springframework.ai.mistralai.api.MistralAiApi;
import org.springframework.ai.mistralai.api.MistralAiApi.ChatCompletionRequest.ToolChoice;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
@@ -224,7 +225,11 @@ class MistralAiChatClientIT {
String response = ChatClient.create(this.chatModel).prompt()
.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."))
.function("getCurrentWeather", "Get the weather in location", new MockWeatherService())
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.call()
.content();
// @formatter:on
@@ -242,7 +247,11 @@ class MistralAiChatClientIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultOptions(MistralAiChatOptions.builder().withModel(MistralAiApi.ChatModel.SMALL).build())
.defaultFunction("getCurrentWeather", "Get the weather in location", new MockWeatherService())
.defaultFunctions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.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."))
.build()
.prompt().call().content();
@@ -262,7 +271,11 @@ class MistralAiChatClientIT {
Flux<String> response = ChatClient.create(this.chatModel).prompt()
.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.")
.function("getCurrentWeather", "Get the weather in location", new MockWeatherService())
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.stream()
.content();
// @formatter:on

View File

@@ -42,7 +42,7 @@ import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.ai.converter.ListOutputConverter;
import org.springframework.ai.converter.MapOutputConverter;
import org.springframework.ai.mistralai.api.MistralAiApi;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
@@ -193,10 +193,10 @@ class MistralAiChatModelIT {
var promptOptions = MistralAiChatOptions.builder()
.withModel(MistralAiApi.ChatModel.SMALL.getValue())
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -216,10 +216,10 @@ class MistralAiChatModelIT {
var promptOptions = MistralAiChatOptions.builder()
.withModel(MistralAiApi.ChatModel.SMALL.getValue())
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -34,7 +34,7 @@ import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.moonshot.MoonshotChatOptions;
import org.springframework.ai.moonshot.MoonshotTestConfiguration;
import org.springframework.ai.moonshot.api.MockWeatherService;
@@ -63,10 +63,10 @@ class MoonshotChatModelFunctionCallingIT {
var promptOptions = MoonshotChatOptions.builder()
.withModel(MoonshotApi.ChatModel.MOONSHOT_V1_8K.getValue())
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -86,11 +86,9 @@ class MoonshotChatModelFunctionCallingIT {
List<Message> messages = new ArrayList<>(List.of(userMessage));
var promptOptions = MoonshotChatOptions.builder()
// .withModel(OpenAiApi.ChatModel.GPT_4_TURBO_PREVIEW.getValue())
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.build()))
.build();

View File

@@ -33,7 +33,7 @@ import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.ollama.api.OllamaApi;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.ai.ollama.api.tool.MockWeatherService;
@@ -63,11 +63,11 @@ class OllamaChatModelFunctionCallingIT extends BaseOllamaIT {
var promptOptions = OllamaOptions.builder()
.withModel(MODEL)
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription(
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description(
"Find the weather conditions, forecasts, and temperatures for a location, like a city or state.")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -88,11 +88,11 @@ class OllamaChatModelFunctionCallingIT extends BaseOllamaIT {
var promptOptions = OllamaOptions.builder()
.withModel(MODEL)
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription(
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description(
"Find the weather conditions, forecasts, and temperatures for a location, like a city or state.")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -21,7 +21,7 @@ import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.openai.api.tool.MockWeatherService;
@@ -67,10 +67,10 @@ public class ChatCompletionRequestTests {
var request = client.createRequest(new Prompt("Test message content",
OpenAiChatOptions.builder()
.withModel("PROMPT_MODEL")
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName(TOOL_FUNCTION_NAME)
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build()),
false);
@@ -94,10 +94,10 @@ public class ChatCompletionRequestTests {
var client = new OpenAiChatModel(new OpenAiApi("TEST"),
OpenAiChatOptions.builder()
.withModel("DEFAULT_MODEL")
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName(TOOL_FUNCTION_NAME)
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build());
@@ -126,9 +126,10 @@ public class ChatCompletionRequestTests {
// Override the default options function with one from the prompt
request = client.createRequest(new Prompt("Test message content",
OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName(TOOL_FUNCTION_NAME)
.withDescription("Overridden function description")
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Overridden function description")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build()),
false);

View File

@@ -36,7 +36,7 @@ import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
@@ -63,10 +63,10 @@ class OpenAiChatModelFunctionCallingIT {
void functionCallTest() {
functionCallTest(OpenAiChatOptions.builder()
.withModel(OpenAiApi.ChatModel.GPT_4_O.getValue())
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build());
}
@@ -99,10 +99,10 @@ class OpenAiChatModelFunctionCallingIT {
functionCallTest(OpenAiChatOptions.builder()
.withModel(OpenAiApi.ChatModel.GPT_4_O.getValue())
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(biFunction)
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", biFunction)
.inputType(MockWeatherService.Request.class)
.build()))
.withToolContext(Map.of("sessionId", "123"))
.build());
@@ -125,10 +125,11 @@ class OpenAiChatModelFunctionCallingIT {
void streamFunctionCallTest() {
streamFunctionCallTest(OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of((FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of((FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
// .responseConverter(response -> "" + response.temp() + response.unit())
.build())))
.build());
}
@@ -160,10 +161,10 @@ class OpenAiChatModelFunctionCallingIT {
};
OpenAiChatOptions promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of((FunctionCallbackWrapper.builder(biFunction)
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of((FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", biFunction)
.inputType(MockWeatherService.Request.class)
.build())))
.withToolContext(Map.of("sessionId", "123"))
.build();

View File

@@ -48,7 +48,7 @@ import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.ai.converter.ListOutputConverter;
import org.springframework.ai.converter.MapOutputConverter;
import org.springframework.ai.model.Media;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.OpenAiTestConfiguration;
import org.springframework.ai.openai.api.OpenAiApi;
@@ -328,10 +328,10 @@ public class OpenAiChatModelIT extends AbstractIT {
var promptOptions = OpenAiChatOptions.builder()
.withModel(OpenAiApi.ChatModel.GPT_4_O.getValue())
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -353,10 +353,10 @@ public class OpenAiChatModelIT extends AbstractIT {
var promptOptions = OpenAiChatOptions.builder()
// .withModel(OpenAiApi.ChatModel.GPT_4_TURBO_PREVIEW.getValue())
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -43,7 +43,7 @@ import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.ToolCallHelper;
import org.springframework.ai.model.function.FunctionCallingHelper;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
@@ -64,7 +64,7 @@ class OpenAiChatModelProxyToolCallsIT {
private static final String DEFAULT_MODEL = "gpt-4o-mini";
FunctionCallback functionDefinition = new ToolCallHelper.FunctionDefinition("getWeatherInLocation",
FunctionCallback functionDefinition = new FunctionCallingHelper.FunctionDefinition("getWeatherInLocation",
"Get the weather in location", """
{
"type": "object",
@@ -87,7 +87,7 @@ class OpenAiChatModelProxyToolCallsIT {
// Helper class that reuses some of the {@link AbstractToolCallSupport} functionality
// to help to implement the function call handling logic on the client side.
private ToolCallHelper toolCallHelper = new ToolCallHelper();
private FunctionCallingHelper functionCallingHelper = new FunctionCallingHelper();
@SuppressWarnings("unchecked")
private static Map<String, String> getFunctionArguments(String functionArguments) {
@@ -139,7 +139,7 @@ class OpenAiChatModelProxyToolCallsIT {
// Note that the tool call check could be platform specific because the finish
// reasons.
isToolCall = this.toolCallHelper.isToolCall(chatResponse,
isToolCall = this.functionCallingHelper.isToolCall(chatResponse,
Set.of(OpenAiApi.ChatCompletionFinishReason.TOOL_CALLS.name(),
OpenAiApi.ChatCompletionFinishReason.STOP.name()));
@@ -176,7 +176,7 @@ class OpenAiChatModelProxyToolCallsIT {
ToolResponseMessage toolMessageResponse = new ToolResponseMessage(toolResponses, Map.of());
List<Message> toolCallConversation = this.toolCallHelper
List<Message> toolCallConversation = this.functionCallingHelper
.buildToolCallConversation(prompt.getInstructions(), assistantMessage, toolMessageResponse);
assertThat(toolCallConversation).isNotEmpty();
@@ -236,7 +236,7 @@ class OpenAiChatModelProxyToolCallsIT {
return chatResponses.flatMap(chatResponse -> {
boolean isToolCall = this.toolCallHelper.isToolCall(chatResponse, finishReasons);
boolean isToolCall = this.functionCallingHelper.isToolCall(chatResponse, finishReasons);
if (isToolCall) {
@@ -261,7 +261,7 @@ class OpenAiChatModelProxyToolCallsIT {
ToolResponseMessage toolMessageResponse = new ToolResponseMessage(toolResponses, Map.of());
List<Message> toolCallConversation = this.toolCallHelper
List<Message> toolCallConversation = this.functionCallingHelper
.buildToolCallConversation(prompt.getInstructions(), assistantMessage, toolMessageResponse);
assertThat(toolCallConversation).isNotEmpty();
@@ -285,7 +285,7 @@ class OpenAiChatModelProxyToolCallsIT {
var prompt = new Prompt(messages, promptOptions);
ChatResponse chatResponse = this.toolCallHelper.processCall(this.chatModel, prompt,
ChatResponse chatResponse = this.functionCallingHelper.processCall(this.chatModel, prompt,
Set.of(OpenAiApi.ChatCompletionFinishReason.TOOL_CALLS.name(),
OpenAiApi.ChatCompletionFinishReason.STOP.name()),
toolCall -> {
@@ -319,7 +319,7 @@ class OpenAiChatModelProxyToolCallsIT {
var prompt = new Prompt(messages, promptOptions);
Flux<ChatResponse> responses = this.toolCallHelper.processStream(this.chatModel, prompt,
Flux<ChatResponse> responses = this.functionCallingHelper.processStream(this.chatModel, prompt,
Set.of(OpenAiApi.ChatCompletionFinishReason.TOOL_CALLS.name(),
OpenAiApi.ChatCompletionFinishReason.STOP.name()),
toolCall -> {

View File

@@ -37,6 +37,7 @@ import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.ai.converter.ListOutputConverter;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.OpenAiTestConfiguration;
import org.springframework.ai.openai.api.OpenAiApi;
@@ -244,10 +245,16 @@ class OpenAiChatClientIT extends AbstractIT {
@Test
void functionCallTest() {
FunctionCallback functionCallback = FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build();
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris?"))
.function("getCurrentWeather", "Get the weather in location", new MockWeatherService())
.functions(functionCallback)
.call()
.content();
// @formatter:on
@@ -262,7 +269,11 @@ class OpenAiChatClientIT extends AbstractIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultFunction("getCurrentWeather", "Get the weather in location", new MockWeatherService())
.defaultFunctions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.defaultUser(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris?"))
.build()
.prompt().call().content();
@@ -279,7 +290,11 @@ class OpenAiChatClientIT extends AbstractIT {
// @formatter:off
Flux<String> response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris?")
.function("getCurrentWeather", "Get the weather in location", new MockWeatherService())
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.stream()
.content();
// @formatter:on

View File

@@ -28,12 +28,11 @@ import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.model.function.MethodFunctionCallback;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.openai.OpenAiTestConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
@@ -41,9 +40,10 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
@SpringBootTest(classes = OpenAiTestConfiguration.class)
@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+")
@ActiveProfiles("logging-test")
class OpenAiChatClientMethodFunctionCallbackIT {
class OpenAiChatClientMethodInvokingFunctionCallbackIT {
private static final Logger logger = LoggerFactory.getLogger(OpenAiChatClientMethodFunctionCallbackIT.class);
private static final Logger logger = LoggerFactory
.getLogger(OpenAiChatClientMethodInvokingFunctionCallbackIT.class);
public static Map<String, Object> arguments = new ConcurrentHashMap<>();
@@ -57,14 +57,13 @@ class OpenAiChatClientMethodFunctionCallbackIT {
@Test
void methodGetWeatherStatic() {
var method = ReflectionUtils.findMethod(TestFunctionClass.class, "getWeatherStatic", String.class, Unit.class);
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.functions(MethodFunctionCallback.builder()
.method(method)
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.method("getWeatherStatic",String.class, Unit.class)
.targetClass(TestFunctionClass.class)
.build())
.call()
.content();
@@ -80,15 +79,13 @@ class OpenAiChatClientMethodFunctionCallbackIT {
TestFunctionClass targetObject = new TestFunctionClass();
var method = ReflectionUtils.findMethod(TestFunctionClass.class, "turnLight", String.class, boolean.class);
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("Turn light on in the living room.")
.functions(MethodFunctionCallback.builder()
.functionObject(targetObject)
.method(method)
.functions(FunctionCallback.builder()
.description("Can turn lights on or off by room name")
.method("turnLight", String.class, boolean.class)
.targetObject(targetObject)
.build())
.call()
.content();
@@ -105,16 +102,13 @@ class OpenAiChatClientMethodFunctionCallbackIT {
TestFunctionClass targetObject = new TestFunctionClass();
var method = ReflectionUtils.findMethod(TestFunctionClass.class, "getWeatherNonStatic", String.class,
Unit.class);
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.functions(MethodFunctionCallback.builder()
.functionObject(targetObject)
.method(method)
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.method("getWeatherNonStatic",String.class, Unit.class)
.targetObject(targetObject)
.build())
.call()
.content();
@@ -130,16 +124,13 @@ class OpenAiChatClientMethodFunctionCallbackIT {
TestFunctionClass targetObject = new TestFunctionClass();
var method = ReflectionUtils.findMethod(TestFunctionClass.class, "getWeatherWithContext", String.class,
Unit.class, ToolContext.class);
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.functions(MethodFunctionCallback.builder()
.functionObject(targetObject)
.method(method)
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.method("getWeatherWithContext", String.class, Unit.class, ToolContext.class)
.targetObject(targetObject)
.build())
.toolContext(Map.of("tool", "value"))
.call()
@@ -157,17 +148,14 @@ class OpenAiChatClientMethodFunctionCallbackIT {
TestFunctionClass targetObject = new TestFunctionClass();
var method = ReflectionUtils.findMethod(TestFunctionClass.class, "getWeatherNonStatic", String.class,
Unit.class);
// @formatter:off
assertThatThrownBy(() -> ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris? Use Celsius.")
.functions(MethodFunctionCallback.builder()
.functionObject(targetObject)
.method(method)
.description("Get the weather in location")
.build())
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.method("getWeatherNonStatic", String.class, Unit.class)
.targetObject(targetObject)
.build())
.toolContext(Map.of("tool", "value"))
.call()
.content())
@@ -181,16 +169,14 @@ class OpenAiChatClientMethodFunctionCallbackIT {
TestFunctionClass targetObject = new TestFunctionClass();
var method = ReflectionUtils.findMethod(TestFunctionClass.class, "turnLivingRoomLightOn");
// @formatter:off
String response = ChatClient.create(this.chatModel).prompt()
.user("Turn light on in the living room.")
.functions(MethodFunctionCallback.builder()
.functionObject(targetObject)
.method(method)
.functions(FunctionCallback.builder()
.description("Can turn lights on in the Living Room")
.build())
.method("turnLivingRoomLightOn")
.targetObject(targetObject)
.build())
.call()
.content();
// @formatter:on

View File

@@ -31,6 +31,7 @@ import reactor.core.publisher.Flux;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.openai.OpenAiTestConfiguration;
import org.springframework.ai.openai.api.tool.MockWeatherService;
import org.springframework.ai.openai.api.tool.MockWeatherService.Request;
@@ -83,7 +84,11 @@ class OpenAiChatClientMultipleFunctionCallsIT extends AbstractIT {
// @formatter:off
response = chatClientBuilder.build().prompt()
.user(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris?"))
.function("getCurrentWeather", "Get the weather in location", new MockWeatherService())
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.call()
.content();
// @formatter:on
@@ -110,7 +115,11 @@ class OpenAiChatClientMultipleFunctionCallsIT extends AbstractIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultFunction("getCurrentWeather", "Get the weather in location", new MockWeatherService())
.defaultFunctions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.defaultUser(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris?"))
.build()
.prompt().call().content();
@@ -149,7 +158,11 @@ class OpenAiChatClientMultipleFunctionCallsIT extends AbstractIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultFunction("getCurrentWeather", "Get the weather in location", biFunction)
.defaultFunctions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", biFunction)
.inputType(MockWeatherService.Request.class)
.build())
.defaultUser(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris?"))
.defaultToolContext(Map.of("sessionId", "123"))
.build()
@@ -189,7 +202,11 @@ class OpenAiChatClientMultipleFunctionCallsIT extends AbstractIT {
// @formatter:off
String response = ChatClient.builder(this.chatModel)
.defaultFunction("getCurrentWeather", "Get the weather in location", biFunction)
.defaultFunctions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", biFunction)
.inputType(MockWeatherService.Request.class)
.build())
.defaultUser(u -> u.text("What's the weather like in San Francisco, Tokyo, and Paris?"))
.build()
.prompt()
@@ -208,7 +225,11 @@ class OpenAiChatClientMultipleFunctionCallsIT extends AbstractIT {
// @formatter:off
Flux<String> response = ChatClient.create(this.chatModel).prompt()
.user("What's the weather like in San Francisco, Tokyo, and Paris?")
.function("getCurrentWeather", "Get the weather in location", new MockWeatherService())
.functions(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build())
.stream()
.content();
// @formatter:on

View File

@@ -39,7 +39,7 @@ import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.ToolCallHelper;
import org.springframework.ai.model.function.FunctionCallingHelper;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.OpenAiTestConfiguration;
@@ -64,7 +64,7 @@ class OpenAiChatClientProxyFunctionCallsIT extends AbstractIT {
@Value("classpath:/prompts/system-message.st")
private Resource systemTextResource;
FunctionCallback functionDefinition = new ToolCallHelper.FunctionDefinition("getWeatherInLocation",
FunctionCallback functionDefinition = new FunctionCallingHelper.FunctionDefinition("getWeatherInLocation",
"Get the weather in location", """
{
"type": "object",
@@ -87,7 +87,7 @@ class OpenAiChatClientProxyFunctionCallsIT extends AbstractIT {
// Helper class that reuses some of the {@link AbstractToolCallSupport} functionality
// to help to implement the function call handling logic on the client side.
private ToolCallHelper toolCallHelper = new ToolCallHelper();
private FunctionCallingHelper functionCallingHelper = new FunctionCallingHelper();
// Function which will be called by the AI model.
private String getWeatherInLocation(String location, String unit) {
@@ -130,7 +130,7 @@ class OpenAiChatClientProxyFunctionCallsIT extends AbstractIT {
// Note that the tool call check could be platform specific because the finish
// reasons.
isToolCall = this.toolCallHelper.isToolCall(chatResponse,
isToolCall = this.functionCallingHelper.isToolCall(chatResponse,
Set.of(OpenAiApi.ChatCompletionFinishReason.TOOL_CALLS.name(),
OpenAiApi.ChatCompletionFinishReason.STOP.name()));
@@ -167,7 +167,7 @@ class OpenAiChatClientProxyFunctionCallsIT extends AbstractIT {
ToolResponseMessage toolMessageResponse = new ToolResponseMessage(toolResponses, Map.of());
messages = this.toolCallHelper.buildToolCallConversation(messages, assistantMessage,
messages = this.functionCallingHelper.buildToolCallConversation(messages, assistantMessage,
toolMessageResponse);
assertThat(messages).isNotEmpty();

View File

@@ -46,7 +46,7 @@ import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.ai.converter.ListOutputConverter;
import org.springframework.ai.converter.MapOutputConverter;
import org.springframework.ai.model.Media;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
@@ -249,10 +249,10 @@ class GroqWithOpenAiChatModelIT {
List<Message> messages = new ArrayList<>(List.of(userMessage));
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -272,10 +272,10 @@ class GroqWithOpenAiChatModelIT {
List<Message> messages = new ArrayList<>(List.of(userMessage));
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -46,7 +46,7 @@ import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.ai.converter.ListOutputConverter;
import org.springframework.ai.converter.MapOutputConverter;
import org.springframework.ai.model.Media;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
@@ -251,10 +251,10 @@ class MistralWithOpenAiChatModelIT {
var promptOptions = OpenAiChatOptions.builder()
.withModel(modelName)
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -276,10 +276,10 @@ class MistralWithOpenAiChatModelIT {
var promptOptions = OpenAiChatOptions.builder()
.withModel(modelName)
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -41,7 +41,7 @@ import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.ai.converter.ListOutputConverter;
import org.springframework.ai.converter.MapOutputConverter;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
@@ -246,10 +246,10 @@ class NvidiaWithOpenAiChatModelIT {
List<Message> messages = new ArrayList<>(List.of(userMessage));
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -269,10 +269,10 @@ class NvidiaWithOpenAiChatModelIT {
List<Message> messages = new ArrayList<>(List.of(userMessage));
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();

View File

@@ -49,7 +49,7 @@ import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.ai.converter.ListOutputConverter;
import org.springframework.ai.converter.MapOutputConverter;
import org.springframework.ai.model.Media;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
@@ -268,10 +268,10 @@ class OllamaWithOpenAiChatModelIT {
List<Message> messages = new ArrayList<>(List.of(userMessage));
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -292,10 +292,9 @@ class OllamaWithOpenAiChatModelIT {
List<Message> messages = new ArrayList<>(List.of(userMessage));
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.build()))
.build();

View File

@@ -32,7 +32,7 @@ import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.Media;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel.GeminiRequest;
import org.springframework.ai.vertexai.gemini.function.MockWeatherService;
import org.springframework.util.MimeTypeUtils;
@@ -117,10 +117,10 @@ public class CreateGeminiRequestTests {
var request = client.createGeminiRequest(new Prompt("Test message content",
VertexAiGeminiChatOptions.builder()
.withModel("PROMPT_MODEL")
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName(TOOL_FUNCTION_NAME)
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build()),
null);
@@ -145,10 +145,10 @@ public class CreateGeminiRequestTests {
var client = new VertexAiGeminiChatModel(this.vertexAI,
VertexAiGeminiChatOptions.builder()
.withModel("DEFAULT_MODEL")
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName(TOOL_FUNCTION_NAME)
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build());
@@ -178,9 +178,10 @@ public class CreateGeminiRequestTests {
// Override the default options function with one from the prompt
request = client.createGeminiRequest(new Prompt("Test message content",
VertexAiGeminiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName(TOOL_FUNCTION_NAME)
.withDescription("Overridden function description")
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Overridden function description")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build()),
null);

View File

@@ -35,8 +35,8 @@ import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallbackContext.SchemaType;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel;
import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions;
import org.springframework.beans.factory.annotation.Autowired;
@@ -83,11 +83,11 @@ public class VertexAiGeminiChatModelFunctionCallingIT {
""";
var promptOptions = VertexAiGeminiChatOptions.builder()
// .withModel(VertexAiGeminiChatModel.ChatModel.GEMINI_1_5_FLASH)
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("get_current_weather")
.withDescription("Get the current weather in a given location")
.withInputTypeSchema(openApiSchema)
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the current weather in a given location")
.inputTypeSchema(openApiSchema)
.function("get_current_weather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -108,16 +108,18 @@ public class VertexAiGeminiChatModelFunctionCallingIT {
var promptOptions = VertexAiGeminiChatOptions.builder()
.withModel(VertexAiGeminiChatModel.ChatModel.GEMINI_1_5_FLASH)
.withFunctionCallbacks(List.of(
FunctionCallbackWrapper.builder(new MockWeatherService())
.withSchemaType(SchemaType.OPEN_API_SCHEMA)
.withName("get_current_weather")
.withDescription("Get the current weather in a given location.")
FunctionCallback.builder()
.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(),
FunctionCallbackWrapper.builder(new PaymentStatus())
.withSchemaType(SchemaType.OPEN_API_SCHEMA)
.withName("get_payment_status")
.withDescription(
FunctionCallback.builder()
.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();
@@ -147,16 +149,18 @@ public class VertexAiGeminiChatModelFunctionCallingIT {
var promptOptions = VertexAiGeminiChatOptions.builder()
.withModel(VertexAiGeminiChatModel.ChatModel.GEMINI_1_5_FLASH)
.withFunctionCallbacks(List.of(
FunctionCallbackWrapper.builder(new MockWeatherService())
.withSchemaType(SchemaType.OPEN_API_SCHEMA)
.withName("get_current_weather")
.withDescription("Get the current weather in a given location.")
FunctionCallback.builder()
.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(),
FunctionCallbackWrapper.builder(new PaymentStatus())
.withSchemaType(SchemaType.OPEN_API_SCHEMA)
.withName("get_payment_status")
.withDescription(
FunctionCallback.builder()
.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();
@@ -185,10 +189,11 @@ public class VertexAiGeminiChatModelFunctionCallingIT {
var promptOptions = VertexAiGeminiChatOptions.builder()
.withModel(VertexAiGeminiChatModel.ChatModel.GEMINI_1_5_FLASH)
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withSchemaType(SchemaType.OPEN_API_SCHEMA)
.withName("getCurrentWeather")
.withDescription("Get the current weather in a given location")
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.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

@@ -21,7 +21,7 @@ import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.zhipuai.api.MockWeatherService;
import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
@@ -67,10 +67,10 @@ public class ChatCompletionRequestTests {
var request = client.createRequest(new Prompt("Test message content",
ZhiPuAiChatOptions.builder()
.withModel("PROMPT_MODEL")
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName(TOOL_FUNCTION_NAME)
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build()),
false);
@@ -94,10 +94,10 @@ public class ChatCompletionRequestTests {
var client = new ZhiPuAiChatModel(new ZhiPuAiApi("TEST"),
ZhiPuAiChatOptions.builder()
.withModel("DEFAULT_MODEL")
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName(TOOL_FUNCTION_NAME)
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build());
@@ -126,9 +126,10 @@ public class ChatCompletionRequestTests {
// Override the default options function with one from the prompt
request = client.createRequest(new Prompt("Test message content",
ZhiPuAiChatOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName(TOOL_FUNCTION_NAME)
.withDescription("Overridden function description")
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Overridden function description")
.function(TOOL_FUNCTION_NAME, new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build()),
false);

View File

@@ -47,7 +47,7 @@ import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.ai.converter.ListOutputConverter;
import org.springframework.ai.converter.MapOutputConverter;
import org.springframework.ai.model.Media;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
import org.springframework.ai.zhipuai.ZhiPuAiTestConfiguration;
import org.springframework.ai.zhipuai.api.MockWeatherService;
@@ -230,10 +230,10 @@ class ZhiPuAiChatModelIT {
var promptOptions = ZhiPuAiChatOptions.builder()
.withModel(ZhiPuAiApi.ChatModel.GLM_4.getValue())
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();
@@ -256,10 +256,10 @@ class ZhiPuAiChatModelIT {
var promptOptions = ZhiPuAiChatOptions.builder()
.withModel(ZhiPuAiApi.ChatModel.GLM_4.getValue())
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("getCurrentWeather")
.withDescription("Get the weather in location")
.withResponseConverter(response -> "" + response.temp() + response.unit())
.withFunctionCallbacks(List.of(FunctionCallback.builder()
.description("Get the weather in location")
.function("getCurrentWeather", new MockWeatherService())
.inputType(MockWeatherService.Request.class)
.build()))
.build();