Skip to content
Open
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 @@ -61,19 +61,13 @@ public int errorRetryAttempts() {
}

/**
* The list of the enclosing delimiters to identify the code blocks.
* The list of enclosing delimiters to identify code blocks in model responses.
*
* <p>Each inner list contains a pair of start and end delimiters. This supports multiple pairs of
* delimiters.
* <p>Each inner list should contain a pair of strings: the start delimiter and the end delimiter.
* This structure supports multiple pairs of delimiters. For example, `[["```tool_code\n",
* "\n```"], ["```python\n", "\n```"]]` defines two sets of delimiters.
*
* <p>For example, the delimiter ('```python\n', '\n```') can be used to identify code blocks with
* the following format:
*
* <p>```python
*
* <p>print("hello")
*
* <p>```
* <p>Defaults to `[["```tool_code\n", "\n```"], ["```python\n", "\n```"]]`.
*/
public ImmutableList<ImmutableList<String>> codeBlockDelimiters() {
return CODE_BLOCK_DELIMITERS;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.util.stream.Collectors.joining;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.google.adk.JsonBaseModel;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
Expand Down Expand Up @@ -211,7 +213,13 @@ public static Builder builder() {

/** Builder for {@link CodeExecutionResult}. */
@AutoValue.Builder
@JsonPOJOBuilder(withPrefix = "")
public abstract static class Builder {
@JsonCreator
static Builder create() {
return CodeExecutionResult.builder();
}

public abstract Builder stdout(String stdout);

public abstract Builder stderr(String stderr);
Expand Down Expand Up @@ -243,7 +251,13 @@ public static Builder builder() {

/** Builder for {@link CodeExecutionInput}. */
@AutoValue.Builder
@JsonPOJOBuilder(withPrefix = "")
public abstract static class Builder {
@JsonCreator
static Builder create() {
return CodeExecutionInput.builder();
}

public abstract Builder code(String code);

public abstract Builder inputFiles(List<File> inputFiles);
Expand Down Expand Up @@ -273,7 +287,13 @@ public static Builder builder() {

/** Builder for {@link File}. */
@AutoValue.Builder
@JsonPOJOBuilder(withPrefix = "")
public abstract static class Builder {
@JsonCreator
static Builder create() {
return File.builder();
}

public abstract Builder name(String name);

public abstract Builder content(String content);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.adk.JsonBaseModel;
import com.google.adk.codeexecutors.CodeExecutionUtils.File;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.time.InstantSource;
import java.util.ArrayList;
Expand Down Expand Up @@ -88,9 +89,10 @@ public void setExecutionId(String sessionId) {
*
* @return A list of processed file names in the code executor context.
*/
public List<String> getProcessedFileNames() {
return (List<String>)
this.context.computeIfAbsent(PROCESSED_FILE_NAMES_KEY, unused -> new ArrayList<>());
public ImmutableList<String> getProcessedFileNames() {
return ImmutableList.copyOf(
(List<String>)
this.context.computeIfAbsent(PROCESSED_FILE_NAMES_KEY, unused -> new ArrayList<>()));
}

/**
Expand All @@ -110,7 +112,7 @@ public void addProcessedFileNames(List<String> fileNames) {
*
* @return A list of input files in the code executor context.
*/
public List<File> getInputFiles() {
public ImmutableList<File> getInputFiles() {
List<Map<String, Object>> fileMaps =
(List<Map<String, Object>>)
this.sessionState.getOrDefault(INPUT_FILE_KEY, new ArrayList<>());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** A code executor that uses a custom container to execute code. */
/**
* A code executor that uses a custom container to execute code.
*
* <p>This implementation uses blocking I/O to interact with Docker via {@code docker-java} library.
*/
public class ContainerCodeExecutor extends BaseCodeExecutor {
private static final Logger logger = LoggerFactory.getLogger(ContainerCodeExecutor.class);
private static final String DEFAULT_IMAGE_TAG = "adk-code-executor:latest";
Expand Down Expand Up @@ -91,6 +95,11 @@ public boolean optimizeDataFile() {
return false;
}

/**
* {@inheritDoc}
*
* <p>This method blocks waiting for container execution to complete.
*/
@Override
public CodeExecutionResult executeCode(
InvocationContext invocationContext, CodeExecutionInput codeExecutionInput) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.adk.codeexecutors;

import static com.google.common.truth.Truth.assertThat;

import com.google.adk.agents.InvocationContext;
import com.google.adk.codeexecutors.CodeExecutionUtils.CodeExecutionInput;
import com.google.adk.codeexecutors.CodeExecutionUtils.CodeExecutionResult;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public class BaseCodeExecutorTest {

private static class TestCodeExecutor extends BaseCodeExecutor {
@Override
public CodeExecutionResult executeCode(
InvocationContext invocationContext, CodeExecutionInput codeExecutionInput) {
return null;
}
}

@Test
public void baseCodeExecutor_defaultValues() {
TestCodeExecutor codeExecutor = new TestCodeExecutor();
assertThat(codeExecutor.optimizeDataFile()).isFalse();
assertThat(codeExecutor.stateful()).isFalse();
assertThat(codeExecutor.errorRetryAttempts()).isEqualTo(2);
assertThat(codeExecutor.codeBlockDelimiters())
.isEqualTo(
ImmutableList.of(
ImmutableList.of("```tool_code\n", "\n```"),
ImmutableList.of("```python\n", "\n```")));
assertThat(codeExecutor.executionResultDelimiters())
.isEqualTo(ImmutableList.of("```tool_output\n", "\n```"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.adk.codeexecutors;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;

import com.google.adk.agents.InvocationContext;
import com.google.adk.codeexecutors.CodeExecutionUtils.CodeExecutionInput;
import com.google.adk.models.LlmRequest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public final class BuiltInCodeExecutorTest {

private BuiltInCodeExecutor codeExecutor;

@Before
public void setUp() {
codeExecutor = new BuiltInCodeExecutor();
}

@Test
public void executeCode_throwsUnsupportedOperationException() {
assertThrows(
UnsupportedOperationException.class,
() ->
codeExecutor.executeCode(
InvocationContext.builder().build(),
CodeExecutionInput.builder().code("code").build()));
}

@Test
public void processLlmRequest_gemini2Model_addsCodeExecutionTool() {
LlmRequest.Builder llmRequestBuilder = LlmRequest.builder().model("gemini-2.0-flash-exp");
codeExecutor.processLlmRequest(llmRequestBuilder);
LlmRequest llmRequest = llmRequestBuilder.build();
assertThat(llmRequest.config()).isPresent();
assertThat(llmRequest.config().get().tools()).isPresent();
assertThat(llmRequest.config().get().tools().get()).hasSize(1);
assertThat(llmRequest.config().get().tools().get().get(0).codeExecution()).isPresent();
}

@Test
public void processLlmRequest_nonGemini2Model_throwsIllegalArgumentException() {
LlmRequest.Builder llmRequestBuilder = LlmRequest.builder().model("gemini-1.5-pro");
assertThrows(
IllegalArgumentException.class, () -> codeExecutor.processLlmRequest(llmRequestBuilder));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.adk.codeexecutors;

import static com.google.common.truth.Truth8.assertThat;

import com.google.adk.codeexecutors.CodeExecutionUtils.CodeExecutionResult;
import com.google.common.collect.ImmutableList;
import com.google.genai.types.CodeExecutionResult.Outcome;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import java.util.Optional;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public final class CodeExecutionUtilsTest {

@Test
public void buildCodeExecutionResultPart_success() {
CodeExecutionResult result = CodeExecutionResult.builder().stdout("output").build();
Part part = CodeExecutionUtils.buildCodeExecutionResultPart(result);
assertThat(part.codeExecutionResult()).isPresent();
assertThat(part.codeExecutionResult().get().outcome()).hasValue(Outcome.OK);
assertThat(part.codeExecutionResult().get().output())
.hasValue("Code execution result:\noutput\n");
}

@Test
public void buildCodeExecutionResultPart_failure() {
CodeExecutionResult result = CodeExecutionResult.builder().stderr("error").build();
Part part = CodeExecutionUtils.buildCodeExecutionResultPart(result);
assertThat(part.codeExecutionResult()).isPresent();
assertThat(part.codeExecutionResult().get().outcome()).hasValue(Outcome.FAILED);
assertThat(part.codeExecutionResult().get().output()).hasValue("error");
}

@Test
public void buildExecutableCodePart_success() {
Part part = CodeExecutionUtils.buildExecutableCodePart("code");
assertThat(part.executableCode()).isPresent();
assertThat(part.executableCode().get().code()).hasValue("code");
}

@Test
public void convertCodeExecutionParts_executableCode() {
Content content =
Content.builder()
.parts(ImmutableList.of(CodeExecutionUtils.buildExecutableCodePart("code")))
.role("model")
.build();
Content newContent =
CodeExecutionUtils.convertCodeExecutionParts(
content, ImmutableList.of("```", "```"), ImmutableList.of());
assertThat(newContent.parts().get()).hasSize(1);
assertThat(newContent.parts().get().get(0).text()).hasValue("```code```");
}

@Test
public void convertCodeExecutionParts_codeExecutionResult() {
Content content =
Content.builder()
.parts(
ImmutableList.of(
CodeExecutionUtils.buildCodeExecutionResultPart(
CodeExecutionResult.builder().stdout("output").build())))
.role("model")
.build();
Content newContent =
CodeExecutionUtils.convertCodeExecutionParts(
content, ImmutableList.of(), ImmutableList.of("'''", "'''"));
assertThat(newContent.parts().get()).hasSize(1);
assertThat(newContent.parts().get().get(0).text().get()).contains("'''");
assertThat(newContent.parts().get().get(0).text().get()).contains("output");
}

@Test
public void extractCodeAndTruncateContent_executableCode() {
Content.Builder contentBuilder =
Content.builder()
.parts(ImmutableList.of(CodeExecutionUtils.buildExecutableCodePart("code")))
.role("model");
Optional<String> code =
CodeExecutionUtils.extractCodeAndTruncateContent(
contentBuilder, ImmutableList.of(ImmutableList.of("```", "```")));
assertThat(code).hasValue("code");
assertThat(contentBuilder.build().parts().get()).hasSize(1);
}

@Test
public void extractCodeAndTruncateContent_text() {
Content.Builder contentBuilder =
Content.builder().parts(ImmutableList.of(Part.fromText("```code```"))).role("model");
Optional<String> code =
CodeExecutionUtils.extractCodeAndTruncateContent(
contentBuilder, ImmutableList.of(ImmutableList.of("```", "```")));
assertThat(code).hasValue("code");
assertThat(contentBuilder.build().parts().get()).hasSize(1);
assertThat(contentBuilder.build().parts().get().get(0).executableCode()).isPresent();
}
}
Loading
Loading