Skip to content

feat(config): support ADK built-in and custom tools in config #2146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 27, 2025
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
9 changes: 5 additions & 4 deletions src/google/adk/agents/base_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from ..utils.feature_decorator import working_in_progress
from .base_agent_config import BaseAgentConfig
from .callback_context import CallbackContext
from .common_configs import AgentRefConfig

if TYPE_CHECKING:
from .invocation_context import InvocationContext
Expand Down Expand Up @@ -503,11 +504,13 @@ def from_config(

Args:
config: The config to create the agent from.
config_abs_path: The absolute path to the config file that contains the
agent config.

Returns:
The created agent.
"""
from .config_agent_utils import build_sub_agent
from .config_agent_utils import resolve_agent_reference
from .config_agent_utils import resolve_callbacks

kwargs: Dict[str, Any] = {
Expand All @@ -517,9 +520,7 @@ def from_config(
if config.sub_agents:
sub_agents = []
for sub_agent_config in config.sub_agents:
sub_agent = build_sub_agent(
sub_agent_config, config_abs_path.rsplit('/', 1)[0]
)
sub_agent = resolve_agent_reference(sub_agent_config, config_abs_path)
sub_agents.append(sub_agent)
kwargs['sub_agents'] = sub_agents

Expand Down
63 changes: 2 additions & 61 deletions src/google/adk/agents/base_agent_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
from ..events.event import Event
from ..utils.feature_decorator import working_in_progress
from .callback_context import CallbackContext
from .common_configs import AgentRefConfig
from .common_configs import CodeConfig

if TYPE_CHECKING:
Expand All @@ -52,66 +53,6 @@
TBaseAgentConfig = TypeVar('TBaseAgentConfig', bound='BaseAgentConfig')


class SubAgentConfig(BaseModel):
"""The config for a sub-agent."""

model_config = ConfigDict(extra='forbid')

config: Optional[str] = None
"""The YAML config file path of the sub-agent.

Only one of `config` or `code` can be set.

Example:

```
sub_agents:
- config: search_agent.yaml
- config: my_library/my_custom_agent.yaml
```
"""

code: Optional[str] = None
"""The agent instance defined in the code.

Only one of `config` or `code` can be set.

Example:

For the following agent defined in Python code:

```
# my_library/custom_agents.py
from google.adk.agents.llm_agent import LlmAgent

my_custom_agent = LlmAgent(
name="my_custom_agent",
instruction="You are a helpful custom agent.",
model="gemini-2.0-flash",
)
```

The yaml config should be:

```
sub_agents:
- code: my_library.custom_agents.my_custom_agent
```
"""

@model_validator(mode='after')
def validate_exactly_one_field(self):
code_provided = self.code is not None
config_provided = self.config is not None

if code_provided and config_provided:
raise ValueError('Only one of code or config should be provided')
if not code_provided and not config_provided:
raise ValueError('Exactly one of code or config must be provided')

return self


@working_in_progress('BaseAgentConfig is not ready for use.')
class BaseAgentConfig(BaseModel):
"""The config for the YAML schema of a BaseAgent.
Expand All @@ -133,7 +74,7 @@ class BaseAgentConfig(BaseModel):
description: str = ''
"""Optional. The description of the agent."""

sub_agents: Optional[List[SubAgentConfig]] = None
sub_agents: Optional[List[AgentRefConfig]] = None
"""Optional. The sub-agents of the agent."""

before_agent_callbacks: Optional[List[CodeConfig]] = None
Expand Down
63 changes: 63 additions & 0 deletions src/google/adk/agents/common_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

from pydantic import BaseModel
from pydantic import ConfigDict
from pydantic import model_validator

from ..utils.feature_decorator import working_in_progress

Expand Down Expand Up @@ -77,3 +78,65 @@ class CodeConfig(BaseModel):
value: True
```
"""


class AgentRefConfig(BaseModel):
"""The config for the reference to another agent."""

model_config = ConfigDict(extra="forbid")

config_path: Optional[str] = None
"""The YAML config file path of the sub-agent.

Only one of `config_path` or `code` can be set.

Example:

```
sub_agents:
- config_path: search_agent.yaml
- config_path: my_library/my_custom_agent.yaml
```
"""

code: Optional[str] = None
"""The agent instance defined in the code.

Only one of `config` or `code` can be set.

Example:

For the following agent defined in Python code:

```
# my_library/custom_agents.py
from google.adk.agents.llm_agent import LlmAgent

my_custom_agent = LlmAgent(
name="my_custom_agent",
instruction="You are a helpful custom agent.",
model="gemini-2.0-flash",
)
```

The yaml config should be:

```
sub_agents:
- code: my_library.custom_agents.my_custom_agent
```
"""

@model_validator(mode="after")
def validate_exactly_one_field(self) -> AgentRefConfig:
code_provided = self.code is not None
config_path_provided = self.config_path is not None

if code_provided and config_path_provided:
raise ValueError("Only one of `code` or `config_path` should be provided")
if not code_provided and not config_path_provided:
raise ValueError(
"Exactly one of `code` or `config_path` must be provided"
)

return self
51 changes: 29 additions & 22 deletions src/google/adk/agents/config_agent_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from ..utils.feature_decorator import working_in_progress
from .agent_config import AgentConfig
from .base_agent import BaseAgent
from .base_agent_config import SubAgentConfig
from .common_configs import AgentRefConfig
from .common_configs import CodeConfig
from .llm_agent import LlmAgent
from .llm_agent_config import LlmAgentConfig
Expand Down Expand Up @@ -90,44 +90,48 @@ def _load_config_from_path(config_path: str) -> AgentConfig:
return AgentConfig.model_validate(config_data)


@working_in_progress("build_sub_agent is not ready for use.")
def build_sub_agent(
sub_config: SubAgentConfig, parent_agent_folder_path: str
@working_in_progress("resolve_agent_reference is not ready for use.")
def resolve_agent_reference(
ref_config: AgentRefConfig, referencing_agent_config_abs_path: str
) -> BaseAgent:
"""Build a sub-agent from configuration.
"""Build an agent from a reference.

Args:
sub_config: The sub-agent configuration (SubAgentConfig).
parent_agent_folder_path: The folder path to the parent agent's YAML config.
ref_config: The agent reference configuration (AgentRefConfig).
referencing_agent_config_abs_path: The absolute path to the agent config
that contains the reference.

Returns:
The created sub-agent instance.
The created agent instance.
"""
if sub_config.config:
if os.path.isabs(sub_config.config):
return from_config(sub_config.config)
if ref_config.config_path:
if os.path.isabs(ref_config.config_path):
return from_config(ref_config.config_path)
else:
return from_config(
os.path.join(parent_agent_folder_path, sub_config.config)
os.path.join(
referencing_agent_config_abs_path.rsplit("/", 1)[0],
ref_config.config_path,
)
)
elif sub_config.code:
return _resolve_sub_agent_code_reference(sub_config.code)
elif ref_config.code:
return _resolve_agent_code_reference(ref_config.code)
else:
raise ValueError("SubAgentConfig must have either 'code' or 'config'")
raise ValueError("AgentRefConfig must have either 'code' or 'config_path'")


@working_in_progress("_resolve_sub_agent_code_reference is not ready for use.")
def _resolve_sub_agent_code_reference(code: str) -> Any:
"""Resolve a code reference to an actual agent object.
@working_in_progress("_resolve_agent_code_reference is not ready for use.")
def _resolve_agent_code_reference(code: str) -> Any:
"""Resolve a code reference to an actual agent instance.

Args:
code: The code reference to the sub-agent.
code: The fully-qualified path to an agent instance.

Returns:
The resolved agent object.
The resolved agent instance.

Raises:
ValueError: If the code reference cannot be resolved.
ValueError: If the agent reference cannot be resolved.
"""
if "." not in code:
raise ValueError(f"Invalid code reference: {code}")
Expand All @@ -137,7 +141,10 @@ def _resolve_sub_agent_code_reference(code: str) -> Any:
obj = getattr(module, obj_name)

if callable(obj):
raise ValueError(f"Invalid code reference to a callable: {code}")
raise ValueError(f"Invalid agent reference to a callable: {code}")

if not isinstance(obj, BaseAgent):
raise ValueError(f"Invalid agent reference to a non-agent instance: {code}")

return obj

Expand Down
Loading
Loading