diff --git a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatClient.java b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatClient.java index b687ac97c..762407cd4 100644 --- a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatClient.java +++ b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatClient.java @@ -349,16 +349,19 @@ public class VertexAiGeminiChatClient private List getFunctionTools(Set functionNames) { - return this.resolveFunctionCallbacks(functionNames).stream().map(functionCallback -> { - FunctionDeclaration functionDeclaration = FunctionDeclaration.newBuilder() + final var tool = Tool.newBuilder(); + + final var functionDeclarations = this.resolveFunctionCallbacks(functionNames) + .stream() + .map(functionCallback -> FunctionDeclaration.newBuilder() .setName(functionCallback.getName()) .setDescription(functionCallback.getDescription()) .setParameters(jsonToSchema(functionCallback.getInputTypeSchema())) // .setParameters(toOpenApiSchema(functionCallback.getInputTypeSchema())) - .build(); - - return Tool.newBuilder().addFunctionDeclarations(functionDeclaration).build(); - }).toList(); + .build()) + .toList(); + tool.addAllFunctionDeclarations(functionDeclarations); + return List.of(tool.build()); } private static String structToJson(Struct struct) { diff --git a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/function/VertexAiGeminiChatClientFunctionCallingIT.java b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/function/VertexAiGeminiChatClientFunctionCallingIT.java index 19d4b4e9c..855bb09e6 100644 --- a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/function/VertexAiGeminiChatClientFunctionCallingIT.java +++ b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/function/VertexAiGeminiChatClientFunctionCallingIT.java @@ -15,10 +15,6 @@ */ package org.springframework.ai.vertexai.gemini.function; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - import com.google.cloud.vertexai.Transport; import com.google.cloud.vertexai.VertexAI; import org.junit.jupiter.api.AfterEach; @@ -26,8 +22,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; - import org.springframework.ai.chat.ChatResponse; import org.springframework.ai.chat.Generation; import org.springframework.ai.chat.messages.AssistantMessage; @@ -42,8 +36,15 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; +import reactor.core.publisher.Flux; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; @SpringBootTest @EnabledIfEnvironmentVariable(named = "VERTEX_AI_GEMINI_PROJECT_ID", matches = ".*") @@ -178,6 +179,48 @@ public class VertexAiGeminiChatClientFunctionCallingIT { } + //Gemini wants single tool with multiple function, instead multiple tools with single function + @Test + public void canDeclareMultipleFunctions() { + + UserMessage userMessage = new UserMessage( + "What's the weather like in San Francisco, in Paris and in Tokyo, Japan? Use Multi-turn function calling. Provide answer for all requested locations."); + + List messages = new ArrayList<>(List.of(userMessage)); + + final var weatherFunction = FunctionCallbackWrapper.builder(new MockWeatherService()) + .withSchemaType(SchemaType.OPEN_API_SCHEMA) + .withName("getCurrentWeather") + .withDescription("Get the current weather in a given location") + .build(); + final var theAnswer = FunctionCallbackWrapper.builder(new TheAnswerMock()) + .withSchemaType(SchemaType.OPEN_API_SCHEMA) + .withName("theAnswerToTheUniverse") + .withDescription("the answer to the ultimate question of life, the universe, and everything") + .build(); + var promptOptions = VertexAiGeminiChatOptions.builder() + .withModel(VertexAiGeminiChatClient.ChatModel.GEMINI_PRO.getValue()) + .withFunctionCallbacks(List.of(weatherFunction, theAnswer)) + .build(); + + ChatResponse response = vertexGeminiClient.call(new Prompt(messages, promptOptions)); + + String responseString = response.getResult().getOutput().getContent(); + + logger.info("Response: {}", responseString); + assertNotNull(responseString); + + } + + public static class TheAnswerMock implements Function { + + @Override + public Integer apply(String s) { + return 42; + } + + } + @SpringBootConfiguration public static class TestConfiguration {