Skip to content

Commit 067dce3

Browse files
google-genai-botcopybara-github
authored andcommitted
fix: Enable built-in code execution for Gemini 3 models
PiperOrigin-RevId: 897673367
1 parent 14027d1 commit 067dce3

4 files changed

Lines changed: 161 additions & 1 deletion

File tree

core/src/main/java/com/google/adk/codeexecutors/BuiltInCodeExecutor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public CodeExecutionResult executeCode(
4343
/** Pre-process the LLM request for Gemini 2.0+ models to use the code execution tool. */
4444
public void processLlmRequest(LlmRequest.Builder llmRequestBuilder) {
4545
LlmRequest llmRequest = llmRequestBuilder.build();
46-
if (ModelNameUtils.isGemini2Model(llmRequest.model().orElse(null))) {
46+
if (llmRequest.model().map(ModelNameUtils::isGemini2OrAbove).orElse(false)) {
4747
GenerateContentConfig.Builder configBuilder =
4848
llmRequest.config().map(c -> c.toBuilder()).orElseGet(GenerateContentConfig::builder);
4949
ImmutableList.Builder<Tool> toolsBuilder = ImmutableList.<Tool>builder();

core/src/main/java/com/google/adk/utils/ModelNameUtils.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
public final class ModelNameUtils {
2626
private static final String GEMINI_PREFIX = "gemini-";
2727
private static final Pattern GEMINI_2_PATTERN = Pattern.compile("^gemini-2\\..*");
28+
private static final Pattern GEMINI_VERSION_PATTERN =
29+
Pattern.compile("^gemini-(\\d+)(?:\\.(\\d+))?.*");
2830
private static final String GEMINI_CLASS = "com.google.adk.models.Gemini";
2931
private static final Pattern PATH_PATTERN =
3032
Pattern.compile("^projects/[^/]+/locations/[^/]+/publishers/[^/]+/models/(.+)$");
@@ -39,6 +41,31 @@ public static boolean isGemini2Model(String modelString) {
3941
return matchesModelPattern(modelString, GEMINI_2_PATTERN);
4042
}
4143

44+
public static boolean isGemini2OrAbove(String modelString) {
45+
return isGeminiVersionOrAbove(modelString, 2, 0);
46+
}
47+
48+
private static boolean isGeminiVersionOrAbove(String modelString, int minMajor, int minMinor) {
49+
if (modelString == null) {
50+
return false;
51+
}
52+
String modelName = extractModelName(modelString);
53+
Matcher matcher = GEMINI_VERSION_PATTERN.matcher(modelName);
54+
if (matcher.matches()) {
55+
try {
56+
int major = Integer.parseInt(matcher.group(1));
57+
int minor = matcher.group(2) != null ? Integer.parseInt(matcher.group(2)) : 0;
58+
if (major > minMajor) {
59+
return true;
60+
}
61+
return major == minMajor && minor >= minMinor;
62+
} catch (NumberFormatException e) {
63+
return false;
64+
}
65+
}
66+
return false;
67+
}
68+
4269
private static boolean matchesModelPattern(String modelString, Pattern pattern) {
4370
if (modelString == null) {
4471
return false;
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.google.adk.codeexecutors;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
import static org.junit.Assert.assertThrows;
5+
6+
import com.google.adk.models.LlmRequest;
7+
import com.google.genai.types.GenerateContentConfig;
8+
import com.google.genai.types.Tool;
9+
import java.util.List;
10+
import org.junit.Test;
11+
import org.junit.runner.RunWith;
12+
import org.junit.runners.JUnit4;
13+
14+
@RunWith(JUnit4.class)
15+
public class BuiltInCodeExecutorTest {
16+
17+
@Test
18+
public void executeCode_throwsUnsupportedOperationException() {
19+
BuiltInCodeExecutor executor = new BuiltInCodeExecutor();
20+
assertThrows(UnsupportedOperationException.class, () -> executor.executeCode(null, null));
21+
}
22+
23+
@Test
24+
public void processLlmRequest_withGemini2_addsCodeExecutionTool() {
25+
BuiltInCodeExecutor executor = new BuiltInCodeExecutor();
26+
LlmRequest.Builder requestBuilder = LlmRequest.builder().model("gemini-2.5-flash");
27+
28+
executor.processLlmRequest(requestBuilder);
29+
30+
LlmRequest modifiedRequest = requestBuilder.build();
31+
assertThat(modifiedRequest.config()).isPresent();
32+
33+
GenerateContentConfig config = modifiedRequest.config().get();
34+
assertThat(config.tools()).isPresent();
35+
36+
List<Tool> tools = config.tools().get();
37+
assertThat(tools).hasSize(1);
38+
assertThat(tools.get(0).codeExecution()).isPresent();
39+
}
40+
41+
@Test
42+
public void processLlmRequest_withGemini3_addsCodeExecutionTool() {
43+
BuiltInCodeExecutor executor = new BuiltInCodeExecutor();
44+
LlmRequest.Builder requestBuilder = LlmRequest.builder().model("gemini-3.0-pro");
45+
46+
executor.processLlmRequest(requestBuilder);
47+
48+
LlmRequest modifiedRequest = requestBuilder.build();
49+
assertThat(modifiedRequest.config()).isPresent();
50+
51+
GenerateContentConfig config = modifiedRequest.config().get();
52+
assertThat(config.tools()).isPresent();
53+
54+
List<Tool> tools = config.tools().get();
55+
assertThat(tools).hasSize(1);
56+
assertThat(tools.get(0).codeExecution()).isPresent();
57+
}
58+
59+
@Test
60+
public void processLlmRequest_withGemini1_throwsException() {
61+
BuiltInCodeExecutor executor = new BuiltInCodeExecutor();
62+
LlmRequest.Builder requestBuilder = LlmRequest.builder().model("gemini-1.5-pro");
63+
64+
IllegalArgumentException exception =
65+
assertThrows(
66+
IllegalArgumentException.class, () -> executor.processLlmRequest(requestBuilder));
67+
68+
assertThat(exception)
69+
.hasMessageThat()
70+
.contains("Gemini code execution tool is not supported for model gemini-1.5-pro");
71+
}
72+
73+
@Test
74+
public void processLlmRequest_withoutModel_throwsException() {
75+
BuiltInCodeExecutor executor = new BuiltInCodeExecutor();
76+
LlmRequest.Builder requestBuilder = LlmRequest.builder();
77+
78+
IllegalArgumentException exception =
79+
assertThrows(
80+
IllegalArgumentException.class, () -> executor.processLlmRequest(requestBuilder));
81+
82+
assertThat(exception)
83+
.hasMessageThat()
84+
.contains("Gemini code execution tool is not supported for model");
85+
}
86+
}

core/src/test/java/com/google/adk/utils/ModelNameUtilsTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,53 @@ public void isGemini2Model_withNullModel_returnsFalse() {
7171
assertThat(ModelNameUtils.isGemini2Model(null)).isFalse();
7272
}
7373

74+
@Test
75+
public void isGemini2OrAbove_withGemini3Model_returnsTrue() {
76+
assertThat(ModelNameUtils.isGemini2OrAbove("gemini-3.0-pro")).isTrue();
77+
}
78+
79+
@Test
80+
public void isGemini2OrAbove_withGemini2Model_returnsTrue() {
81+
assertThat(ModelNameUtils.isGemini2OrAbove("gemini-2.5-flash")).isTrue();
82+
}
83+
84+
@Test
85+
public void isGemini2OrAbove_withGemini1Model_returnsFalse() {
86+
assertThat(ModelNameUtils.isGemini2OrAbove("gemini-1.5-pro")).isFalse();
87+
}
88+
89+
@Test
90+
public void isGemini2OrAbove_withPathBasedGemini3Model_returnsTrue() {
91+
assertThat(
92+
ModelNameUtils.isGemini2OrAbove(
93+
"projects/test-project/locations/us-central1/publishers/google/models/gemini-3.0-flash"))
94+
.isTrue();
95+
}
96+
97+
@Test
98+
public void isGemini2OrAbove_withPathBasedGemini1Model_returnsFalse() {
99+
assertThat(
100+
ModelNameUtils.isGemini2OrAbove(
101+
"projects/test-project/locations/us-central1/publishers/google/models/gemini-1.5-pro"))
102+
.isFalse();
103+
}
104+
105+
@Test
106+
public void isGemini2OrAbove_withApigeeGemini3Model_returnsTrue() {
107+
assertThat(ModelNameUtils.isGemini2OrAbove("apigee/gemini-3.0-flash")).isTrue();
108+
}
109+
110+
@Test
111+
public void isGemini2OrAbove_withApigeeProviderV1BetaGemini3Model_returnsTrue() {
112+
assertThat(ModelNameUtils.isGemini2OrAbove("apigee/vertex_ai/v1beta/gemini-3.0-flash"))
113+
.isTrue();
114+
}
115+
116+
@Test
117+
public void isGemini2OrAbove_withNullModel_returnsFalse() {
118+
assertThat(ModelNameUtils.isGemini2OrAbove(null)).isFalse();
119+
}
120+
74121
@Test
75122
public void isGeminiModel_withGeminiModel_returnsTrue() {
76123
assertThat(ModelNameUtils.isGeminiModel("gemini-1.5-flash")).isTrue();

0 commit comments

Comments
 (0)