Skip to content

Commit 03e9d84

Browse files
wuliang229copybara-github
authored andcommitted
feat(config): support ADK built-in and custom tools in config
PiperOrigin-RevId: 786412207
1 parent c69dcf8 commit 03e9d84

File tree

10 files changed

+440
-132
lines changed

10 files changed

+440
-132
lines changed

src/google/adk/agents/base_agent.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from ..utils.feature_decorator import working_in_progress
4242
from .base_agent_config import BaseAgentConfig
4343
from .callback_context import CallbackContext
44+
from .common_configs import AgentRefConfig
4445

4546
if TYPE_CHECKING:
4647
from .invocation_context import InvocationContext
@@ -503,11 +504,13 @@ def from_config(
503504
504505
Args:
505506
config: The config to create the agent from.
507+
config_abs_path: The absolute path to the config file that contains the
508+
agent config.
506509
507510
Returns:
508511
The created agent.
509512
"""
510-
from .config_agent_utils import build_sub_agent
513+
from .config_agent_utils import resolve_agent_reference
511514
from .config_agent_utils import resolve_callbacks
512515

513516
kwargs: Dict[str, Any] = {
@@ -517,9 +520,7 @@ def from_config(
517520
if config.sub_agents:
518521
sub_agents = []
519522
for sub_agent_config in config.sub_agents:
520-
sub_agent = build_sub_agent(
521-
sub_agent_config, config_abs_path.rsplit('/', 1)[0]
522-
)
523+
sub_agent = resolve_agent_reference(sub_agent_config, config_abs_path)
523524
sub_agents.append(sub_agent)
524525
kwargs['sub_agents'] = sub_agents
525526

src/google/adk/agents/base_agent_config.py

Lines changed: 2 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
from ..events.event import Event
4444
from ..utils.feature_decorator import working_in_progress
4545
from .callback_context import CallbackContext
46+
from .common_configs import AgentRefConfig
4647
from .common_configs import CodeConfig
4748

4849
if TYPE_CHECKING:
@@ -52,66 +53,6 @@
5253
TBaseAgentConfig = TypeVar('TBaseAgentConfig', bound='BaseAgentConfig')
5354

5455

55-
class SubAgentConfig(BaseModel):
56-
"""The config for a sub-agent."""
57-
58-
model_config = ConfigDict(extra='forbid')
59-
60-
config: Optional[str] = None
61-
"""The YAML config file path of the sub-agent.
62-
63-
Only one of `config` or `code` can be set.
64-
65-
Example:
66-
67-
```
68-
sub_agents:
69-
- config: search_agent.yaml
70-
- config: my_library/my_custom_agent.yaml
71-
```
72-
"""
73-
74-
code: Optional[str] = None
75-
"""The agent instance defined in the code.
76-
77-
Only one of `config` or `code` can be set.
78-
79-
Example:
80-
81-
For the following agent defined in Python code:
82-
83-
```
84-
# my_library/custom_agents.py
85-
from google.adk.agents.llm_agent import LlmAgent
86-
87-
my_custom_agent = LlmAgent(
88-
name="my_custom_agent",
89-
instruction="You are a helpful custom agent.",
90-
model="gemini-2.0-flash",
91-
)
92-
```
93-
94-
The yaml config should be:
95-
96-
```
97-
sub_agents:
98-
- code: my_library.custom_agents.my_custom_agent
99-
```
100-
"""
101-
102-
@model_validator(mode='after')
103-
def validate_exactly_one_field(self):
104-
code_provided = self.code is not None
105-
config_provided = self.config is not None
106-
107-
if code_provided and config_provided:
108-
raise ValueError('Only one of code or config should be provided')
109-
if not code_provided and not config_provided:
110-
raise ValueError('Exactly one of code or config must be provided')
111-
112-
return self
113-
114-
11556
@working_in_progress('BaseAgentConfig is not ready for use.')
11657
class BaseAgentConfig(BaseModel):
11758
"""The config for the YAML schema of a BaseAgent.
@@ -133,7 +74,7 @@ class BaseAgentConfig(BaseModel):
13374
description: str = ''
13475
"""Optional. The description of the agent."""
13576

136-
sub_agents: Optional[List[SubAgentConfig]] = None
77+
sub_agents: Optional[List[AgentRefConfig]] = None
13778
"""Optional. The sub-agents of the agent."""
13879

13980
before_agent_callbacks: Optional[List[CodeConfig]] = None

src/google/adk/agents/common_configs.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
from pydantic import BaseModel
2323
from pydantic import ConfigDict
24+
from pydantic import model_validator
2425

2526
from ..utils.feature_decorator import working_in_progress
2627

@@ -77,3 +78,65 @@ class CodeConfig(BaseModel):
7778
value: True
7879
```
7980
"""
81+
82+
83+
class AgentRefConfig(BaseModel):
84+
"""The config for the reference to another agent."""
85+
86+
model_config = ConfigDict(extra="forbid")
87+
88+
config_path: Optional[str] = None
89+
"""The YAML config file path of the sub-agent.
90+
91+
Only one of `config_path` or `code` can be set.
92+
93+
Example:
94+
95+
```
96+
sub_agents:
97+
- config_path: search_agent.yaml
98+
- config_path: my_library/my_custom_agent.yaml
99+
```
100+
"""
101+
102+
code: Optional[str] = None
103+
"""The agent instance defined in the code.
104+
105+
Only one of `config` or `code` can be set.
106+
107+
Example:
108+
109+
For the following agent defined in Python code:
110+
111+
```
112+
# my_library/custom_agents.py
113+
from google.adk.agents.llm_agent import LlmAgent
114+
115+
my_custom_agent = LlmAgent(
116+
name="my_custom_agent",
117+
instruction="You are a helpful custom agent.",
118+
model="gemini-2.0-flash",
119+
)
120+
```
121+
122+
The yaml config should be:
123+
124+
```
125+
sub_agents:
126+
- code: my_library.custom_agents.my_custom_agent
127+
```
128+
"""
129+
130+
@model_validator(mode="after")
131+
def validate_exactly_one_field(self) -> AgentRefConfig:
132+
code_provided = self.code is not None
133+
config_path_provided = self.config_path is not None
134+
135+
if code_provided and config_path_provided:
136+
raise ValueError("Only one of `code` or `config_path` should be provided")
137+
if not code_provided and not config_path_provided:
138+
raise ValueError(
139+
"Exactly one of `code` or `config_path` must be provided"
140+
)
141+
142+
return self

src/google/adk/agents/config_agent_utils.py

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from ..utils.feature_decorator import working_in_progress
2525
from .agent_config import AgentConfig
2626
from .base_agent import BaseAgent
27-
from .base_agent_config import SubAgentConfig
27+
from .common_configs import AgentRefConfig
2828
from .common_configs import CodeConfig
2929
from .llm_agent import LlmAgent
3030
from .llm_agent_config import LlmAgentConfig
@@ -90,44 +90,48 @@ def _load_config_from_path(config_path: str) -> AgentConfig:
9090
return AgentConfig.model_validate(config_data)
9191

9292

93-
@working_in_progress("build_sub_agent is not ready for use.")
94-
def build_sub_agent(
95-
sub_config: SubAgentConfig, parent_agent_folder_path: str
93+
@working_in_progress("resolve_agent_reference is not ready for use.")
94+
def resolve_agent_reference(
95+
ref_config: AgentRefConfig, referencing_agent_config_abs_path: str
9696
) -> BaseAgent:
97-
"""Build a sub-agent from configuration.
97+
"""Build an agent from a reference.
9898
9999
Args:
100-
sub_config: The sub-agent configuration (SubAgentConfig).
101-
parent_agent_folder_path: The folder path to the parent agent's YAML config.
100+
ref_config: The agent reference configuration (AgentRefConfig).
101+
referencing_agent_config_abs_path: The absolute path to the agent config
102+
that contains the reference.
102103
103104
Returns:
104-
The created sub-agent instance.
105+
The created agent instance.
105106
"""
106-
if sub_config.config:
107-
if os.path.isabs(sub_config.config):
108-
return from_config(sub_config.config)
107+
if ref_config.config_path:
108+
if os.path.isabs(ref_config.config_path):
109+
return from_config(ref_config.config_path)
109110
else:
110111
return from_config(
111-
os.path.join(parent_agent_folder_path, sub_config.config)
112+
os.path.join(
113+
referencing_agent_config_abs_path.rsplit("/", 1)[0],
114+
ref_config.config_path,
115+
)
112116
)
113-
elif sub_config.code:
114-
return _resolve_sub_agent_code_reference(sub_config.code)
117+
elif ref_config.code:
118+
return _resolve_agent_code_reference(ref_config.code)
115119
else:
116-
raise ValueError("SubAgentConfig must have either 'code' or 'config'")
120+
raise ValueError("AgentRefConfig must have either 'code' or 'config_path'")
117121

118122

119-
@working_in_progress("_resolve_sub_agent_code_reference is not ready for use.")
120-
def _resolve_sub_agent_code_reference(code: str) -> Any:
121-
"""Resolve a code reference to an actual agent object.
123+
@working_in_progress("_resolve_agent_code_reference is not ready for use.")
124+
def _resolve_agent_code_reference(code: str) -> Any:
125+
"""Resolve a code reference to an actual agent instance.
122126
123127
Args:
124-
code: The code reference to the sub-agent.
128+
code: The fully-qualified path to an agent instance.
125129
126130
Returns:
127-
The resolved agent object.
131+
The resolved agent instance.
128132
129133
Raises:
130-
ValueError: If the code reference cannot be resolved.
134+
ValueError: If the agent reference cannot be resolved.
131135
"""
132136
if "." not in code:
133137
raise ValueError(f"Invalid code reference: {code}")
@@ -137,7 +141,10 @@ def _resolve_sub_agent_code_reference(code: str) -> Any:
137141
obj = getattr(module, obj_name)
138142

139143
if callable(obj):
140-
raise ValueError(f"Invalid code reference to a callable: {code}")
144+
raise ValueError(f"Invalid agent reference to a callable: {code}")
145+
146+
if not isinstance(obj, BaseAgent):
147+
raise ValueError(f"Invalid agent reference to a non-agent instance: {code}")
141148

142149
return obj
143150

0 commit comments

Comments
 (0)