Skip to content

feat: load yaml agents in adk web #1649

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions src/google/adk/agents/__init__.py
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from .agent_factory import AgentFactory
from .base_agent import BaseAgent
from .live_request_queue import LiveRequest
from .live_request_queue import LiveRequestQueue
@@ -24,6 +25,7 @@

__all__ = [
'Agent',
'AgentFactory',
'BaseAgent',
'LlmAgent',
'LoopAgent',
412 changes: 412 additions & 0 deletions src/google/adk/agents/agent_factory.py

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions src/google/adk/agents/configs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# 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.
34 changes: 34 additions & 0 deletions src/google/adk/agents/configs/agents_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 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.

from __future__ import annotations

from typing import List
from typing import Union

from pydantic import BaseModel
from pydantic import ConfigDict
from pydantic import Field

from .custom_agent_config import CustomAgentConfig
from .llm_agent_config import LlmAgentConfig
from .loop_agent_config import LoopAgentConfig

AgentConfig = Union[LlmAgentConfig, CustomAgentConfig, LoopAgentConfig]


class AgentsConfig(BaseModel):
model_config = ConfigDict(extra="forbid")

agents: List[AgentConfig]
71 changes: 71 additions & 0 deletions src/google/adk/agents/configs/base_agent_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# 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.

from __future__ import annotations

from typing import List
from typing import Literal
from typing import Optional

from pydantic import BaseModel
from pydantic import ConfigDict

from .common_config import CodeConfig
from .common_config import SubAgentConfig


# Should not be used directly, only as a sub-class
class BaseAgentConfig(BaseModel):
"""The config for a BaseAgent."""

model_config = ConfigDict(extra="forbid")

agent_type: Literal["BaseAgent"] = "BaseAgent"

name: str
"""BaseAgent.name. Required."""

description: str = ""
"""BaseAgent.description. Optional."""

before_agent_callbacks: Optional[List[CodeConfig]] = None
"""BaseAgent.before_agent_callbacks. Optional.
Examples:
```
before_agent_callbacks:
- name: my_library.security_callbacks.before_agent_callback
```
"""

after_agent_callbacks: Optional[List[CodeConfig]] = None
"""BaseAgent.after_agent_callbacks. Optional."""

sub_agents: Optional[List[SubAgentConfig]] = None
"""The sub_agents of this agent.
Examples:
Below is a sample with two sub-agents in yaml.
- The first agent is defined via config.
- The second agent is implemented python file.
```
sub_agents:
- config: search_agent.yaml
- code:
name: my_library.my_custom_agent
```
"""
146 changes: 146 additions & 0 deletions src/google/adk/agents/configs/common_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# 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.

from __future__ import annotations

from typing import Any
from typing import List
from typing import Optional

from pydantic import BaseModel
from pydantic import ConfigDict


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

model_config = ConfigDict(extra="forbid")

config: Optional[str] = None
"""The config file path of the sub-agent defined via agent config. It could
also be the name of the agent defined in the same file.
Example:
```
sub_agents:
- config: search_agent.yaml
- config: my_library/my_custom_agent.yaml
- config: another_agent_defined_in_the_same_file
```
"""

code: Optional[CodeConfig] = None
"""The agent defined by code.
When this exists, it override the config.
Example:
For below agent defined in Python code:
```
# my_library/custom_agents.py
from google.adk.agents 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:
name: my_library.custom_agents.my_custom_agent
```
For below agent defined in Java code:
```
package com.acme.agents
class CustomAgent {
public static final LlmAgent customAgent = LlmAgent.builder()
.name("custom_agent")
.model("gemini-2.0-flash")
.instruction("You are a helpful assistant.")
.build();
}
```
```
sub_agents:
- code:
name: com.acme.agents.customAgent.customAgent
```
"""


class ArgumentConfig(BaseModel):
"""An argument passed to a method or constructor."""

model_config = ConfigDict(extra="forbid")

name: Optional[str] = None
"""The argument name. Optional
When the argument is for a positional argument, this can be omitted.
"""

value: Any
"""The argument value."""


class CodeConfig(BaseModel):
"""Reference the code"""

model_config = ConfigDict(extra="forbid")

name: str
"""Required. The name of the variable, method, class, etc. in Python or Java.
Examples:
When used for agent_class, it could be ADK builtin agents, such as LlmAgent,
SequetialAgent, etc.
When used for tools,
- It can be ADK builtin tools, such as google_search, etc.
- It can be users' custom tools, e.g. my_library.my_tools.my_tool.
When used for clalbacks, it refers to a function, e.g. my_library.my_callbacks.my_callback
"""

args: Optional[List[ArgumentConfig]] = None
"""The arguments for the code when name references a method or a contructor.
Examples:
Below is a sample usage:
```
tools
- name: AgentTool
args:
- name: agent
value: search_agent.yaml
- name: skip_summarization
value: True
```
"""
32 changes: 32 additions & 0 deletions src/google/adk/agents/configs/custom_agent_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# 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.

from __future__ import annotations

from typing import List
from typing import Literal
from typing import Optional

from .base_agent_config import BaseAgentConfig
from .common_config import ArgumentConfig


# This is essentially a CodeConfig, maybe using CodeConfig is sufficient.
class CustomAgentConfig(BaseAgentConfig):

agent_type: Literal['CustomAgent'] = 'CustomAgent'

path: str

args: Optional[List[ArgumentConfig]] = None
114 changes: 114 additions & 0 deletions src/google/adk/agents/configs/llm_agent_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# 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.

from __future__ import annotations

from typing import List
from typing import Literal
from typing import Optional

from .base_agent_config import BaseAgentConfig
from .common_config import CodeConfig


class LlmAgentConfig(BaseAgentConfig):
"""Config for LlmAgent."""

agent_type: Literal['LlmAgent'] = 'LlmAgent'

model: Optional[str] = None
"""LlmAgent.model. Optional.
When not set, using the same model as the parent model.
"""

instruction: str
"""LlmAgent.instruction. Required."""

tools: Optional[List[CodeConfig]] = None
"""LlmAgent.tools. Optional.
Examples:
For ADK builtin tools in google.adk.tools package, they can be referenced
directly with the name.
```
- tools:
- name: google_search
- name: load_memory
```
For user-defined tools, they can be referenced with fully qualified name.
```
- tools:
- name: my_library.my_tools.my_tool
```
For tools that needs to be created via functions.
```
- tools:
- name: my_library.my_tools.create_tool
- args:
- name: param1
value: value1
- name: param2
value: value2
```
For more advanced tools, instead of specifying arguments in config, it's
recommended defining them in python files and reference them.
```
# tools.py
my_mcp_toolset = MCPToolset(
connection_params=StdioServerParameters(
command="npx",
args=["-y", "@notionhq/notion-mcp-server"],
env={"OPENAPI_MCP_HEADERS": NOTION_HEADERS},
)
)
```
Then, reference the toolset in config:
```
- tools:
- name: tools.my_mcp_toolset
```
"""

input_schema: Optional[CodeConfig] = None
"""LlmAgent.input_schema. Optional."""
output_schema: Optional[CodeConfig] = None
"""LlmAgent.output_schema. Optional."""

before_model_callbacks: Optional[List[CodeConfig]] = None
after_model_callbacks: Optional[List[CodeConfig]] = None
before_tool_callbacks: Optional[List[CodeConfig]] = None
after_tool_callbacks: Optional[List[CodeConfig]] = None

disallow_transfer_to_parent: Optional[bool] = None
"""LlmAgent.disallow_transfer_to_parent. Optional."""

disallow_transfer_to_peers: Optional[bool] = None
"""LlmAgent.disallow_transfer_to_peers. Optional."""

include_contents: Literal['default', 'none'] = 'default'
"""LlmAgent.include_contents. Optional."""

output_key: Optional[str] = None
"""LlmAgent.output_key. Optional."""
15 changes: 15 additions & 0 deletions src/google/adk/agents/configs/loop_agent_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from __future__ import annotations

from typing import Literal
from typing import Optional

from .base_agent_config import BaseAgentConfig


class LoopAgentConfig(BaseAgentConfig):
"""Configuration for a loop agent."""

agent_type: Literal['LoopAgent'] = 'LoopAgent'

max_iterations: Optional[int] = None
"""LoopAgent.max_iterations. Optional"""
10 changes: 10 additions & 0 deletions src/google/adk/cli/utils/agent_loader.py
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@
from typing import Optional

from . import envs
from ...agents.agent_factory import AgentFactory
from ...agents.base_agent import BaseAgent

logger = logging.getLogger("google_adk." + __name__)
@@ -128,6 +129,12 @@ def _load_from_submodule(self, agent_name: str) -> Optional[BaseAgent]:

return None

def _load_from_yaml(self, agent_name: str) -> BaseAgent:
# The path is for illustrative purposes only. We should decide what the yaml
# file requirements are.
relative_path = agent_name + "/root_agent.yaml"
return AgentFactory.from_config(relative_path)

def _perform_load(self, agent_name: str) -> BaseAgent:
"""Internal logic to load an agent"""
# Add self.agents_dir to sys.path
@@ -145,6 +152,9 @@ def _perform_load(self, agent_name: str) -> BaseAgent:
if root_agent := self._load_from_submodule(agent_name):
return root_agent

if root_agent := self._load_from_yaml(agent_name):
return root_agent

# If no root_agent was found by any pattern
raise ValueError(
f"No root_agent found for '{agent_name}'. Searched in"