Skip to content

Commit e5109a0

Browse files
yyyu-googlecopybara-github
authored andcommitted
feat: automatically parse a java.lang.reflect.Method instance into a FunctionDeclaration when users pass it in as a Tool.
PiperOrigin-RevId: 756851696
1 parent 78cc345 commit e5109a0

File tree

10 files changed

+447
-162
lines changed

10 files changed

+447
-162
lines changed

examples/pom.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,22 @@
1414
<maven.compiler.target>1.8</maven.compiler.target>
1515
<google-genai.version>0.8.0-SNAPSHOT</google-genai.version><!-- {x-version-update:google-genai:current} -->
1616
</properties>
17+
<build>
18+
<pluginManagement>
19+
<plugins>
20+
<plugin>
21+
<groupId>org.apache.maven.plugins</groupId>
22+
<artifactId>maven-compiler-plugin</artifactId>
23+
<version>3.14.0</version>
24+
<configuration>
25+
<compilerArgs>
26+
<arg>-parameters</arg>
27+
</compilerArgs>
28+
</configuration>
29+
</plugin>
30+
</plugins>
31+
</pluginManagement>
32+
</build>
1733

1834
<dependencies>
1935
<dependency>

examples/src/main/java/com/google/genai/examples/GenerateContentWithFunctionCall.java

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -40,52 +40,38 @@
4040
package com.google.genai.examples;
4141

4242
import com.google.common.collect.ImmutableList;
43-
import com.google.common.collect.ImmutableMap;
4443
import com.google.genai.Client;
45-
import com.google.genai.types.FunctionDeclaration;
4644
import com.google.genai.types.GenerateContentConfig;
4745
import com.google.genai.types.GenerateContentResponse;
48-
import com.google.genai.types.Schema;
49-
import com.google.genai.types.Type;
46+
import com.google.genai.types.Tool;
47+
import java.lang.reflect.Method;
5048

5149
/** An example of using the Unified Gen AI Java SDK to generate content with function calling. */
5250
public class GenerateContentWithFunctionCall {
53-
public static void main(String[] args) {
51+
public static String getCurrentWeather(String location, String unit) {
52+
return "The weather in " + location + " is " + "very nice.";
53+
}
54+
public static void main(String[] args) throws NoSuchMethodException {
5455
// Instantiate the client using Gemini Developer API.
5556
Client client = new Client();
5657

57-
FunctionDeclaration functionDeclaration =
58-
FunctionDeclaration.builder()
59-
.name("get_current_weather")
60-
.parameters(
61-
Schema.builder()
62-
.type(Type.Known.OBJECT)
63-
.properties(
64-
ImmutableMap.of(
65-
"location",
66-
Schema.builder()
67-
.type(Type.Known.STRING)
68-
.description("The location to get the weather for.")
69-
.build(),
70-
"unit",
71-
Schema.builder()
72-
.type(Type.Known.STRING)
73-
.description("The unit to return the weather in, e.g. 'celsius'.")
74-
.build()))
75-
.required(ImmutableList.of("location", "unit"))
76-
.build())
77-
.build();
58+
Method method = GenerateContentWithFunctionCall.class.getMethod(
59+
"getCurrentWeather", String.class, String.class);
7860

7961
GenerateContentConfig config =
80-
GenerateContentConfig.fromJson(
81-
String.format(
82-
"{\"tools\":[{\"functionDeclarations\":[%s]}]}", functionDeclaration.toJson()));
62+
GenerateContentConfig.builder()
63+
.tools(ImmutableList.of(
64+
Tool.builder()
65+
.functions(ImmutableList.of(method))
66+
.build()
67+
))
68+
.build();
8369

8470
GenerateContentResponse response =
8571
client.models.generateContent(
8672
"gemini-2.0-flash-001", "What is the weather in Vancouver?", config);
8773

88-
// Gets the function calls from the response by the quick accessor method `functionCalls()`.
89-
System.out.println("Response: " + response.functionCalls());
74+
// to look into what arguments were passed to call the getCurrentWeather function.
75+
System.out.println("Function call arguments: " + response.functionCalls());
9076
}
9177
}

pom.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,17 @@
256256
</executions>
257257
</plugin>
258258
<plugin>
259+
<groupId>org.apache.maven.plugins</groupId>
260+
<artifactId>maven-compiler-plugin</artifactId>
261+
<version>3.14.0</version>
262+
<configuration>
263+
<compilerArgs>
264+
<arg>-parameters</arg>
265+
</compilerArgs>
266+
</configuration>
267+
</plugin>
268+
<plugin>
269+
<groupId>org.apache.maven.plugins</groupId>
259270
<artifactId>maven-javadoc-plugin</artifactId>
260271
<version>3.6.3</version>
261272
<configuration>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.genai;
18+
19+
import static com.google.common.collect.ImmutableList.toImmutableList;
20+
21+
import com.fasterxml.jackson.databind.node.ObjectNode;
22+
import com.google.common.collect.ImmutableList;
23+
import com.google.genai.types.GenerateContentConfig;
24+
import com.google.genai.types.Tool;
25+
26+
final class AfcUtil {
27+
28+
static GenerateContentConfig transformGenerateContentConfig(
29+
ApiClient apiClient, GenerateContentConfig config) {
30+
GenerateContentConfig transformedConfig;
31+
if (config != null && config.tools().isPresent() && !config.tools().get().isEmpty()) {
32+
ImmutableList<Tool> transformedTools =
33+
config.tools().get().stream()
34+
.map(tool -> Transformers.tTool(apiClient, tool))
35+
.collect(toImmutableList());
36+
ObjectNode configNode = JsonSerializable.objectMapper.valueToTree(config);
37+
configNode.set("tools", JsonSerializable.objectMapper.valueToTree(transformedTools));
38+
transformedConfig = JsonSerializable.fromJsonNode(configNode, GenerateContentConfig.class);
39+
} else {
40+
transformedConfig = config;
41+
}
42+
return transformedConfig;
43+
}
44+
45+
private AfcUtil() {}
46+
}

src/main/java/com/google/genai/Models.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4576,7 +4576,9 @@ public GenerateVideosOperation generateVideos(
45764576
*/
45774577
public GenerateContentResponse generateContent(
45784578
String model, List<Content> contents, GenerateContentConfig config) {
4579-
return privateGenerateContent(model, contents, config);
4579+
GenerateContentConfig transformedConfig =
4580+
AfcUtil.transformGenerateContentConfig(apiClient, config);
4581+
return privateGenerateContent(model, contents, transformedConfig);
45804582
}
45814583

45824584
/**
@@ -4591,8 +4593,7 @@ public GenerateContentResponse generateContent(
45914593
*/
45924594
public GenerateContentResponse generateContent(
45934595
String model, Content content, GenerateContentConfig config) {
4594-
return privateGenerateContent(
4595-
model, Transformers.tContents(this.apiClient, (Object) content), config);
4596+
return generateContent(model, Transformers.tContents(this.apiClient, (Object) content), config);
45964597
}
45974598

45984599
/**
@@ -4607,8 +4608,7 @@ public GenerateContentResponse generateContent(
46074608
*/
46084609
public GenerateContentResponse generateContent(
46094610
String model, String text, GenerateContentConfig config) {
4610-
return privateGenerateContent(
4611-
model, Transformers.tContents(this.apiClient, (Object) text), config);
4611+
return generateContent(model, Transformers.tContents(this.apiClient, (Object) text), config);
46124612
}
46134613

46144614
/**
@@ -4623,7 +4623,9 @@ public GenerateContentResponse generateContent(
46234623
*/
46244624
public ResponseStream<GenerateContentResponse> generateContentStream(
46254625
String model, List<Content> contents, GenerateContentConfig config) {
4626-
return privateGenerateContentStream(model, contents, config);
4626+
GenerateContentConfig transformedConfig =
4627+
AfcUtil.transformGenerateContentConfig(apiClient, config);
4628+
return privateGenerateContentStream(model, contents, transformedConfig);
46274629
}
46284630

46294631
/**
@@ -4638,7 +4640,7 @@ public ResponseStream<GenerateContentResponse> generateContentStream(
46384640
*/
46394641
public ResponseStream<GenerateContentResponse> generateContentStream(
46404642
String model, Content content, GenerateContentConfig config) {
4641-
return privateGenerateContentStream(
4643+
return generateContentStream(
46424644
model, Transformers.tContents(this.apiClient, (Object) content), config);
46434645
}
46444646

@@ -4654,7 +4656,7 @@ public ResponseStream<GenerateContentResponse> generateContentStream(
46544656
*/
46554657
public ResponseStream<GenerateContentResponse> generateContentStream(
46564658
String model, String text, GenerateContentConfig config) {
4657-
return privateGenerateContentStream(
4659+
return generateContentStream(
46584660
model, Transformers.tContents(this.apiClient, (Object) text), config);
46594661
}
46604662

src/main/java/com/google/genai/Transformers.java

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,18 @@
2020
import com.fasterxml.jackson.databind.JsonNode;
2121
import com.fasterxml.jackson.databind.node.ArrayNode;
2222
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
23+
import com.fasterxml.jackson.databind.node.ObjectNode;
2324
import com.google.common.collect.ImmutableList;
2425
import com.google.genai.types.Blob;
2526
import com.google.genai.types.Content;
27+
import com.google.genai.types.FunctionDeclaration;
2628
import com.google.genai.types.Part;
2729
import com.google.genai.types.PrebuiltVoiceConfig;
2830
import com.google.genai.types.Schema;
2931
import com.google.genai.types.SpeechConfig;
3032
import com.google.genai.types.Tool;
3133
import com.google.genai.types.VoiceConfig;
34+
import java.lang.reflect.Method;
3235
import java.util.ArrayList;
3336
import java.util.List;
3437
import org.jspecify.annotations.Nullable;
@@ -171,7 +174,12 @@ public static List<Tool> tTools(ApiClient apiClient, Object origin) {
171174
if (origin == null) {
172175
return null;
173176
} else if (origin instanceof List) {
174-
return (List<Tool>) origin;
177+
List<Tool> tools = (List<Tool>) origin;
178+
List<Tool> transformedTools = new ArrayList<>();
179+
for (Tool tool : tools) {
180+
transformedTools.add(tTool(apiClient, tool));
181+
}
182+
return transformedTools;
175183
} else if (origin instanceof JsonNode) {
176184
return JsonSerializable.objectMapper.convertValue(
177185
(JsonNode) origin, new TypeReference<List<Tool>>() {});
@@ -185,10 +193,30 @@ public static Tool tTool(ApiClient apiClient, Object origin) {
185193
if (origin == null) {
186194
return null;
187195
} else if (origin instanceof Tool) {
188-
return (Tool) origin;
196+
Tool tool = (Tool) origin;
197+
if (!tool.functions().isPresent()) {
198+
return tool;
199+
}
200+
List<FunctionDeclaration> combinedFunctionDeclarations = new ArrayList<>();
201+
for (Method method : tool.functions().get()) {
202+
combinedFunctionDeclarations.add(FunctionDeclaration.fromMethod(method));
203+
}
204+
if (tool.functionDeclarations().isPresent()) {
205+
combinedFunctionDeclarations.addAll(tool.functionDeclarations().get());
206+
}
207+
ObjectNode toolNode = JsonSerializable.objectMapper.valueToTree(tool);
208+
toolNode.remove("functions");
209+
toolNode.set(
210+
"functionDeclarations",
211+
JsonSerializable.objectMapper.valueToTree(combinedFunctionDeclarations));
212+
return JsonSerializable.fromJsonNode(toolNode, Tool.class);
189213
} else if (origin instanceof JsonNode) {
190-
return JsonSerializable.objectMapper.convertValue(
191-
(JsonNode) origin, new TypeReference<Tool>() {});
214+
// in case reflectMethods is present in the json node, call tTool to parse it and remove it
215+
// from the json node.
216+
return tTool(
217+
apiClient,
218+
JsonSerializable.objectMapper.convertValue(
219+
(JsonNode) origin, new TypeReference<Tool>() {}));
192220
}
193221

194222
throw new IllegalArgumentException("Unsupported tool type: " + origin.getClass());

src/main/java/com/google/genai/types/Tool.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
package com.google.genai.types;
2020

2121
import com.fasterxml.jackson.annotation.JsonCreator;
22+
import com.fasterxml.jackson.annotation.JsonIgnore;
2223
import com.fasterxml.jackson.annotation.JsonProperty;
2324
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
2425
import com.google.auto.value.AutoValue;
2526
import com.google.genai.JsonSerializable;
27+
import java.lang.reflect.Method;
2628
import java.util.List;
2729
import java.util.Optional;
2830

@@ -62,6 +64,13 @@ public abstract class Tool extends JsonSerializable {
6264
@JsonProperty("googleMaps")
6365
public abstract Optional<GoogleMaps> googleMaps();
6466

67+
/**
68+
* The java.lang.reflect.Method instance. If provided, it will to be parsed into a list of
69+
* FunctionDeclaration instances, and be assigned to the functionDeclarations field.
70+
*/
71+
@JsonIgnore
72+
public abstract Optional<List<Method>> functions();
73+
6574
/**
6675
* Optional. CodeExecution tool type. Enables the model to execute code as part of generation.
6776
* This field is only used by the Gemini Developer API services.
@@ -111,6 +120,9 @@ private static Builder create() {
111120
@JsonProperty("googleMaps")
112121
public abstract Builder googleMaps(GoogleMaps googleMaps);
113122

123+
@JsonIgnore
124+
public abstract Builder functions(List<Method> functions);
125+
114126
@JsonProperty("codeExecution")
115127
public abstract Builder codeExecution(ToolCodeExecution codeExecution);
116128

0 commit comments

Comments
 (0)