Skip to content

Commit b4194bd

Browse files
Grogdunntzolov
authored andcommitted
🐛 Vertex API admit only one tool per request, but tools can have multiple function inside
1 parent 4b532ae commit b4194bd

File tree

2 files changed

+58
-12
lines changed

2 files changed

+58
-12
lines changed

models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatClient.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -349,16 +349,19 @@ else if (message instanceof AssistantMessage assistantMessage) {
349349

350350
private List<Tool> getFunctionTools(Set<String> functionNames) {
351351

352-
return this.resolveFunctionCallbacks(functionNames).stream().map(functionCallback -> {
353-
FunctionDeclaration functionDeclaration = FunctionDeclaration.newBuilder()
352+
final var tool = Tool.newBuilder();
353+
354+
final var functionDeclarations = this.resolveFunctionCallbacks(functionNames)
355+
.stream()
356+
.map(functionCallback -> FunctionDeclaration.newBuilder()
354357
.setName(functionCallback.getName())
355358
.setDescription(functionCallback.getDescription())
356359
.setParameters(jsonToSchema(functionCallback.getInputTypeSchema()))
357360
// .setParameters(toOpenApiSchema(functionCallback.getInputTypeSchema()))
358-
.build();
359-
360-
return Tool.newBuilder().addFunctionDeclarations(functionDeclaration).build();
361-
}).toList();
361+
.build())
362+
.toList();
363+
tool.addAllFunctionDeclarations(functionDeclarations);
364+
return List.of(tool.build());
362365
}
363366

364367
private static String structToJson(Struct struct) {

models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/function/VertexAiGeminiChatClientFunctionCallingIT.java

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,13 @@
1515
*/
1616
package org.springframework.ai.vertexai.gemini.function;
1717

18-
import java.util.ArrayList;
19-
import java.util.List;
20-
import java.util.stream.Collectors;
21-
2218
import com.google.cloud.vertexai.Transport;
2319
import com.google.cloud.vertexai.VertexAI;
2420
import org.junit.jupiter.api.AfterEach;
2521
import org.junit.jupiter.api.Test;
2622
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
2723
import org.slf4j.Logger;
2824
import org.slf4j.LoggerFactory;
29-
import reactor.core.publisher.Flux;
30-
3125
import org.springframework.ai.chat.ChatResponse;
3226
import org.springframework.ai.chat.Generation;
3327
import org.springframework.ai.chat.messages.AssistantMessage;
@@ -42,8 +36,15 @@
4236
import org.springframework.boot.SpringBootConfiguration;
4337
import org.springframework.boot.test.context.SpringBootTest;
4438
import org.springframework.context.annotation.Bean;
39+
import reactor.core.publisher.Flux;
40+
41+
import java.util.ArrayList;
42+
import java.util.List;
43+
import java.util.function.Function;
44+
import java.util.stream.Collectors;
4545

4646
import static org.assertj.core.api.Assertions.assertThat;
47+
import static org.junit.jupiter.api.Assertions.assertNotNull;
4748

4849
@SpringBootTest
4950
@EnabledIfEnvironmentVariable(named = "VERTEX_AI_GEMINI_PROJECT_ID", matches = ".*")
@@ -178,6 +179,48 @@ public void functionCallTestInferredOpenApiSchemaStream() {
178179

179180
}
180181

182+
//Gemini wants single tool with multiple function, instead multiple tools with single function
183+
@Test
184+
public void canDeclareMultipleFunctions() {
185+
186+
UserMessage userMessage = new UserMessage(
187+
"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.");
188+
189+
List<Message> messages = new ArrayList<>(List.of(userMessage));
190+
191+
final var weatherFunction = FunctionCallbackWrapper.builder(new MockWeatherService())
192+
.withSchemaType(SchemaType.OPEN_API_SCHEMA)
193+
.withName("getCurrentWeather")
194+
.withDescription("Get the current weather in a given location")
195+
.build();
196+
final var theAnswer = FunctionCallbackWrapper.builder(new TheAnswerMock())
197+
.withSchemaType(SchemaType.OPEN_API_SCHEMA)
198+
.withName("theAnswerToTheUniverse")
199+
.withDescription("the answer to the ultimate question of life, the universe, and everything")
200+
.build();
201+
var promptOptions = VertexAiGeminiChatOptions.builder()
202+
.withModel(VertexAiGeminiChatClient.ChatModel.GEMINI_PRO.getValue())
203+
.withFunctionCallbacks(List.of(weatherFunction, theAnswer))
204+
.build();
205+
206+
ChatResponse response = vertexGeminiClient.call(new Prompt(messages, promptOptions));
207+
208+
String responseString = response.getResult().getOutput().getContent();
209+
210+
logger.info("Response: {}", responseString);
211+
assertNotNull(responseString);
212+
213+
}
214+
215+
public static class TheAnswerMock implements Function<String, Integer> {
216+
217+
@Override
218+
public Integer apply(String s) {
219+
return 42;
220+
}
221+
222+
}
223+
181224
@SpringBootConfiguration
182225
public static class TestConfiguration {
183226

0 commit comments

Comments
 (0)