From f1889ae440386668bee9283527db6d0631e35aa3 Mon Sep 17 00:00:00 2001 From: Liang Wu Date: Sun, 27 Jul 2025 11:13:13 -0700 Subject: [PATCH] feat(config): support ADK built-in and custom tools in config PiperOrigin-RevId: 787735915 --- src/google/adk/agents/base_agent.py | 9 +- src/google/adk/agents/base_agent_config.py | 63 +------ src/google/adk/agents/common_configs.py | 63 +++++++ src/google/adk/agents/config_agent_utils.py | 51 +++--- .../agents/config_schemas/AgentConfig.json | 160 +++++++++++++++--- src/google/adk/agents/llm_agent.py | 53 ++++-- src/google/adk/agents/llm_agent_config.py | 3 +- src/google/adk/tools/agent_tool.py | 29 ++++ src/google/adk/tools/base_tool.py | 123 +++++++++++++- tests/unittests/agents/test_agent_config.py | 18 +- 10 files changed, 440 insertions(+), 132 deletions(-) diff --git a/src/google/adk/agents/base_agent.py b/src/google/adk/agents/base_agent.py index 1ea63f284..9ee7477aa 100644 --- a/src/google/adk/agents/base_agent.py +++ b/src/google/adk/agents/base_agent.py @@ -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 @@ -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] = { @@ -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 diff --git a/src/google/adk/agents/base_agent_config.py b/src/google/adk/agents/base_agent_config.py index 04ef0e7d0..aef9b03a9 100644 --- a/src/google/adk/agents/base_agent_config.py +++ b/src/google/adk/agents/base_agent_config.py @@ -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: @@ -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. @@ -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 diff --git a/src/google/adk/agents/common_configs.py b/src/google/adk/agents/common_configs.py index 0e6e389b4..094b8fb75 100644 --- a/src/google/adk/agents/common_configs.py +++ b/src/google/adk/agents/common_configs.py @@ -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 @@ -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 diff --git a/src/google/adk/agents/config_agent_utils.py b/src/google/adk/agents/config_agent_utils.py index 9e5901365..8bbcdc954 100644 --- a/src/google/adk/agents/config_agent_utils.py +++ b/src/google/adk/agents/config_agent_utils.py @@ -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 @@ -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}") @@ -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 diff --git a/src/google/adk/agents/config_schemas/AgentConfig.json b/src/google/adk/agents/config_schemas/AgentConfig.json index e2dc4c9c3..fdf025485 100644 --- a/src/google/adk/agents/config_schemas/AgentConfig.json +++ b/src/google/adk/agents/config_schemas/AgentConfig.json @@ -1,5 +1,37 @@ { "$defs": { + "AgentRefConfig": { + "additionalProperties": false, + "description": "The config for the reference to another agent.", + "properties": { + "config_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Config Path" + }, + "code": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Code" + } + }, + "title": "AgentRefConfig", + "type": "object" + }, "ArgumentConfig": { "additionalProperties": false, "description": "An argument passed to a function or a class's constructor.", @@ -26,6 +58,84 @@ "title": "ArgumentConfig", "type": "object" }, + "BaseAgentConfig": { + "additionalProperties": true, + "description": "The config for the YAML schema of a BaseAgent.\n\nDo not use this class directly. It's the base class for all agent configs.", + "properties": { + "agent_class": { + "anyOf": [ + { + "const": "BaseAgent", + "type": "string" + }, + { + "type": "string" + } + ], + "default": "BaseAgent", + "title": "Agent Class" + }, + "name": { + "title": "Name", + "type": "string" + }, + "description": { + "default": "", + "title": "Description", + "type": "string" + }, + "sub_agents": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/AgentRefConfig" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sub Agents" + }, + "before_agent_callbacks": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/CodeConfig" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Before Agent Callbacks" + }, + "after_agent_callbacks": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/CodeConfig" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "After Agent Callbacks" + } + }, + "required": [ + "name" + ], + "title": "BaseAgentConfig", + "type": "object" + }, "CodeConfig": { "additionalProperties": false, "description": "Code reference config for a variable, a function, or a class.\n\nThis config is used for configuring callbacks and tools.", @@ -82,7 +192,7 @@ "anyOf": [ { "items": { - "$ref": "#/$defs/SubAgentConfig" + "$ref": "#/$defs/AgentRefConfig" }, "type": "array" }, @@ -210,7 +320,7 @@ "anyOf": [ { "items": { - "$ref": "#/$defs/CodeConfig" + "$ref": "#/$defs/ToolConfig" }, "type": "array" }, @@ -312,7 +422,7 @@ "anyOf": [ { "items": { - "$ref": "#/$defs/SubAgentConfig" + "$ref": "#/$defs/AgentRefConfig" }, "type": "array" }, @@ -395,7 +505,7 @@ "anyOf": [ { "items": { - "$ref": "#/$defs/SubAgentConfig" + "$ref": "#/$defs/AgentRefConfig" }, "type": "array" }, @@ -466,7 +576,7 @@ "anyOf": [ { "items": { - "$ref": "#/$defs/SubAgentConfig" + "$ref": "#/$defs/AgentRefConfig" }, "type": "array" }, @@ -514,36 +624,37 @@ "title": "SequentialAgentConfig", "type": "object" }, - "SubAgentConfig": { + "ToolArgsConfig": { + "additionalProperties": true, + "description": "The configuration for tool arguments.\n\nThis config allows arbitrary key-value pairs as tool arguments.", + "properties": {}, + "title": "ToolArgsConfig", + "type": "object" + }, + "ToolConfig": { "additionalProperties": false, - "description": "The config for a sub-agent.", + "description": "The configuration for a tool.\n\nThe config supports these types of tools:\n1. ADK built-in tools\n2. User-defined tool instances\n3. User-defined tool classes\n4. User-defined functions that generate tool instances\n5. User-defined function tools\n\nFor examples:\n\n 1. For ADK built-in tool instances or classes in `google.adk.tools` package,\n they can be referenced directly with the `name` and optionally with\n `config`.\n\n ```\n tools:\n - name: google_search\n - name: AgentTool\n config:\n agent: ./another_agent.yaml\n skip_summarization: true\n ```\n\n 2. For user-defined tool instances, the `name` is the fully qualified path\n to the tool instance.\n\n ```\n tools:\n - name: my_package.my_module.my_tool\n ```\n\n 3. For user-defined tool classes (custom tools), the `name` is the fully\n qualified path to the tool class and `config` is the arguments for the tool.\n\n ```\n tools:\n - name: my_package.my_module.my_tool_class\n config:\n my_tool_arg1: value1\n my_tool_arg2: value2\n ```\n\n 4. For user-defined functions that generate tool instances, the `name` is the\n fully qualified path to the function and `config` is passed to the function\n as arguments.\n\n ```\n tools:\n - name: my_package.my_module.my_tool_function\n config:\n my_function_arg1: value1\n my_function_arg2: value2\n ```\n\n The function must have the following signature:\n ```\n def my_function(config: ToolArgsConfig) -> BaseTool:\n ...\n ```\n\n 5. For user-defined function tools, the `name` is the fully qualified path\n to the function.\n\n ```\n tools:\n - name: my_package.my_module.my_function_tool\n ```", "properties": { - "config": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "default": null, - "title": "Config" + "name": { + "title": "Name", + "type": "string" }, - "code": { + "args": { "anyOf": [ { - "type": "string" + "$ref": "#/$defs/ToolArgsConfig" }, { "type": "null" } ], - "default": null, - "title": "Code" + "default": null } }, - "title": "SubAgentConfig", + "required": [ + "name" + ], + "title": "ToolConfig", "type": "object" } }, @@ -559,6 +670,9 @@ }, { "$ref": "#/$defs/SequentialAgentConfig" + }, + { + "$ref": "#/$defs/BaseAgentConfig" } ], "description": "The config for the YAML schema to create an agent.", diff --git a/src/google/adk/agents/llm_agent.py b/src/google/adk/agents/llm_agent.py index 170bec5ec..68219318e 100644 --- a/src/google/adk/agents/llm_agent.py +++ b/src/google/adk/agents/llm_agent.py @@ -17,6 +17,7 @@ import importlib import inspect import logging +import os from typing import Any from typing import AsyncGenerator from typing import Awaitable @@ -46,7 +47,9 @@ from ..models.llm_response import LlmResponse from ..models.registry import LLMRegistry from ..planners.base_planner import BasePlanner +from ..tools.agent_tool import AgentTool from ..tools.base_tool import BaseTool +from ..tools.base_tool import ToolConfig from ..tools.base_toolset import BaseToolset from ..tools.function_tool import FunctionTool from ..tools.tool_context import ToolContext @@ -525,31 +528,59 @@ def __validate_generate_content_config( @classmethod @working_in_progress('LlmAgent._resolve_tools is not ready for use.') - def _resolve_tools(cls, tools_config: list[CodeConfig]) -> list[Any]: + def _resolve_tools( + cls, tool_configs: list[ToolConfig], config_abs_path: str + ) -> list[Any]: """Resolve tools from configuration. Args: - tools_config: List of tool configurations (CodeConfig objects). + tool_configs: List of tool configurations (ToolConfig objects). + config_abs_path: The absolute path to the agent config file. Returns: List of resolved tool objects. """ resolved_tools = [] - for tool_config in tools_config: + for tool_config in tool_configs: if '.' not in tool_config.name: + # ADK built-in tools module = importlib.import_module('google.adk.tools') obj = getattr(module, tool_config.name) - if isinstance(obj, ToolUnion): - resolved_tools.append(obj) + else: + # User-defined tools + module_path, obj_name = tool_config.name.rsplit('.', 1) + module = importlib.import_module(module_path) + obj = getattr(module, obj_name) + + if isinstance(obj, BaseTool) or isinstance(obj, BaseToolset): + logger.debug( + 'Tool %s is an instance of BaseTool/BaseToolset.', tool_config.name + ) + resolved_tools.append(obj) + elif inspect.isclass(obj) and ( + issubclass(obj, BaseTool) or issubclass(obj, BaseToolset) + ): + logger.debug( + 'Tool %s is a sub-class of BaseTool/BaseToolset.', tool_config.name + ) + resolved_tools.append( + obj.from_config(tool_config.args, config_abs_path) + ) + elif callable(obj): + if tool_config.args: + logger.debug( + 'Tool %s is a user-defined tool-generating function.', + tool_config.name, + ) + resolved_tools.append(obj(tool_config.args)) else: - raise ValueError( - f'Invalid tool name: {tool_config.name} is not a built-in tool.' + logger.debug( + 'Tool %s is a user-defined function tool.', tool_config.name ) + resolved_tools.append(obj) else: - from .config_agent_utils import resolve_code_reference - - resolved_tools.append(resolve_code_reference(tool_config)) + raise ValueError(f'Invalid tool YAML config: {tool_config}.') return resolved_tools @@ -582,7 +613,7 @@ def from_config( if config.output_key: agent.output_key = config.output_key if config.tools: - agent.tools = cls._resolve_tools(config.tools) + agent.tools = cls._resolve_tools(config.tools, config_abs_path) if config.before_model_callbacks: agent.before_model_callback = resolve_callbacks( config.before_model_callbacks diff --git a/src/google/adk/agents/llm_agent_config.py b/src/google/adk/agents/llm_agent_config.py index a99ea3ce9..0a08e3482 100644 --- a/src/google/adk/agents/llm_agent_config.py +++ b/src/google/adk/agents/llm_agent_config.py @@ -21,6 +21,7 @@ from pydantic import ConfigDict +from ..tools.base_tool import ToolConfig from .base_agent_config import BaseAgentConfig from .common_configs import CodeConfig @@ -63,7 +64,7 @@ class LlmAgentConfig(BaseAgentConfig): include_contents: Literal['default', 'none'] = 'default' """Optional. LlmAgent.include_contents.""" - tools: Optional[list[CodeConfig]] = None + tools: Optional[list[ToolConfig]] = None """Optional. LlmAgent.tools. Examples: diff --git a/src/google/adk/tools/agent_tool.py b/src/google/adk/tools/agent_tool.py index 2638d79df..7fa92df64 100644 --- a/src/google/adk/tools/agent_tool.py +++ b/src/google/adk/tools/agent_tool.py @@ -18,13 +18,16 @@ from typing import TYPE_CHECKING from google.genai import types +from pydantic import BaseModel from pydantic import model_validator from typing_extensions import override from . import _automatic_function_calling_util +from ..agents.common_configs import AgentRefConfig from ..memory.in_memory_memory_service import InMemoryMemoryService from ._forwarding_artifact_service import ForwardingArtifactService from .base_tool import BaseTool +from .base_tool import ToolArgsConfig from .tool_context import ToolContext if TYPE_CHECKING: @@ -154,3 +157,29 @@ async def run_async( else: tool_result = merged_text return tool_result + + @classmethod + @override + def from_config( + cls, config: ToolArgsConfig, config_abs_path: str + ) -> AgentTool: + from ..agents import config_agent_utils + + agent_tool_config = AgentToolConfig.model_validate(config.model_dump()) + + agent = config_agent_utils.resolve_agent_reference( + agent_tool_config.agent, config_abs_path + ) + return cls( + agent=agent, skip_summarization=agent_tool_config.skip_summarization + ) + + +class AgentToolConfig(BaseModel): + """The config for the AgentTool.""" + + agent: AgentRefConfig + """The reference to the agent instance.""" + + skip_summarization: bool = False + """Whether to skip summarization of the agent output.""" diff --git a/src/google/adk/tools/base_tool.py b/src/google/adk/tools/base_tool.py index 43ca64041..7db7533cb 100644 --- a/src/google/adk/tools/base_tool.py +++ b/src/google/adk/tools/base_tool.py @@ -17,9 +17,13 @@ from abc import ABC from typing import Any from typing import Optional +from typing import Type from typing import TYPE_CHECKING +from typing import TypeVar from google.genai import types +from pydantic import BaseModel +from pydantic import ConfigDict from ..utils.variant_utils import get_google_llm_variant from ..utils.variant_utils import GoogleLLMVariant @@ -28,6 +32,8 @@ if TYPE_CHECKING: from ..models.llm_request import LlmRequest +SelfTool = TypeVar("SelfTool", bound="BaseTool") + class BaseTool(ABC): """The base class for all tools.""" @@ -78,7 +84,7 @@ async def run_async( Returns: The result of running the tool. """ - raise NotImplementedError(f'{type(self)} is not implemented') + raise NotImplementedError(f"{type(self)} is not implemented") async def process_llm_request( self, *, tool_context: ToolContext, llm_request: LlmRequest @@ -122,6 +128,25 @@ async def process_llm_request( def _api_variant(self) -> GoogleLLMVariant: return get_google_llm_variant() + @classmethod + def from_config( + cls: Type[SelfTool], config: ToolArgsConfig, config_abs_path: str + ) -> SelfTool: + """Creates a tool instance from a config. + + Subclasses should override and implement this method to do custom + initialization from a config. + + Args: + config: The config for the tool. + config_abs_path: The absolute path to the config file that contains the + tool config. + + Returns: + The tool instance. + """ + raise NotImplementedError(f"from_config for {cls} not implemented.") + def _find_tool_with_function_declarations( llm_request: LlmRequest, @@ -138,3 +163,99 @@ def _find_tool_with_function_declarations( ), None, ) + + +class ToolArgsConfig(BaseModel): + """The configuration for tool arguments. + + This config allows arbitrary key-value pairs as tool arguments. + """ + + model_config = ConfigDict(extra="allow") + + +class ToolConfig(BaseModel): + """The configuration for a tool. + + The config supports these types of tools: + 1. ADK built-in tools + 2. User-defined tool instances + 3. User-defined tool classes + 4. User-defined functions that generate tool instances + 5. User-defined function tools + + For examples: + + 1. For ADK built-in tool instances or classes in `google.adk.tools` package, + they can be referenced directly with the `name` and optionally with + `config`. + + ``` + tools: + - name: google_search + - name: AgentTool + config: + agent: ./another_agent.yaml + skip_summarization: true + ``` + + 2. For user-defined tool instances, the `name` is the fully qualified path + to the tool instance. + + ``` + tools: + - name: my_package.my_module.my_tool + ``` + + 3. For user-defined tool classes (custom tools), the `name` is the fully + qualified path to the tool class and `config` is the arguments for the tool. + + ``` + tools: + - name: my_package.my_module.my_tool_class + config: + my_tool_arg1: value1 + my_tool_arg2: value2 + ``` + + 4. For user-defined functions that generate tool instances, the `name` is the + fully qualified path to the function and `config` is passed to the function + as arguments. + + ``` + tools: + - name: my_package.my_module.my_tool_function + config: + my_function_arg1: value1 + my_function_arg2: value2 + ``` + + The function must have the following signature: + ``` + def my_function(config: ToolArgsConfig) -> BaseTool: + ... + ``` + + 5. For user-defined function tools, the `name` is the fully qualified path + to the function. + + ``` + tools: + - name: my_package.my_module.my_function_tool + ``` + """ + + model_config = ConfigDict(extra="forbid") + + name: str + """The name of the tool. + + For ADK built-in tools, the name is the name of the tool, e.g. `google_search` + or `AgentTool`. + + For user-defined tools, the name is the fully qualified path to the tool, e.g. + `my_package.my_module.my_tool`. + """ + + args: Optional[ToolArgsConfig] = None + """The args for the tool.""" diff --git a/tests/unittests/agents/test_agent_config.py b/tests/unittests/agents/test_agent_config.py index b24f87289..d7c3f0789 100644 --- a/tests/unittests/agents/test_agent_config.py +++ b/tests/unittests/agents/test_agent_config.py @@ -50,9 +50,9 @@ def test_agent_config_discriminator_loop_agent(): name: CodePipelineAgent description: Executes a sequence of code writing, reviewing, and refactoring. sub_agents: - - config: sub_agents/code_writer_agent.yaml - - config: sub_agents/code_reviewer_agent.yaml - - config: sub_agents/code_refactorer_agent.yaml + - config_path: sub_agents/code_writer_agent.yaml + - config_path: sub_agents/code_reviewer_agent.yaml + - config_path: sub_agents/code_refactorer_agent.yaml """ config_data = yaml.safe_load(yaml_content) @@ -68,9 +68,9 @@ def test_agent_config_discriminator_parallel_agent(): name: CodePipelineAgent description: Executes a sequence of code writing, reviewing, and refactoring. sub_agents: - - config: sub_agents/code_writer_agent.yaml - - config: sub_agents/code_reviewer_agent.yaml - - config: sub_agents/code_refactorer_agent.yaml + - config_path: sub_agents/code_writer_agent.yaml + - config_path: sub_agents/code_reviewer_agent.yaml + - config_path: sub_agents/code_refactorer_agent.yaml """ config_data = yaml.safe_load(yaml_content) @@ -86,9 +86,9 @@ def test_agent_config_discriminator_sequential_agent(): name: CodePipelineAgent description: Executes a sequence of code writing, reviewing, and refactoring. sub_agents: - - config: sub_agents/code_writer_agent.yaml - - config: sub_agents/code_reviewer_agent.yaml - - config: sub_agents/code_refactorer_agent.yaml + - config_path: sub_agents/code_writer_agent.yaml + - config_path: sub_agents/code_reviewer_agent.yaml + - config_path: sub_agents/code_refactorer_agent.yaml """ config_data = yaml.safe_load(yaml_content)