context) {
+ return canonicalTools(context.orElse(null));
+ }
+
/**
* Constructs the list of tools for this agent based on the {@link #tools} field.
*
- * This method is only for use by Agent Development Kit.
+ * @return The resolved list of tools as a {@link Single} wrapped list of {@link BaseTool}.
+ */
+ public Flowable canonicalTools() {
+ return canonicalTools((ReadonlyContext) null);
+ }
+
+ /**
+ * Constructs the list of tools for this agent based on the {@link #tools} field.
*
* @param context The context to retrieve the session state.
* @return The resolved list of tools as a {@link Single} wrapped list of {@link BaseTool}.
*/
- public Flowable canonicalTools(Optional context) {
+ public Flowable canonicalTools(@Nullable ReadonlyContext context) {
List> toolFlowables = new ArrayList<>();
for (Object toolOrToolset : toolsUnion) {
if (toolOrToolset instanceof BaseTool baseTool) {
toolFlowables.add(Flowable.just(baseTool));
} else if (toolOrToolset instanceof BaseToolset baseToolset) {
- toolFlowables.add(baseToolset.getTools(context.orElse(null)));
+ toolFlowables.add(baseToolset.getTools(context));
} else {
throw new IllegalArgumentException(
"Object in tools list is not of a supported type: "
@@ -779,16 +794,6 @@ public Flowable canonicalTools(Optional context) {
return Flowable.concat(toolFlowables);
}
- /** Overload of canonicalTools that defaults to an empty context. */
- public Flowable canonicalTools() {
- return canonicalTools(Optional.empty());
- }
-
- /** Convenience overload of canonicalTools that accepts a non-optional ReadonlyContext. */
- public Flowable canonicalTools(ReadonlyContext context) {
- return canonicalTools(Optional.ofNullable(context));
- }
-
public Instruction instruction() {
return instruction;
}
@@ -965,7 +970,10 @@ private Model resolveModelInternal() {
Model currentModel = this.model.get();
if (currentModel.model().isPresent()) {
- return currentModel;
+ String modelName = currentModel.model().get().model();
+ BaseLlm resolvedLlm = currentModel.model().get();
+
+ return Model.builder().modelName(modelName).model(resolvedLlm).build();
}
if (currentModel.modelName().isPresent()) {
diff --git a/core/src/main/java/com/google/adk/artifacts/BaseArtifactService.java b/core/src/main/java/com/google/adk/artifacts/BaseArtifactService.java
index b6a3cee23..32ef9ff4d 100644
--- a/core/src/main/java/com/google/adk/artifacts/BaseArtifactService.java
+++ b/core/src/main/java/com/google/adk/artifacts/BaseArtifactService.java
@@ -55,22 +55,26 @@ Single saveArtifact(
default Single saveAndReloadArtifact(
String appName, String userId, String sessionId, String filename, Part artifact) {
return saveArtifact(appName, userId, sessionId, filename, artifact)
- .flatMap(
- version ->
- loadArtifact(appName, userId, sessionId, filename, Optional.of(version))
- .toSingle());
+ .flatMap(version -> loadArtifact(appName, userId, sessionId, filename, version).toSingle());
+ }
+
+ /** Loads the latest version of an artifact from the service. */
+ default Maybe loadArtifact(
+ String appName, String userId, String sessionId, String filename) {
+ return loadArtifact(appName, userId, sessionId, filename, Optional.empty());
+ }
+
+ /** Loads a specific version of an artifact from the service. */
+ default Maybe loadArtifact(
+ String appName, String userId, String sessionId, String filename, int version) {
+ return loadArtifact(appName, userId, sessionId, filename, Optional.of(version));
}
/**
- * Gets an artifact.
- *
- * @param appName the app name
- * @param userId the user ID
- * @param sessionId the session ID
- * @param filename the filename
- * @param version Optional version number. If null, loads the latest version.
- * @return the artifact or empty if not found
+ * @deprecated Use {@link #loadArtifact(String, String, String, String)} or {@link
+ * #loadArtifact(String, String, String, String, int)} instead.
*/
+ @Deprecated
Maybe loadArtifact(
String appName, String userId, String sessionId, String filename, Optional version);
diff --git a/core/src/main/java/com/google/adk/artifacts/InMemoryArtifactService.java b/core/src/main/java/com/google/adk/artifacts/InMemoryArtifactService.java
index 5808f7083..8c8ec2af8 100644
--- a/core/src/main/java/com/google/adk/artifacts/InMemoryArtifactService.java
+++ b/core/src/main/java/com/google/adk/artifacts/InMemoryArtifactService.java
@@ -129,10 +129,7 @@ public Single> listVersions(
public Single saveAndReloadArtifact(
String appName, String userId, String sessionId, String filename, Part artifact) {
return saveArtifact(appName, userId, sessionId, filename, artifact)
- .flatMap(
- version ->
- loadArtifact(appName, userId, sessionId, filename, Optional.of(version))
- .toSingle());
+ .flatMap(version -> loadArtifact(appName, userId, sessionId, filename, version).toSingle());
}
private Map> getArtifactsMap(String appName, String userId, String sessionId) {
diff --git a/core/src/main/java/com/google/adk/codeexecutors/ContainerCodeExecutor.java b/core/src/main/java/com/google/adk/codeexecutors/ContainerCodeExecutor.java
index 1d1202ead..4e75dab75 100644
--- a/core/src/main/java/com/google/adk/codeexecutors/ContainerCodeExecutor.java
+++ b/core/src/main/java/com/google/adk/codeexecutors/ContainerCodeExecutor.java
@@ -17,6 +17,8 @@
package com.google.adk.codeexecutors;
+import static java.util.Objects.requireNonNullElse;
+
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.model.Container;
@@ -32,7 +34,6 @@
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
-import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -41,37 +42,68 @@ 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";
- private final Optional baseUrl;
+ private final String baseUrl;
private final String image;
- private final Optional dockerPath;
+ private final String dockerPath;
private final DockerClient dockerClient;
private Container container;
/**
- * Initializes the ContainerCodeExecutor.
+ * Creates a ContainerCodeExecutor from an image.
+ *
+ * @param baseUrl The base url of the user hosted Docker client.
+ * @param image The tag of the predefined image or custom image to run on the container.
+ */
+ public static ContainerCodeExecutor fromImage(String baseUrl, String image) {
+ return new ContainerCodeExecutor(baseUrl, image, null);
+ }
+
+ /**
+ * Creates a ContainerCodeExecutor from an image.
+ *
+ * @param image The tag of the predefined image or custom image to run on the container.
+ */
+ public static ContainerCodeExecutor fromImage(String image) {
+ return new ContainerCodeExecutor(null, image, null);
+ }
+
+ /**
+ * Creates a ContainerCodeExecutor from a Dockerfile path.
+ *
+ * @param baseUrl The base url of the user hosted Docker client.
+ * @param dockerPath The path to the directory containing the Dockerfile.
+ */
+ public static ContainerCodeExecutor fromDockerPath(String baseUrl, String dockerPath) {
+ return new ContainerCodeExecutor(baseUrl, null, dockerPath);
+ }
+
+ /**
+ * Creates a ContainerCodeExecutor from a Dockerfile path.
+ *
+ * @param dockerPath The path to the directory containing the Dockerfile.
+ */
+ public static ContainerCodeExecutor fromDockerPath(String dockerPath) {
+ return new ContainerCodeExecutor(null, null, dockerPath);
+ }
+
+ /**
+ * Initializes the ContainerCodeExecutor. Either dockerPath or image must be set.
*
- * @param baseUrl Optional. The base url of the user hosted Docker client.
- * @param image The tag of the predefined image or custom image to run on the container. Either
- * dockerPath or image must be set.
- * @param dockerPath The path to the directory containing the Dockerfile. If set, build the image
- * from the dockerfile path instead of using the predefined image. Either dockerPath or image
- * must be set.
+ * @deprecated Use one of the static factory methods instead.
*/
- public ContainerCodeExecutor(
- Optional baseUrl, Optional image, Optional dockerPath) {
- if (image.isEmpty() && dockerPath.isEmpty()) {
+ @Deprecated
+ public ContainerCodeExecutor(String baseUrl, String image, String dockerPath) {
+ if (image == null && dockerPath == null) {
throw new IllegalArgumentException(
"Either image or dockerPath must be set for ContainerCodeExecutor.");
}
this.baseUrl = baseUrl;
- this.image = image.orElse(DEFAULT_IMAGE_TAG);
- this.dockerPath = dockerPath.map(p -> Paths.get(p).toAbsolutePath().toString());
+ this.image = requireNonNullElse(image, DEFAULT_IMAGE_TAG);
+ this.dockerPath = dockerPath == null ? null : Paths.get(dockerPath).toAbsolutePath().toString();
- if (baseUrl.isPresent()) {
+ if (baseUrl != null) {
var config =
- DefaultDockerClientConfig.createDefaultConfigBuilder()
- .withDockerHost(baseUrl.get())
- .build();
+ DefaultDockerClientConfig.createDefaultConfigBuilder().withDockerHost(baseUrl).build();
this.dockerClient = DockerClientBuilder.getInstance(config).build();
} else {
this.dockerClient = DockerClientBuilder.getInstance().build();
@@ -121,12 +153,12 @@ public CodeExecutionResult executeCode(
}
private void buildDockerImage() {
- if (dockerPath.isEmpty()) {
+ if (dockerPath == null) {
throw new IllegalStateException("Docker path is not set.");
}
- File dockerfile = new File(dockerPath.get());
+ File dockerfile = new File(dockerPath);
if (!dockerfile.exists()) {
- throw new UncheckedIOException(new IOException("Invalid Docker path: " + dockerPath.get()));
+ throw new UncheckedIOException(new IOException("Invalid Docker path: " + dockerPath));
}
logger.info("Building Docker image...");
@@ -158,7 +190,7 @@ private void initContainer() {
if (dockerClient == null) {
throw new IllegalStateException("Docker client is not initialized.");
}
- if (dockerPath.isPresent()) {
+ if (dockerPath != null) {
buildDockerImage();
} else {
// If a dockerPath is not provided, always pull the image to ensure it's up-to-date.
diff --git a/core/src/main/java/com/google/adk/events/EventActions.java b/core/src/main/java/com/google/adk/events/EventActions.java
index 6d8c698dd..bf25acfc7 100644
--- a/core/src/main/java/com/google/adk/events/EventActions.java
+++ b/core/src/main/java/com/google/adk/events/EventActions.java
@@ -22,6 +22,7 @@
import com.google.adk.sessions.State;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.HashSet;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@@ -383,7 +384,7 @@ public Builder compaction(EventCompaction value) {
@CanIgnoreReturnValue
public Builder merge(EventActions other) {
other.skipSummarization().ifPresent(this::skipSummarization);
- this.stateDelta.putAll(other.stateDelta());
+ other.stateDelta().forEach((key, value) -> stateDelta.merge(key, value, Builder::deepMerge));
this.artifactDelta.putAll(other.artifactDelta());
this.deletedArtifactIds.addAll(other.deletedArtifactIds());
other.transferToAgent().ifPresent(this::transferToAgent);
@@ -395,6 +396,34 @@ public Builder merge(EventActions other) {
return this;
}
+ private static Object deepMerge(Object target, Object source) {
+ if (!(target instanceof Map) || !(source instanceof Map)) {
+ // If one of them is not a map, the source value overwrites the target.
+ return source;
+ }
+
+ Map, ?> targetMap = (Map, ?>) target;
+ Map, ?> sourceMap = (Map, ?>) source;
+
+ if (!targetMap.isEmpty() && !sourceMap.isEmpty()) {
+ Object targetKey = targetMap.keySet().iterator().next();
+ Object sourceKey = sourceMap.keySet().iterator().next();
+ if (targetKey != null
+ && sourceKey != null
+ && !targetKey.getClass().equals(sourceKey.getClass())) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Cannot merge maps with different key types: %s vs %s",
+ targetKey.getClass().getName(), sourceKey.getClass().getName()));
+ }
+ }
+
+ // Create a new map to prevent UnsupportedOperationException from immutable maps
+ Map