Fix support for FunctionCallingOptions across AI models

Add support for PortableFunctionCallingOptions across AI models

- Modify FunctionCallingOptions interface to extend ChatOptions for better integration
- Refactor option handling in chat models to accommodate both ChatOptions and FunctionCallingOptions
- Implement handling of FunctionCallingOptions in Anthropic, Azure OpenAI,
  MistralAI, Ollama, OpenAI, VertexAI Gemini, and other models
- Update existing function calling tests to use new FunctionCallingOptions.

Resolves #624
This commit is contained in:
Christian Tzolov
2024-10-01 19:25:14 +02:00
committed by Mark Pollack
parent 89cfa9911d
commit 45c6622736
17 changed files with 241 additions and 40 deletions

View File

@@ -53,6 +53,7 @@ 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.FunctionCallbackContext;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.retry.support.RetryTemplate;
@@ -413,8 +414,15 @@ public class AnthropicChatModel extends AbstractToolCallSupport implements ChatM
systemPrompt, this.defaultOptions.getMaxTokens(), this.defaultOptions.getTemperature(), stream);
if (prompt.getOptions() != null) {
AnthropicChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(),
ChatOptions.class, AnthropicChatOptions.class);
AnthropicChatOptions updatedRuntimeOptions;
if (prompt.getOptions() instanceof FunctionCallingOptions functionCallingOptions) {
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(functionCallingOptions,
FunctionCallingOptions.class, AnthropicChatOptions.class);
}
else {
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
AnthropicChatOptions.class);
}
functionsForThisRequest.addAll(this.runtimeFunctionCallbackConfigurations(updatedRuntimeOptions));

View File

@@ -42,6 +42,7 @@ import org.springframework.ai.model.Media;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallbackContext;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Flux;
@@ -268,8 +269,15 @@ public class AzureOpenAiChatModel extends AbstractToolCallSupport implements Cha
functionsForThisRequest.addAll(this.defaultOptions.getFunctions());
if (prompt.getOptions() != null) {
AzureOpenAiChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(),
ChatOptions.class, AzureOpenAiChatOptions.class);
AzureOpenAiChatOptions updatedRuntimeOptions;
if (prompt.getOptions() instanceof FunctionCallingOptions functionCallingOptions) {
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(functionCallingOptions,
FunctionCallingOptions.class, AzureOpenAiChatOptions.class);
}
else {
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
AzureOpenAiChatOptions.class);
}
options = this.merge(updatedRuntimeOptions, options);
functionsForThisRequest.addAll(this.runtimeFunctionCallbackConfigurations(updatedRuntimeOptions));

View File

@@ -44,6 +44,7 @@ import org.springframework.ai.minimax.metadata.MiniMaxUsage;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallbackContext;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.retry.support.RetryTemplate;
@@ -391,8 +392,16 @@ public class MiniMaxChatModel extends AbstractToolCallSupport implements ChatMod
Set<String> enabledToolsToUse = new HashSet<>();
if (prompt.getOptions() != null) {
MiniMaxChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(),
ChatOptions.class, MiniMaxChatOptions.class);
MiniMaxChatOptions updatedRuntimeOptions;
if (prompt.getOptions() instanceof FunctionCallingOptions functionCallingOptions) {
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(functionCallingOptions,
FunctionCallingOptions.class, MiniMaxChatOptions.class);
}
else {
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
MiniMaxChatOptions.class);
}
enabledToolsToUse.addAll(this.runtimeFunctionCallbackConfigurations(updatedRuntimeOptions));

View File

@@ -52,6 +52,7 @@ import org.springframework.ai.mistralai.metadata.MistralAiUsage;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallbackContext;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.retry.support.RetryTemplate;
@@ -367,8 +368,16 @@ public class MistralAiChatModel extends AbstractToolCallSupport implements ChatM
request = ModelOptionsUtils.merge(request, this.defaultOptions, MistralAiApi.ChatCompletionRequest.class);
if (prompt.getOptions() != null) {
var updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
MistralAiChatOptions.class);
MistralAiChatOptions updatedRuntimeOptions;
if (prompt.getOptions() instanceof FunctionCallingOptions functionCallingOptions) {
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(functionCallingOptions,
FunctionCallingOptions.class, MistralAiChatOptions.class);
}
else {
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
MistralAiChatOptions.class);
}
functionsForThisRequest.addAll(this.runtimeFunctionCallbackConfigurations(updatedRuntimeOptions));

View File

@@ -33,6 +33,7 @@ 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.FunctionCallbackContext;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.moonshot.api.MoonshotApi;
import org.springframework.ai.moonshot.api.MoonshotApi.ChatCompletion;
import org.springframework.ai.moonshot.api.MoonshotApi.ChatCompletion.Choice;
@@ -341,9 +342,16 @@ public class MoonshotChatModel extends AbstractToolCallSupport implements ChatMo
Set<String> enabledToolsToUse = new HashSet<>();
if (prompt.getOptions() != null) {
MoonshotChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(),
ChatOptions.class, MoonshotChatOptions.class);
MoonshotChatOptions updatedRuntimeOptions;
if (prompt.getOptions() instanceof FunctionCallingOptions functionCallingOptions) {
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(functionCallingOptions,
FunctionCallingOptions.class, MoonshotChatOptions.class);
}
else {
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
MoonshotChatOptions.class);
}
enabledToolsToUse.addAll(this.runtimeFunctionCallbackConfigurations(updatedRuntimeOptions));
request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, ChatCompletionRequest.class);

View File

@@ -41,6 +41,7 @@ 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.FunctionCallbackContext;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.ollama.api.OllamaApi;
import org.springframework.ai.ollama.api.OllamaApi.ChatRequest;
import org.springframework.ai.ollama.api.OllamaApi.Message.Role;
@@ -297,8 +298,14 @@ public class OllamaChatModel extends AbstractToolCallSupport implements ChatMode
// runtime options
OllamaOptions runtimeOptions = null;
if (prompt.getOptions() != null) {
runtimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
OllamaOptions.class);
if (prompt.getOptions() instanceof FunctionCallingOptions functionCallingOptions) {
runtimeOptions = ModelOptionsUtils.copyToTarget(functionCallingOptions, FunctionCallingOptions.class,
OllamaOptions.class);
}
else {
runtimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
OllamaOptions.class);
}
functionsForThisRequest.addAll(this.runtimeFunctionCallbackConfigurations(runtimeOptions));
}

View File

@@ -51,6 +51,7 @@ 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.FunctionCallbackContext;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.openai.api.OpenAiApi.ChatCompletion;
import org.springframework.ai.openai.api.OpenAiApi.ChatCompletion.Choice;
@@ -477,8 +478,16 @@ public class OpenAiChatModel extends AbstractToolCallSupport implements ChatMode
Set<String> enabledToolsToUse = new HashSet<>();
if (prompt.getOptions() != null) {
OpenAiChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(),
ChatOptions.class, OpenAiChatOptions.class);
OpenAiChatOptions updatedRuntimeOptions = null;
if (prompt.getOptions() instanceof FunctionCallingOptions) {
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(((FunctionCallingOptions) prompt.getOptions()),
FunctionCallingOptions.class, OpenAiChatOptions.class);
}
else if (prompt.getOptions() instanceof OpenAiChatOptions) {
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
OpenAiChatOptions.class);
}
enabledToolsToUse.addAll(this.runtimeFunctionCallbackConfigurations(updatedRuntimeOptions));

View File

@@ -181,15 +181,9 @@ public class QianFanChatModel implements ChatModel, StreamingChatModel {
}
if (prompt.getOptions() != null) {
if (prompt.getOptions() != null) {
var updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
QianFanChatOptions.class);
request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, ChatCompletionRequest.class);
}
else {
throw new IllegalArgumentException("Prompt options are not of type ChatOptions: "
+ prompt.getOptions().getClass().getSimpleName());
}
var updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
QianFanChatOptions.class);
request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, ChatCompletionRequest.class);
}
return request;
}

View File

@@ -44,6 +44,7 @@ import org.springframework.ai.model.Media;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallbackContext;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.ai.vertexai.gemini.metadata.VertexAiUsage;
import org.springframework.beans.factory.DisposableBean;
@@ -71,8 +72,6 @@ import java.util.Set;
*/
public class VertexAiGeminiChatModel extends AbstractToolCallSupport implements ChatModel, DisposableBean {
private final static boolean IS_RUNTIME_CALL = true;
private final VertexAI vertexAI;
private final VertexAiGeminiChatOptions defaultOptions;
@@ -297,9 +296,15 @@ public class VertexAiGeminiChatModel extends AbstractToolCallSupport implements
VertexAiGeminiChatOptions updatedRuntimeOptions = VertexAiGeminiChatOptions.builder().build();
if (prompt.getOptions() != null) {
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
VertexAiGeminiChatOptions.class);
if (prompt.getOptions() instanceof FunctionCallingOptions functionCallingOptions) {
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(functionCallingOptions,
FunctionCallingOptions.class, VertexAiGeminiChatOptions.class);
}
else {
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
VertexAiGeminiChatOptions.class);
}
functionsForThisRequest.addAll(runtimeFunctionCallbackConfigurations(updatedRuntimeOptions));
}

View File

@@ -33,6 +33,7 @@ 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.FunctionCallbackContext;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
import org.springframework.ai.zhipuai.api.ZhiPuAiApi.ChatCompletion;
@@ -358,8 +359,15 @@ public class ZhiPuAiChatModel extends AbstractToolCallSupport implements ChatMod
Set<String> enabledToolsToUse = new HashSet<>();
if (prompt.getOptions() != null) {
ZhiPuAiChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(),
ChatOptions.class, ZhiPuAiChatOptions.class);
ZhiPuAiChatOptions updatedRuntimeOptions;
if (prompt.getOptions() instanceof FunctionCallingOptions functionCallingOptions) {
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(functionCallingOptions,
FunctionCallingOptions.class, ZhiPuAiChatOptions.class);
}
else {
updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,
ZhiPuAiChatOptions.class);
}
enabledToolsToUse.addAll(this.runtimeFunctionCallbackConfigurations(updatedRuntimeOptions));

View File

@@ -18,10 +18,12 @@ package org.springframework.ai.model.function;
import java.util.List;
import java.util.Set;
import org.springframework.ai.chat.prompt.ChatOptions;
/**
* @author Christian Tzolov
*/
public interface FunctionCallingOptions {
public interface FunctionCallingOptions extends ChatOptions {
/**
* Function Callbacks to be registered with the ChatModel. For Prompt Options the

View File

@@ -33,6 +33,7 @@ import org.springframework.ai.autoconfigure.anthropic.tool.MockWeatherService.Re
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
@@ -79,6 +80,28 @@ class FunctionCallWithFunctionBeanIT {
});
}
@Test
void functionCallWithPortableFunctionCallingOptions() {
contextRunner
.withPropertyValues(
"spring.ai.anthropic.chat.options.model=" + AnthropicApi.ChatModel.CLAUDE_3_OPUS.getValue())
.run(context -> {
AnthropicChatModel chatModel = context.getBean(AnthropicChatModel.class);
var userMessage = new UserMessage(
"What's the weather like in San Francisco, in Paris, France and in Tokyo, Japan? Return the temperature in Celsius.");
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage),
PortableFunctionCallingOptions.builder().withFunction("weatherFunction").build()));
logger.info("Response: {}", response);
assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15");
});
}
@Configuration
static class Config {

View File

@@ -30,6 +30,7 @@ import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
@@ -80,6 +81,26 @@ class FunctionCallWithFunctionBeanIT {
});
}
@Test
void functionCallWithPortableFunctionCallingOptions() {
contextRunner.withPropertyValues("spring.ai.azure.openai.chat.options..deployment-name=" + getDeploymentName())
.run(context -> {
ChatModel chatModel = context.getBean(AzureOpenAiChatModel.class);
UserMessage userMessage = new UserMessage(
"What's the weather like in San Francisco, Paris and in Tokyo? Use Multi-turn function calling.");
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage),
PortableFunctionCallingOptions.builder().withFunction("weatherFunction").build()));
logger.info("Response: {}", response);
assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15");
});
}
@Configuration
static class Config {

View File

@@ -35,6 +35,8 @@ import org.springframework.ai.mistralai.MistralAiChatOptions;
import org.springframework.ai.mistralai.api.MistralAiApi;
import org.springframework.ai.mistralai.api.MistralAiApi.ChatCompletionRequest.ToolChoice;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -63,7 +65,7 @@ public class WeatherServicePromptIT {
MistralAiChatModel chatModel = context.getBean(MistralAiChatModel.class);
UserMessage userMessage = new UserMessage("What's the weather like in Paris?");
UserMessage userMessage = new UserMessage("What's the weather like in Paris? Use Celsius.");
// UserMessage userMessage = new UserMessage("What's the weather like in
// San Francisco, Tokyo, and
// Paris?");
@@ -86,6 +88,32 @@ public class WeatherServicePromptIT {
});
}
@Test
void functionCallWithPortableFunctionCallingOptions() {
contextRunner
.withPropertyValues("spring.ai.mistralai.chat.options.model=" + MistralAiApi.ChatModel.LARGE.getValue())
.run(context -> {
MistralAiChatModel chatModel = context.getBean(MistralAiChatModel.class);
UserMessage userMessage = new UserMessage("What's the weather like in Paris? Use Celsius.");
PortableFunctionCallingOptions functionOptions = FunctionCallingOptions.builder()
.withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MyWeatherService())
.withName("CurrentWeatherService")
.withDescription("Get the current weather in requested location")
.build()))
.build();
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), functionOptions));
logger.info("Response: {}", response);
assertThat(response.getResult().getOutput().getContent()).containsAnyOf("15", "15.0");
});
}
public static class MyWeatherService implements Function<Request, Response> {
// @formatter:off

View File

@@ -35,6 +35,8 @@ 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.FunctionCallbackWrapper;
import org.springframework.ai.model.function.FunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.boot.autoconfigure.AutoConfigurations;
@@ -125,6 +127,27 @@ public class FunctionCallbackWrapperIT {
});
}
@Test
void functionCallWithPortableFunctionCallingOptions() {
contextRunner.run(context -> {
OllamaChatModel chatModel = context.getBean(OllamaChatModel.class);
// Test weatherFunction
UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?");
PortableFunctionCallingOptions functionOptions = FunctionCallingOptions.builder()
.withFunction("WeatherInfo")
.build();
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), functionOptions));
logger.info("Response: " + response.getResult().getOutput().getContent());
assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15");
});
}
@Configuration
static class Config {

View File

@@ -26,12 +26,13 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.AssistantMessage;
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.FunctionCallingOptions;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi.ChatModel;
@@ -91,15 +92,19 @@ class FunctionCallbackWithPlainFunctionBeanIT {
OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class);
// @formatter:off
String content = ChatClient.builder(chatModel).build().prompt()
.functions("weatherFunction")
.user("What's the weather like in San Francisco, Tokyo, and Paris? You can call the following functions 'weatherFunction'")
.stream().content()
.collectList().block().stream().collect(Collectors.joining());
// @formatter:on
// Test weatherFunction
UserMessage userMessage = new UserMessage(
"What's the weather like in San Francisco, Tokyo, and Paris?");
logger.info("Response: {}", content);
PortableFunctionCallingOptions functionOptions = FunctionCallingOptions.builder()
.withFunction("weatherFunction")
.build();
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), functionOptions));
logger.info("Response: {}", response.getResult().getOutput().getContent());
assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15");
});
}

View File

@@ -28,6 +28,7 @@ import org.springframework.ai.autoconfigure.vertexai.gemini.VertexAiGeminiAutoCo
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.function.FunctionCallingOptionsBuilder.PortableFunctionCallingOptions;
import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel;
import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions;
import org.springframework.boot.autoconfigure.AutoConfigurations;
@@ -89,6 +90,39 @@ class FunctionCallWithFunctionBeanIT {
});
}
@Test
void functionCallWithPortableFunctionCallingOptions() {
contextRunner.withPropertyValues("spring.ai.vertex.ai.gemini.chat.options.model="
// + VertexAiGeminiChatModel.ChatModel.GEMINI_PRO_1_5_PRO.getValue())
+ VertexAiGeminiChatModel.ChatModel.GEMINI_1_5_FLASH.getValue())
.run(context -> {
VertexAiGeminiChatModel chatModel = context.getBean(VertexAiGeminiChatModel.class);
var userMessage = new UserMessage("""
What's the weather like in San Francisco, Paris and in Tokyo?
Return the temperature in Celsius.
Perform multiple funciton execution if necessary.
""");
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage),
PortableFunctionCallingOptions.builder().withFunction("weatherFunction").build()));
logger.info("Response: {}", response);
assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15");
response = chatModel.call(new Prompt(List.of(userMessage),
VertexAiGeminiChatOptions.builder().withFunction("weatherFunction3").build()));
logger.info("Response: {}", response);
assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15");
});
}
@Configuration
static class Config {