Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<Tool> toolsBuilder = ImmutableList.<Tool>builder();
Expand Down
25 changes: 25 additions & 0 deletions core/src/main/java/com/google/adk/utils/ModelNameUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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/(.+)$");
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Tool> 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<Tool> 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");
}
}
62 changes: 62 additions & 0 deletions core/src/test/java/com/google/adk/utils/ModelNameUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading