From 1e43c51f17adcad67e811d20cfbe0b54780ff3c0 Mon Sep 17 00:00:00 2001 From: Lalit Kumar Date: Tue, 15 Sep 2026 14:19:50 +0530 Subject: [PATCH] allow custom name/description when VertexAiSearchTool is swapped --- src/google/adk/agents/llm_agent.py | 15 +++++++++-- .../adk/tools/discovery_engine_search_tool.py | 27 +++++++++++++++++++ src/google/adk/tools/vertex_ai_search_tool.py | 8 ++++++ .../test_bigquery_agent_analytics_plugin.py | 1 - 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/google/adk/agents/llm_agent.py b/src/google/adk/agents/llm_agent.py index 0d9936b2efb..c758f66053d 100644 --- a/src/google/adk/agents/llm_agent.py +++ b/src/google/adk/agents/llm_agent.py @@ -167,10 +167,19 @@ async def _convert_tool_union_to_tools( # other tools. # TODO: Remove once the workaround is no longer needed. if multiple_tools and isinstance(tool_union, VertexAiSearchTool): - from ..tools.discovery_engine_search_tool import DiscoveryEngineSearchTool - vais_tool = tool_union if vais_tool.bypass_multi_tools_limit: + # Imported lazily and only on the bypass path: the Discovery Engine + # client is an optional dependency, and agents that leave the flag off + # must not be forced to install it. + try: + from ..tools.discovery_engine_search_tool import DiscoveryEngineSearchTool + except ImportError as e: + raise ImportError( + 'bypass_multi_tools_limit=True requires the Discovery Engine' + ' client. Install it with `pip install google-adk[gcp]`.' + ) from e + return [ DiscoveryEngineSearchTool( data_store_id=vais_tool.data_store_id, @@ -178,6 +187,8 @@ async def _convert_tool_union_to_tools( search_engine_id=vais_tool.search_engine_id, filter=vais_tool.filter, max_results=vais_tool.max_results, + name=vais_tool._name_override, + description=vais_tool._description_override, ) ] from ..workflow._base_node import BaseNode diff --git a/src/google/adk/tools/discovery_engine_search_tool.py b/src/google/adk/tools/discovery_engine_search_tool.py index cba2a9cdbf3..d9d547a18c7 100644 --- a/src/google/adk/tools/discovery_engine_search_tool.py +++ b/src/google/adk/tools/discovery_engine_search_tool.py @@ -28,6 +28,7 @@ import google.auth from google.cloud import discoveryengine_v1beta as discoveryengine from google.genai import types +from typing_extensions import override from ..utils._mtls_utils import get_api_endpoint from .function_tool import FunctionTool @@ -144,6 +145,8 @@ def __init__( *, search_result_mode: Optional[SearchResultMode] = None, location: Optional[str] = None, + name: Optional[str] = None, + description: Optional[str] = None, ): """Initializes the DiscoveryEngineSearchTool. @@ -164,8 +167,16 @@ def __init__( location: Optional endpoint location override. Examples: "global", "us", "eu". If not specified, location is inferred from `data_store_id` or `search_engine_id` and defaults to "global". + name: Optional override for the tool name advertised to the model. + Defaults to the function name, "discovery_engine_search". + description: Optional override for the tool description advertised to + the model. Defaults to the function docstring. """ super().__init__(self.discovery_engine_search) + if name: + self.name = name + if description: + self.description = description if (data_store_id is None and search_engine_id is None) or ( data_store_id is not None and search_engine_id is not None ): @@ -200,6 +211,22 @@ def __init__( credentials=credentials, client_options=options ) + @override + def _get_declaration(self) -> Optional[types.FunctionDeclaration]: + # FunctionTool builds the declaration from `self.func`, ignoring + # `self.name`. LlmRequest.append_tools advertises `declaration.name` to the + # model but keys dispatch on `tool.name`; if the two diverge the model emits + # a call that cannot be resolved. Keep them in sync here. + # + # Safe to mutate: FunctionTool._get_declaration() already returns + # `declaration.model_copy(deep=True)`, so the shared + # `_build_declaration_cached` lru_cache entry is not touched. + declaration = super()._get_declaration() + if declaration is not None: + declaration.name = self.name + declaration.description = self.description + return declaration + def discovery_engine_search( self, query: str, diff --git a/src/google/adk/tools/vertex_ai_search_tool.py b/src/google/adk/tools/vertex_ai_search_tool.py index 17895df1ef6..c92a888e63d 100644 --- a/src/google/adk/tools/vertex_ai_search_tool.py +++ b/src/google/adk/tools/vertex_ai_search_tool.py @@ -71,6 +71,8 @@ def __init__( filter: Optional[str] = None, max_results: Optional[int] = None, bypass_multi_tools_limit: bool = False, + name: Optional[str] = None, + description: Optional[str] = None, ): """Initializes the Vertex AI Search tool. @@ -86,6 +88,10 @@ def __init__( max_results: The maximum number of results to return. bypass_multi_tools_limit: Whether to bypass the multi tools limitation, so that the tool can be used with other tools in the same agent. + name: Optional tool name. Only takes effect when + bypass_multi_tools_limit=True, where it renames the substituted + DiscoveryEngineSearchTool. Ignored on the built-in grounding path. + description: Optional tool description. Raises: ValueError: If both data_store_id and search_engine_id are not specified @@ -109,6 +115,8 @@ def __init__( self.filter = filter self.max_results = max_results self.bypass_multi_tools_limit = bypass_multi_tools_limit + self._name_override = name + self._description_override = description def _build_vertex_ai_search_config( self, readonly_context: ReadonlyContext diff --git a/tests/unittests/plugins/test_bigquery_agent_analytics_plugin.py b/tests/unittests/plugins/test_bigquery_agent_analytics_plugin.py index ea835618623..854d47b031e 100644 --- a/tests/unittests/plugins/test_bigquery_agent_analytics_plugin.py +++ b/tests/unittests/plugins/test_bigquery_agent_analytics_plugin.py @@ -12200,7 +12200,6 @@ async def test_multiple_plugins_share_background_loop_state_with_equivalent_cred creds1 ) - plugin1 = bigquery_agent_analytics_plugin.BigQueryAgentAnalyticsPlugin( project_id=PROJECT_ID, dataset_id=DATASET_ID,