From 1a3dd612217a05e2f8fff69720087ed1136a09ab Mon Sep 17 00:00:00 2001 From: Damian Momot Date: Wed, 15 Apr 2026 01:02:18 -0700 Subject: [PATCH] fix: Allow BuiltInCodeExecutor for Gemini 3 models PiperOrigin-RevId: 900003448 --- .../codeexecutors/BuiltInCodeExecutor.java | 2 +- .../com/google/adk/utils/ModelNameUtils.java | 25 +++++++ .../BuiltInCodeExecutorTest.java | 73 +++++++++++++++++++ .../google/adk/utils/ModelNameUtilsTest.java | 62 ++++++++++++++++ 4 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 core/src/test/java/com/google/adk/codeexecutors/BuiltInCodeExecutorTest.java diff --git a/core/src/main/java/com/google/adk/codeexecutors/BuiltInCodeExecutor.java b/core/src/main/java/com/google/adk/codeexecutors/BuiltInCodeExecutor.java index 972082dde..ef9078e4d 100644 --- a/core/src/main/java/com/google/adk/codeexecutors/BuiltInCodeExecutor.java +++ b/core/src/main/java/com/google/adk/codeexecutors/BuiltInCodeExecutor.java @@ -43,7 +43,7 @@ public CodeExecutionResult executeCode( /** Pre-process the LLM request for Gemini 2.0+ models to use the code execution tool. */ public void processLlmRequest(LlmRequest.Builder llmRequestBuilder) { LlmRequest llmRequest = llmRequestBuilder.build(); - if (ModelNameUtils.isGemini2Model(llmRequest.model().orElse(null))) { + if (llmRequest.model().map(ModelNameUtils::isGemini2OrAbove).orElse(false)) { GenerateContentConfig.Builder configBuilder = llmRequest.config().map(c -> c.toBuilder()).orElseGet(GenerateContentConfig::builder); ImmutableList.Builder toolsBuilder = ImmutableList.builder(); diff --git a/core/src/main/java/com/google/adk/utils/ModelNameUtils.java b/core/src/main/java/com/google/adk/utils/ModelNameUtils.java index cf0f2221e..56fd6dd95 100644 --- a/core/src/main/java/com/google/adk/utils/ModelNameUtils.java +++ b/core/src/main/java/com/google/adk/utils/ModelNameUtils.java @@ -20,11 +20,14 @@ import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.jspecify.annotations.Nullable; /** Utility class for model names. */ public final class ModelNameUtils { private static final String GEMINI_PREFIX = "gemini-"; private static final Pattern GEMINI_2_PATTERN = Pattern.compile("^gemini-2\\..*"); + private static final Pattern GEMINI_VERSION_PATTERN = + Pattern.compile("^gemini-(\\d+)(?:\\.(\\d+))?.*"); private static final String GEMINI_CLASS = "com.google.adk.models.Gemini"; private static final Pattern PATH_PATTERN = Pattern.compile("^projects/[^/]+/locations/[^/]+/publishers/[^/]+/models/(.+)$"); @@ -39,6 +42,28 @@ public static boolean isGemini2Model(String modelString) { return matchesModelPattern(modelString, GEMINI_2_PATTERN); } + public static boolean isGemini2OrAbove(@Nullable String modelString) { + return isGeminiVersionOrAbove(modelString, 2, 0); + } + + private static boolean isGeminiVersionOrAbove( + @Nullable String modelString, int minMajor, int minMinor) { + if (modelString == null) { + return false; + } + String modelName = extractModelName(modelString); + Matcher matcher = GEMINI_VERSION_PATTERN.matcher(modelName); + if (matcher.matches()) { + int major = Integer.parseInt(matcher.group(1)); + int minor = matcher.group(2) != null ? Integer.parseInt(matcher.group(2)) : 0; + if (major > minMajor) { + return true; + } + return major == minMajor && minor >= minMinor; + } + return false; + } + private static boolean matchesModelPattern(String modelString, Pattern pattern) { if (modelString == null) { return false; diff --git a/core/src/test/java/com/google/adk/codeexecutors/BuiltInCodeExecutorTest.java b/core/src/test/java/com/google/adk/codeexecutors/BuiltInCodeExecutorTest.java new file mode 100644 index 000000000..e3e4c660c --- /dev/null +++ b/core/src/test/java/com/google/adk/codeexecutors/BuiltInCodeExecutorTest.java @@ -0,0 +1,73 @@ +package com.google.adk.codeexecutors; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.adk.models.LlmRequest; +import com.google.genai.types.Tool; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class BuiltInCodeExecutorTest { + + @Test + public void executeCode_throwsUnsupportedOperationException() { + BuiltInCodeExecutor executor = new BuiltInCodeExecutor(); + assertThrows(UnsupportedOperationException.class, () -> executor.executeCode(null, null)); + } + + @Test + public void processLlmRequest_withGemini2_addsCodeExecutionTool() { + BuiltInCodeExecutor executor = new BuiltInCodeExecutor(); + LlmRequest.Builder requestBuilder = LlmRequest.builder().model("gemini-2.5-flash"); + + executor.processLlmRequest(requestBuilder); + + List tools = requestBuilder.build().config().get().tools().get(); + assertThat(tools).hasSize(1); + assertThat(tools.get(0).codeExecution()).isPresent(); + } + + @Test + public void processLlmRequest_withGemini3_addsCodeExecutionTool() { + BuiltInCodeExecutor executor = new BuiltInCodeExecutor(); + LlmRequest.Builder requestBuilder = LlmRequest.builder().model("gemini-3.0-pro"); + + executor.processLlmRequest(requestBuilder); + + List tools = requestBuilder.build().config().get().tools().get(); + assertThat(tools).hasSize(1); + assertThat(tools.get(0).codeExecution()).isPresent(); + } + + @Test + public void processLlmRequest_withGemini1_throwsException() { + BuiltInCodeExecutor executor = new BuiltInCodeExecutor(); + LlmRequest.Builder requestBuilder = LlmRequest.builder().model("gemini-1.5-pro"); + + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> executor.processLlmRequest(requestBuilder)); + + assertThat(exception) + .hasMessageThat() + .contains("Gemini code execution tool is not supported for model gemini-1.5-pro"); + } + + @Test + public void processLlmRequest_withoutModel_throwsException() { + BuiltInCodeExecutor executor = new BuiltInCodeExecutor(); + LlmRequest.Builder requestBuilder = LlmRequest.builder(); + + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, () -> executor.processLlmRequest(requestBuilder)); + + assertThat(exception) + .hasMessageThat() + .contains("Gemini code execution tool is not supported for model"); + } +} diff --git a/core/src/test/java/com/google/adk/utils/ModelNameUtilsTest.java b/core/src/test/java/com/google/adk/utils/ModelNameUtilsTest.java index 20dda7034..86bf126f6 100644 --- a/core/src/test/java/com/google/adk/utils/ModelNameUtilsTest.java +++ b/core/src/test/java/com/google/adk/utils/ModelNameUtilsTest.java @@ -71,6 +71,68 @@ public void isGemini2Model_withNullModel_returnsFalse() { assertThat(ModelNameUtils.isGemini2Model(null)).isFalse(); } + @Test + public void isGemini2OrAbove_withGemini3Model_returnsTrue() { + assertThat(ModelNameUtils.isGemini2OrAbove("gemini-3.0-pro")).isTrue(); + } + + @Test + public void isGemini2OrAbove_withGemini2Model_returnsTrue() { + assertThat(ModelNameUtils.isGemini2OrAbove("gemini-2.0-pro")).isTrue(); + } + + @Test + public void isGemini2OrAbove_withGemini25Model_returnsTrue() { + assertThat(ModelNameUtils.isGemini2OrAbove("gemini-2.5-flash")).isTrue(); + } + + @Test + public void isGemini2OrAbove_withGemini1Model_returnsFalse() { + assertThat(ModelNameUtils.isGemini2OrAbove("gemini-1.5-pro")).isFalse(); + } + + @Test + public void isGemini2OrAbove_withInvalid_returnsFalse() { + assertThat(ModelNameUtils.isGemini2OrAbove("???")).isFalse(); + } + + @Test + public void isGemini2OrAbove_withInvalidGemini1Version_returnsFalse() { + assertThat(ModelNameUtils.isGemini2OrAbove("gemini-01")).isFalse(); + } + + @Test + public void isGemini2OrAbove_withPathBasedGemini3Model_returnsTrue() { + assertThat( + ModelNameUtils.isGemini2OrAbove( + "projects/test-project/locations/us-central1/publishers/google/models/gemini-3.0-flash")) + .isTrue(); + } + + @Test + public void isGemini2OrAbove_withPathBasedGemini1Model_returnsFalse() { + assertThat( + ModelNameUtils.isGemini2OrAbove( + "projects/test-project/locations/us-central1/publishers/google/models/gemini-1.5-pro")) + .isFalse(); + } + + @Test + public void isGemini2OrAbove_withApigeeGemini3Model_returnsTrue() { + assertThat(ModelNameUtils.isGemini2OrAbove("apigee/gemini-3.0-flash")).isTrue(); + } + + @Test + public void isGemini2OrAbove_withApigeeProviderV1BetaGemini3Model_returnsTrue() { + assertThat(ModelNameUtils.isGemini2OrAbove("apigee/vertex_ai/v1beta/gemini-3.0-flash")) + .isTrue(); + } + + @Test + public void isGemini2OrAbove_withNullModel_returnsFalse() { + assertThat(ModelNameUtils.isGemini2OrAbove(null)).isFalse(); + } + @Test public void isGeminiModel_withGeminiModel_returnsTrue() { assertThat(ModelNameUtils.isGeminiModel("gemini-1.5-flash")).isTrue();