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
5 changes: 5 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
Expand Down
16 changes: 16 additions & 0 deletions core/src/main/java/com/google/adk/agents/BaseAgent.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.google.adk.agents.Callbacks.BeforeAgentCallback;
import com.google.adk.events.Event;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.DoNotCall;
import com.google.genai.types.Content;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
Expand Down Expand Up @@ -359,4 +360,19 @@ public Flowable<Event> runLive(InvocationContext parentContext) {
* @return stream of agent-generated events.
*/
protected abstract Flowable<Event> runLiveImpl(InvocationContext invocationContext);

/**
* Creates a new agent instance from a configuration object.
*
* @param config Agent configuration.
* @param configAbsPath Absolute path to the configuration file.
* @return new agent instance.
*/
// TODO: Makes `BaseAgent.fromConfig` a final method and let sub-class to optionally override
// `_parse_config` to update kwargs if needed.
@DoNotCall("Always throws java.lang.UnsupportedOperationException")
public static BaseAgent fromConfig(BaseAgentConfig config, String configAbsPath) {
throw new UnsupportedOperationException(
"BaseAgent is abstract. Override fromConfig in concrete subclasses.");
}
}
14 changes: 13 additions & 1 deletion core/src/main/java/com/google/adk/agents/BaseAgentConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@
/**
* Base configuration for all agents.
*
* <p>workInProgress: Config agent features are not yet ready for public use.
* <p>TODO: Config agent features are not yet ready for public use.
*/
public class BaseAgentConfig {
private String name;
private String description = "";
// TODO: Add agentClassType enum to the config and handle different values from user
// input.e.g.LLM_AGENT, LlmAgent
private String agentClass = null;

@JsonProperty(value = "name", required = true)
public String name() {
Expand All @@ -44,4 +47,13 @@ public String description() {
public void setDescription(String description) {
this.description = description;
}

@JsonProperty("agent_class")
public String agentClass() {
return agentClass;
}

public void setAgentClass(String agentClass) {
this.agentClass = agentClass;
}
}
159 changes: 159 additions & 0 deletions core/src/main/java/com/google/adk/agents/ConfigAgentUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Copyright 2025 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.agents;

import static com.google.common.base.Strings.isNullOrEmpty;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Utility class for loading agent configurations from YAML files.
*
* <p>TODO: Config agent features are not yet ready for public use.
*/
public final class ConfigAgentUtils {

private static final Logger logger = LoggerFactory.getLogger(ConfigAgentUtils.class);

private ConfigAgentUtils() {}

/**
* Load agent from a YAML config file path.
*
* @param configPath the path to a YAML config file
* @return the created agent instance as a {@link BaseAgent}
* @throws ConfigurationException if loading fails
*/
public static BaseAgent fromConfig(String configPath) throws ConfigurationException {

File configFile = new File(configPath);
if (!configFile.exists()) {
logger.error("Config file not found: {}", configPath);
throw new ConfigurationException("Config file not found: " + configPath);
}

String absolutePath = configFile.getAbsolutePath();

try {
// Load the base config to determine the agent class
BaseAgentConfig baseConfig = loadConfigAsType(absolutePath, BaseAgentConfig.class);
Class<? extends BaseAgent> agentClass = resolveAgentClass(baseConfig.agentClass());

// Load the config file with the specific config class
Class<? extends BaseAgentConfig> configClass = getConfigClassForAgent(agentClass);
BaseAgentConfig config = loadConfigAsType(absolutePath, configClass);
logger.info("agentClass value = '{}'", config.agentClass());

// Use reflection to call the fromConfig method with the correct types
java.lang.reflect.Method fromConfigMethod =
agentClass.getDeclaredMethod("fromConfig", configClass, String.class);
return (BaseAgent) fromConfigMethod.invoke(null, config, absolutePath);

} catch (ConfigurationException e) {
throw e;
} catch (Exception e) {
throw new ConfigurationException("Failed to create agent from config: " + configPath, e);
}
}

/**
* Load configuration from a YAML file path as a specific type.
*
* @param configPath the absolute path to the config file
* @param configClass the class to deserialize the config into
* @return the loaded configuration
* @throws ConfigurationException if loading fails
*/
private static <T extends BaseAgentConfig> T loadConfigAsType(
String configPath, Class<T> configClass) throws ConfigurationException {
try (InputStream inputStream = new FileInputStream(configPath)) {
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper.readValue(inputStream, configClass);
} catch (IOException e) {
throw new ConfigurationException("Failed to load or parse config file: " + configPath, e);
}
}

/**
* Resolves the agent class based on the agent class name from the configuration.
*
* @param agentClassName the name of the agent class from the config
* @return the corresponding agent class
* @throws ConfigurationException if the agent class is not supported
*/
private static Class<? extends BaseAgent> resolveAgentClass(String agentClassName)
throws ConfigurationException {
// If no agent_class is specified in the yaml file, it will default to LlmAgent.
if (isNullOrEmpty(agentClassName) || agentClassName.equals("LlmAgent")) {
return LlmAgent.class;
}

// TODO: Support more agent classes
// Example for future extensions:
// if (agentClassName.equals("CustomAgent")) {
// return CustomAgent.class;
// }

throw new ConfigurationException(
"agentClass '"
+ agentClassName
+ "' is not supported. It must be a subclass of BaseAgent.");
}

/**
* Maps agent classes to their corresponding config classes.
*
* @param agentClass the agent class
* @return the corresponding config class
*/
private static Class<? extends BaseAgentConfig> getConfigClassForAgent(
Class<? extends BaseAgent> agentClass) {

if (agentClass == LlmAgent.class) {
return LlmAgentConfig.class;
}

// TODO: Add more agent class to config class mappings as needed
// Example:
// if (agentClass == CustomAgent.class) {
// return CustomAgentConfig.class;
// }

// Default fallback to BaseAgentConfig
return BaseAgentConfig.class;
}

/** Exception thrown when configuration is invalid. */
public static class ConfigurationException extends Exception {
public ConfigurationException(String message) {
super(message);
}

public ConfigurationException(String message, Throwable cause) {
super(message, cause);
}
}
}
94 changes: 94 additions & 0 deletions core/src/main/java/com/google/adk/agents/LlmAgent.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package com.google.adk.agents;

import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.base.Strings.nullToEmpty;
import static java.util.stream.Collectors.joining;

import com.fasterxml.jackson.core.JsonProcessingException;
Expand Down Expand Up @@ -45,6 +47,7 @@
import com.google.adk.flows.llmflows.BaseLlmFlow;
import com.google.adk.flows.llmflows.SingleFlow;
import com.google.adk.models.BaseLlm;
import com.google.adk.models.Gemini;
import com.google.adk.models.Model;
import com.google.adk.tools.BaseTool;
import com.google.adk.tools.BaseToolset;
Expand Down Expand Up @@ -845,4 +848,95 @@ private Model resolveModelInternal() {
}
throw new IllegalStateException("No model found for agent " + name() + " or its ancestors.");
}

/**
* Creates an LlmAgent from configuration.
*
* @param config the agent configuration
* @param configAbsPath The absolute path to the agent config file. This is needed for resolving
* relative paths for e.g. tools.
* @return the configured LlmAgent
* @throws ConfigAgentUtils.ConfigurationException if the configuration is invalid
* <p>TODO: Config agent features are not yet ready for public use.
*/
public static LlmAgent fromConfig(LlmAgentConfig config, String configAbsPath)
throws ConfigAgentUtils.ConfigurationException {
logger.debug("Creating LlmAgent from config: {}", config.name());

// Validate required fields
if (config.name() == null || config.name().trim().isEmpty()) {
throw new ConfigAgentUtils.ConfigurationException("Agent name is required");
}

if (config.instruction() == null || config.instruction().trim().isEmpty()) {
throw new ConfigAgentUtils.ConfigurationException("Agent instruction is required");
}

// Create builder with required fields
Builder builder =
LlmAgent.builder()
.name(config.name())
.description(nullToEmpty(config.description()))
.instruction(config.instruction());

// Set optional model configuration
if (config.model() != null && !config.model().trim().isEmpty()) {
logger.info("Configuring model: {}", config.model());

// TODO: resolve model name
if (config.model().startsWith("gemini")) {
try {
// Check for API key in system properties (for testing) or environment variables
String apiKey = System.getProperty("GOOGLE_API_KEY");
if (isNullOrEmpty(apiKey)) {
apiKey = System.getProperty("GEMINI_API_KEY");
}
if (isNullOrEmpty(apiKey)) {
apiKey = System.getenv("GOOGLE_API_KEY");
}
if (isNullOrEmpty(apiKey)) {
apiKey = System.getenv("GEMINI_API_KEY");
}

Gemini.Builder geminiBuilder = Gemini.builder().modelName(config.model());
if (apiKey != null && !apiKey.isEmpty()) {
geminiBuilder.apiKey(apiKey);
}

BaseLlm model = geminiBuilder.build();
builder.model(model);
logger.debug("Successfully configured Gemini model: {}", config.model());
} catch (RuntimeException e) {
logger.warn(
"Failed to create Gemini model '{}'. The agent will use the default LLM. Error: {}",
config.model(),
e.getMessage());
}
} else {
logger.warn(
"Model '{}' is not a supported Gemini model. The agent will use the default LLM.",
config.model());
}
}

// Set optional transfer configuration
if (config.disallowTransferToParent() != null) {
builder.disallowTransferToParent(config.disallowTransferToParent());
}

if (config.disallowTransferToPeers() != null) {
builder.disallowTransferToPeers(config.disallowTransferToPeers());
}

// Set optional output key
if (config.outputKey() != null && !config.outputKey().trim().isEmpty()) {
builder.outputKey(config.outputKey());
}

// Build and return the agent
LlmAgent agent = builder.build();
logger.info("Successfully created LlmAgent: {}", agent.name());

return agent;
}
}
7 changes: 6 additions & 1 deletion core/src/main/java/com/google/adk/agents/LlmAgentConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
/**
* Configuration for LlmAgent.
*
* <p>workInProgress: Config agent features are not yet ready for public use.
* <p>TODO: Config agent features are not yet ready for public use.
*/
public class LlmAgentConfig extends BaseAgentConfig {
private String model;
Expand All @@ -30,6 +30,11 @@ public class LlmAgentConfig extends BaseAgentConfig {
private Boolean disallowTransferToPeers;
private String outputKey;

public LlmAgentConfig() {
super();
setAgentClass("LlmAgent");
}

// Non-standard accessors with JsonProperty annotations
@JsonProperty("model")
public String model() {
Expand Down
Loading