google-adk 1.2.1__py3-none-any.whl → 1.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. google/adk/a2a/__init__.py +13 -0
  2. google/adk/a2a/converters/__init__.py +13 -0
  3. google/adk/a2a/converters/part_converter.py +177 -0
  4. google/adk/agents/invocation_context.py +2 -0
  5. google/adk/agents/llm_agent.py +1 -6
  6. google/adk/agents/run_config.py +11 -0
  7. google/adk/auth/auth_credential.py +4 -0
  8. google/adk/auth/auth_handler.py +22 -96
  9. google/adk/auth/auth_preprocessor.py +3 -3
  10. google/adk/auth/auth_tool.py +46 -0
  11. google/adk/auth/credential_manager.py +261 -0
  12. google/adk/auth/credential_service/__init__.py +13 -0
  13. google/adk/auth/credential_service/base_credential_service.py +75 -0
  14. google/adk/auth/credential_service/in_memory_credential_service.py +64 -0
  15. google/adk/auth/exchanger/__init__.py +21 -0
  16. google/adk/auth/exchanger/base_credential_exchanger.py +57 -0
  17. google/adk/auth/exchanger/credential_exchanger_registry.py +58 -0
  18. google/adk/auth/exchanger/oauth2_credential_exchanger.py +104 -0
  19. google/adk/auth/oauth2_credential_util.py +107 -0
  20. google/adk/auth/refresher/__init__.py +21 -0
  21. google/adk/auth/refresher/base_credential_refresher.py +74 -0
  22. google/adk/auth/refresher/credential_refresher_registry.py +59 -0
  23. google/adk/auth/refresher/oauth2_credential_refresher.py +126 -0
  24. google/adk/cli/agent_graph.py +34 -32
  25. google/adk/cli/browser/index.html +2 -2
  26. google/adk/cli/browser/main-JAAWEV7F.js +92 -0
  27. google/adk/cli/browser/polyfills-B6TNHZQ6.js +17 -0
  28. google/adk/cli/cli.py +10 -0
  29. google/adk/cli/cli_deploy.py +80 -21
  30. google/adk/cli/cli_tools_click.py +132 -61
  31. google/adk/cli/fast_api.py +46 -41
  32. google/adk/cli/utils/agent_loader.py +15 -2
  33. google/adk/code_executors/container_code_executor.py +10 -6
  34. google/adk/code_executors/vertex_ai_code_executor.py +8 -2
  35. google/adk/evaluation/_eval_set_results_manager_utils.py +44 -0
  36. google/adk/evaluation/_eval_sets_manager_utils.py +108 -0
  37. google/adk/evaluation/eval_metrics.py +0 -5
  38. google/adk/evaluation/eval_result.py +12 -7
  39. google/adk/evaluation/eval_set_results_manager.py +6 -1
  40. google/adk/evaluation/gcs_eval_set_results_manager.py +121 -0
  41. google/adk/evaluation/gcs_eval_sets_manager.py +196 -0
  42. google/adk/evaluation/local_eval_set_results_manager.py +6 -18
  43. google/adk/evaluation/local_eval_sets_manager.py +27 -78
  44. google/adk/flows/llm_flows/basic.py +9 -0
  45. google/adk/flows/llm_flows/functions.py +1 -2
  46. google/adk/models/anthropic_llm.py +1 -1
  47. google/adk/models/gemini_llm_connection.py +2 -0
  48. google/adk/models/google_llm.py +57 -16
  49. google/adk/models/lite_llm.py +2 -1
  50. google/adk/platform/__init__.py +13 -0
  51. google/adk/platform/internal/__init__.py +15 -0
  52. google/adk/platform/internal/thread.py +30 -0
  53. google/adk/platform/thread.py +31 -0
  54. google/adk/runners.py +8 -2
  55. google/adk/sessions/in_memory_session_service.py +12 -1
  56. google/adk/sessions/vertex_ai_session_service.py +71 -50
  57. google/adk/tools/__init__.py +2 -0
  58. google/adk/tools/_automatic_function_calling_util.py +1 -0
  59. google/adk/tools/_forwarding_artifact_service.py +96 -0
  60. google/adk/tools/_function_parameter_parse_util.py +1 -0
  61. google/adk/tools/agent_tool.py +5 -39
  62. google/adk/tools/application_integration_tool/integration_connector_tool.py +2 -2
  63. google/adk/tools/authenticated_function_tool.py +107 -0
  64. google/adk/tools/base_authenticated_tool.py +107 -0
  65. google/adk/tools/bigquery/bigquery_credentials.py +6 -4
  66. google/adk/tools/bigquery/bigquery_tool.py +22 -9
  67. google/adk/tools/bigquery/bigquery_toolset.py +9 -3
  68. google/adk/tools/bigquery/client.py +7 -3
  69. google/adk/tools/bigquery/config.py +46 -0
  70. google/adk/tools/bigquery/metadata_tool.py +114 -91
  71. google/adk/tools/bigquery/query_tool.py +141 -23
  72. google/adk/tools/google_api_tool/googleapi_to_openapi_converter.py +7 -4
  73. google/adk/tools/google_search_tool.py +0 -1
  74. google/adk/tools/mcp_tool/__init__.py +6 -0
  75. google/adk/tools/mcp_tool/mcp_session_manager.py +271 -149
  76. google/adk/tools/mcp_tool/mcp_tool.py +73 -22
  77. google/adk/tools/mcp_tool/mcp_toolset.py +32 -29
  78. google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +3 -3
  79. google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py +55 -33
  80. google/adk/tools/retrieval/files_retrieval.py +7 -1
  81. google/adk/tools/url_context_tool.py +61 -0
  82. google/adk/tools/vertex_ai_search_tool.py +13 -2
  83. google/adk/utils/feature_decorator.py +175 -0
  84. google/adk/version.py +1 -1
  85. {google_adk-1.2.1.dist-info → google_adk-1.4.1.dist-info}/METADATA +10 -2
  86. {google_adk-1.2.1.dist-info → google_adk-1.4.1.dist-info}/RECORD +89 -59
  87. google/adk/cli/browser/main-CS5OLUMF.js +0 -91
  88. google/adk/cli/browser/polyfills-FFHMD2TL.js +0 -17
  89. {google_adk-1.2.1.dist-info → google_adk-1.4.1.dist-info}/WHEEL +0 -0
  90. {google_adk-1.2.1.dist-info → google_adk-1.4.1.dist-info}/entry_points.txt +0 -0
  91. {google_adk-1.2.1.dist-info → google_adk-1.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -22,13 +22,16 @@ from typing import TextIO
22
22
  from typing import Union
23
23
 
24
24
  from ...agents.readonly_context import ReadonlyContext
25
+ from ...auth.auth_credential import AuthCredential
26
+ from ...auth.auth_schemes import AuthScheme
25
27
  from ..base_tool import BaseTool
26
28
  from ..base_toolset import BaseToolset
27
29
  from ..base_toolset import ToolPredicate
28
30
  from .mcp_session_manager import MCPSessionManager
29
31
  from .mcp_session_manager import retry_on_closed_resource
30
- from .mcp_session_manager import SseServerParams
31
- from .mcp_session_manager import StreamableHTTPServerParams
32
+ from .mcp_session_manager import SseConnectionParams
33
+ from .mcp_session_manager import StdioConnectionParams
34
+ from .mcp_session_manager import StreamableHTTPConnectionParams
32
35
 
33
36
  # Attempt to import MCP Tool from the MCP library, and hints user to upgrade
34
37
  # their Python version to 3.10 if it fails.
@@ -85,23 +88,34 @@ class MCPToolset(BaseToolset):
85
88
  def __init__(
86
89
  self,
87
90
  *,
88
- connection_params: (
89
- StdioServerParameters | SseServerParams | StreamableHTTPServerParams
90
- ),
91
+ connection_params: Union[
92
+ StdioServerParameters,
93
+ StdioConnectionParams,
94
+ SseConnectionParams,
95
+ StreamableHTTPConnectionParams,
96
+ ],
91
97
  tool_filter: Optional[Union[ToolPredicate, List[str]]] = None,
92
98
  errlog: TextIO = sys.stderr,
99
+ auth_scheme: Optional[AuthScheme] = None,
100
+ auth_credential: Optional[AuthCredential] = None,
93
101
  ):
94
102
  """Initializes the MCPToolset.
95
103
 
96
104
  Args:
97
105
  connection_params: The connection parameters to the MCP server. Can be:
98
- `StdioServerParameters` for using local mcp server (e.g. using `npx` or
99
- `python3`); or `SseServerParams` for a local/remote SSE server; or
100
- `StreamableHTTPServerParams` for local/remote Streamable http server.
101
- tool_filter: Optional filter to select specific tools. Can be either:
102
- - A list of tool names to include
103
- - A ToolPredicate function for custom filtering logic
106
+ `StdioConnectionParams` for using local mcp server (e.g. using `npx` or
107
+ `python3`); or `SseConnectionParams` for a local/remote SSE server; or
108
+ `StreamableHTTPConnectionParams` for local/remote Streamable http
109
+ server. Note, `StdioServerParameters` is also supported for using local
110
+ mcp server (e.g. using `npx` or `python3` ), but it does not support
111
+ timeout, and we recommend to use `StdioConnectionParams` instead when
112
+ timeout is needed.
113
+ tool_filter: Optional filter to select specific tools. Can be either: - A
114
+ list of tool names to include - A ToolPredicate function for custom
115
+ filtering logic
104
116
  errlog: TextIO stream for error logging.
117
+ auth_scheme: The auth scheme of the tool for tool calling
118
+ auth_credential: The auth credential of the tool for tool calling
105
119
  """
106
120
  super().__init__(tool_filter=tool_filter)
107
121
 
@@ -116,10 +130,10 @@ class MCPToolset(BaseToolset):
116
130
  connection_params=self._connection_params,
117
131
  errlog=self._errlog,
118
132
  )
133
+ self._auth_scheme = auth_scheme
134
+ self._auth_credential = auth_credential
119
135
 
120
- self._session = None
121
-
122
- @retry_on_closed_resource("_reinitialize_session")
136
+ @retry_on_closed_resource
123
137
  async def get_tools(
124
138
  self,
125
139
  readonly_context: Optional[ReadonlyContext] = None,
@@ -134,11 +148,10 @@ class MCPToolset(BaseToolset):
134
148
  List[BaseTool]: A list of tools available under the specified context.
135
149
  """
136
150
  # Get session from session manager
137
- if not self._session:
138
- self._session = await self._mcp_session_manager.create_session()
151
+ session = await self._mcp_session_manager.create_session()
139
152
 
140
153
  # Fetch available tools from the MCP server
141
- tools_response: ListToolsResult = await self._session.list_tools()
154
+ tools_response: ListToolsResult = await session.list_tools()
142
155
 
143
156
  # Apply filtering based on context and tool_filter
144
157
  tools = []
@@ -146,20 +159,14 @@ class MCPToolset(BaseToolset):
146
159
  mcp_tool = MCPTool(
147
160
  mcp_tool=tool,
148
161
  mcp_session_manager=self._mcp_session_manager,
162
+ auth_scheme=self._auth_scheme,
163
+ auth_credential=self._auth_credential,
149
164
  )
150
165
 
151
166
  if self._is_tool_selected(mcp_tool, readonly_context):
152
167
  tools.append(mcp_tool)
153
168
  return tools
154
169
 
155
- async def _reinitialize_session(self):
156
- """Reinitializes the session when connection is lost."""
157
- # Close the old session and clear cache
158
- await self._mcp_session_manager.close()
159
- self._session = await self._mcp_session_manager.create_session()
160
-
161
- # Tools will be reloaded on next get_tools call
162
-
163
170
  async def close(self) -> None:
164
171
  """Performs cleanup and releases resources held by the toolset.
165
172
 
@@ -172,7 +179,3 @@ class MCPToolset(BaseToolset):
172
179
  except Exception as e:
173
180
  # Log the error but don't re-raise to avoid blocking shutdown
174
181
  print(f"Warning: Error during MCPToolset cleanup: {e}", file=self._errlog)
175
- finally:
176
- # Clear cached tools
177
- self._tools_cache = None
178
- self._tools_loaded = False
@@ -345,9 +345,9 @@ class RestApiTool(BaseTool):
345
345
  async def run_async(
346
346
  self, *, args: dict[str, Any], tool_context: Optional[ToolContext]
347
347
  ) -> Dict[str, Any]:
348
- return self.call(args=args, tool_context=tool_context)
348
+ return await self.call(args=args, tool_context=tool_context)
349
349
 
350
- def call(
350
+ async def call(
351
351
  self, *, args: dict[str, Any], tool_context: Optional[ToolContext]
352
352
  ) -> Dict[str, Any]:
353
353
  """Executes the REST API call.
@@ -364,7 +364,7 @@ class RestApiTool(BaseTool):
364
364
  tool_auth_handler = ToolAuthHandler.from_tool_context(
365
365
  tool_context, self.auth_scheme, self.auth_credential
366
366
  )
367
- auth_result = tool_auth_handler.prepare_auth_credentials()
367
+ auth_result = await tool_auth_handler.prepare_auth_credentials()
368
368
  auth_state, auth_scheme, auth_credential = (
369
369
  auth_result.state,
370
370
  auth_result.auth_scheme,
@@ -12,12 +12,12 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from __future__ import annotations
15
16
 
16
17
  import logging
17
18
  from typing import Literal
18
19
  from typing import Optional
19
20
 
20
- from fastapi.encoders import jsonable_encoder
21
21
  from pydantic import BaseModel
22
22
 
23
23
  from ....auth.auth_credential import AuthCredential
@@ -25,6 +25,7 @@ from ....auth.auth_credential import AuthCredentialTypes
25
25
  from ....auth.auth_schemes import AuthScheme
26
26
  from ....auth.auth_schemes import AuthSchemeType
27
27
  from ....auth.auth_tool import AuthConfig
28
+ from ....auth.refresher.oauth2_credential_refresher import OAuth2CredentialRefresher
28
29
  from ...tool_context import ToolContext
29
30
  from ..auth.credential_exchangers.auto_auth_credential_exchanger import AutoAuthCredentialExchanger
30
31
  from ..auth.credential_exchangers.base_credential_exchanger import AuthCredentialMissingError
@@ -95,10 +96,9 @@ class ToolContextCredentialStore:
95
96
  auth_credential: Optional[AuthCredential],
96
97
  ):
97
98
  if self.tool_context:
98
- serializable_credential = jsonable_encoder(
99
- auth_credential, exclude_none=True
99
+ self.tool_context.state[key] = auth_credential.model_dump(
100
+ exclude_none=True
100
101
  )
101
- self.tool_context.state[key] = serializable_credential
102
102
 
103
103
  def remove_credential(self, key: str):
104
104
  del self.tool_context.state[key]
@@ -146,20 +146,22 @@ class ToolAuthHandler:
146
146
  credential_store,
147
147
  )
148
148
 
149
- def _handle_existing_credential(
149
+ async def _get_existing_credential(
150
150
  self,
151
- ) -> Optional[AuthPreparationResult]:
151
+ ) -> Optional[AuthCredential]:
152
152
  """Checks for and returns an existing, exchanged credential."""
153
153
  if self.credential_store:
154
154
  existing_credential = self.credential_store.get_credential(
155
155
  self.auth_scheme, self.auth_credential
156
156
  )
157
157
  if existing_credential:
158
- return AuthPreparationResult(
159
- state="done",
160
- auth_scheme=self.auth_scheme,
161
- auth_credential=existing_credential,
162
- )
158
+ if existing_credential.oauth2:
159
+ refresher = OAuth2CredentialRefresher()
160
+ if await refresher.is_refresh_needed(existing_credential):
161
+ existing_credential = await refresher.refresh(
162
+ existing_credential, self.auth_scheme
163
+ )
164
+ return existing_credential
163
165
  return None
164
166
 
165
167
  def _exchange_credential(
@@ -223,7 +225,17 @@ class ToolAuthHandler:
223
225
  )
224
226
  )
225
227
 
226
- def prepare_auth_credentials(
228
+ def _external_exchange_required(self, credential) -> bool:
229
+ return (
230
+ credential.auth_type
231
+ in (
232
+ AuthCredentialTypes.OAUTH2,
233
+ AuthCredentialTypes.OPEN_ID_CONNECT,
234
+ )
235
+ and not credential.oauth2.access_token
236
+ )
237
+
238
+ async def prepare_auth_credentials(
227
239
  self,
228
240
  ) -> AuthPreparationResult:
229
241
  """Prepares authentication credentials, handling exchange and user interaction."""
@@ -233,31 +245,41 @@ class ToolAuthHandler:
233
245
  return AuthPreparationResult(state="done")
234
246
 
235
247
  # Check for existing credential.
236
- existing_result = self._handle_existing_credential()
237
- if existing_result:
238
- return existing_result
248
+ existing_credential = await self._get_existing_credential()
239
249
 
250
+ credential = existing_credential or self.auth_credential
240
251
  # fetch credential from adk framework
241
252
  # Some auth scheme like OAuth2 AuthCode & OpenIDConnect may require
242
253
  # multi-step exchange:
243
254
  # client_id , client_secret -> auth_uri -> auth_code -> access_token
244
- # -> bearer token
245
255
  # adk framework supports exchange access_token already
246
- fetched_credential = self._get_auth_response() or self.auth_credential
247
-
248
- exchanged_credential = self._exchange_credential(fetched_credential)
256
+ # for other credential, adk can also get back the credential directly
257
+ if not credential or self._external_exchange_required(credential):
258
+ credential = self._get_auth_response()
259
+ # store fetched credential
260
+ if credential:
261
+ self._store_credential(credential)
262
+ else:
263
+ self._request_credential()
264
+ return AuthPreparationResult(
265
+ state="pending",
266
+ auth_scheme=self.auth_scheme,
267
+ auth_credential=self.auth_credential,
268
+ )
249
269
 
250
- if exchanged_credential:
251
- self._store_credential(exchanged_credential)
252
- return AuthPreparationResult(
253
- state="done",
254
- auth_scheme=self.auth_scheme,
255
- auth_credential=exchanged_credential,
256
- )
257
- else:
258
- self._request_credential()
259
- return AuthPreparationResult(
260
- state="pending",
261
- auth_scheme=self.auth_scheme,
262
- auth_credential=self.auth_credential,
263
- )
270
+ # here exchangers are doing two different thing:
271
+ # for service account the exchanger is doing actualy token exchange
272
+ # while for oauth2 it's actually doing the credentail conversion
273
+ # from OAuth2 credential to HTTP credentails for setting credential in
274
+ # http header
275
+ # TODO cleanup the logic:
276
+ # 1. service account token exchanger should happen before we store them in
277
+ # the token store
278
+ # 2. blow line should only do credential conversion
279
+
280
+ exchanged_credential = self._exchange_credential(credential)
281
+ return AuthPreparationResult(
282
+ state="done",
283
+ auth_scheme=self.auth_scheme,
284
+ auth_credential=exchanged_credential,
285
+ )
@@ -14,11 +14,17 @@
14
14
 
15
15
  """Provides data for the agent."""
16
16
 
17
+ from __future__ import annotations
18
+
19
+ import logging
20
+
17
21
  from llama_index.core import SimpleDirectoryReader
18
22
  from llama_index.core import VectorStoreIndex
19
23
 
20
24
  from .llama_index_retrieval import LlamaIndexRetrieval
21
25
 
26
+ logger = logging.getLogger("google_adk." + __name__)
27
+
22
28
 
23
29
  class FilesRetrieval(LlamaIndexRetrieval):
24
30
 
@@ -26,7 +32,7 @@ class FilesRetrieval(LlamaIndexRetrieval):
26
32
 
27
33
  self.input_dir = input_dir
28
34
 
29
- print(f'Loading data from {input_dir}')
35
+ logger.info("Loading data from %s", input_dir)
30
36
  retriever = VectorStoreIndex.from_documents(
31
37
  SimpleDirectoryReader(input_dir).load_data()
32
38
  ).as_retriever()
@@ -0,0 +1,61 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import TYPE_CHECKING
18
+
19
+ from google.genai import types
20
+ from typing_extensions import override
21
+
22
+ from .base_tool import BaseTool
23
+ from .tool_context import ToolContext
24
+
25
+ if TYPE_CHECKING:
26
+ from ..models import LlmRequest
27
+
28
+
29
+ class UrlContextTool(BaseTool):
30
+ """A built-in tool that is automatically invoked by Gemini 2 models to retrieve content from the URLs and use that content to inform and shape its response.
31
+
32
+ This tool operates internally within the model and does not require or perform
33
+ local code execution.
34
+ """
35
+
36
+ def __init__(self):
37
+ # Name and description are not used because this is a model built-in tool.
38
+ super().__init__(name='url_context', description='url_context')
39
+
40
+ @override
41
+ async def process_llm_request(
42
+ self,
43
+ *,
44
+ tool_context: ToolContext,
45
+ llm_request: LlmRequest,
46
+ ) -> None:
47
+ llm_request.config = llm_request.config or types.GenerateContentConfig()
48
+ llm_request.config.tools = llm_request.config.tools or []
49
+ if llm_request.model and 'gemini-1' in llm_request.model:
50
+ raise ValueError('Url context tool can not be used in Gemini 1.x.')
51
+ elif llm_request.model and 'gemini-2' in llm_request.model:
52
+ llm_request.config.tools.append(
53
+ types.Tool(url_context=types.UrlContext())
54
+ )
55
+ else:
56
+ raise ValueError(
57
+ f'Url context tool is not supported for model {llm_request.model}'
58
+ )
59
+
60
+
61
+ url_context = UrlContextTool()
@@ -39,6 +39,9 @@ class VertexAiSearchTool(BaseTool):
39
39
  self,
40
40
  *,
41
41
  data_store_id: Optional[str] = None,
42
+ data_store_specs: Optional[
43
+ list[types.VertexAISearchDataStoreSpec]
44
+ ] = None,
42
45
  search_engine_id: Optional[str] = None,
43
46
  filter: Optional[str] = None,
44
47
  max_results: Optional[int] = None,
@@ -49,6 +52,8 @@ class VertexAiSearchTool(BaseTool):
49
52
  data_store_id: The Vertex AI search data store resource ID in the format
50
53
  of
51
54
  "projects/{project}/locations/{location}/collections/{collection}/dataStores/{dataStore}".
55
+ data_store_specs: Specifications that define the specific DataStores to be
56
+ searched. It should only be set if engine is used.
52
57
  search_engine_id: The Vertex AI search engine resource ID in the format of
53
58
  "projects/{project}/locations/{location}/collections/{collection}/engines/{engine}".
54
59
 
@@ -64,7 +69,12 @@ class VertexAiSearchTool(BaseTool):
64
69
  raise ValueError(
65
70
  'Either data_store_id or search_engine_id must be specified.'
66
71
  )
72
+ if data_store_specs is not None and search_engine_id is None:
73
+ raise ValueError(
74
+ 'search_engine_id must be specified if data_store_specs is specified.'
75
+ )
67
76
  self.data_store_id = data_store_id
77
+ self.data_store_specs = data_store_specs
68
78
  self.search_engine_id = search_engine_id
69
79
  self.filter = filter
70
80
  self.max_results = max_results
@@ -76,8 +86,8 @@ class VertexAiSearchTool(BaseTool):
76
86
  tool_context: ToolContext,
77
87
  llm_request: LlmRequest,
78
88
  ) -> None:
79
- if llm_request.model and llm_request.model.startswith('gemini-'):
80
- if llm_request.model.startswith('gemini-1') and llm_request.config.tools:
89
+ if llm_request.model and 'gemini-' in llm_request.model:
90
+ if 'gemini-1' in llm_request.model and llm_request.config.tools:
81
91
  raise ValueError(
82
92
  'Vertex AI search tool can not be used with other tools in Gemini'
83
93
  ' 1.x.'
@@ -89,6 +99,7 @@ class VertexAiSearchTool(BaseTool):
89
99
  retrieval=types.Retrieval(
90
100
  vertex_ai_search=types.VertexAISearch(
91
101
  datastore=self.data_store_id,
102
+ data_store_specs=self.data_store_specs,
92
103
  engine=self.search_engine_id,
93
104
  filter=self.filter,
94
105
  max_results=self.max_results,
@@ -0,0 +1,175 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ import functools
18
+ import os
19
+ from typing import Callable
20
+ from typing import cast
21
+ from typing import Optional
22
+ from typing import TypeVar
23
+ from typing import Union
24
+ import warnings
25
+
26
+ from dotenv import load_dotenv
27
+
28
+ T = TypeVar("T", bound=Union[Callable, type])
29
+
30
+
31
+ def _make_feature_decorator(
32
+ *,
33
+ label: str,
34
+ default_message: str,
35
+ block_usage: bool = False,
36
+ bypass_env_var: Optional[str] = None,
37
+ ) -> Callable:
38
+ def decorator_factory(message_or_obj=None):
39
+ # Case 1: Used as @decorator without parentheses
40
+ # message_or_obj is the decorated class/function
41
+ if message_or_obj is not None and (
42
+ isinstance(message_or_obj, type) or callable(message_or_obj)
43
+ ):
44
+ return _create_decorator(
45
+ default_message, label, block_usage, bypass_env_var
46
+ )(message_or_obj)
47
+
48
+ # Case 2: Used as @decorator() with or without message
49
+ # message_or_obj is either None or a string message
50
+ message = (
51
+ message_or_obj if isinstance(message_or_obj, str) else default_message
52
+ )
53
+ return _create_decorator(message, label, block_usage, bypass_env_var)
54
+
55
+ return decorator_factory
56
+
57
+
58
+ def _create_decorator(
59
+ message: str, label: str, block_usage: bool, bypass_env_var: Optional[str]
60
+ ) -> Callable[[T], T]:
61
+ def decorator(obj: T) -> T:
62
+ obj_name = getattr(obj, "__name__", type(obj).__name__)
63
+ msg = f"[{label.upper()}] {obj_name}: {message}"
64
+
65
+ if isinstance(obj, type): # decorating a class
66
+ orig_init = obj.__init__
67
+
68
+ @functools.wraps(orig_init)
69
+ def new_init(self, *args, **kwargs):
70
+ # Load .env file if dotenv is available
71
+ load_dotenv()
72
+
73
+ # Check if usage should be bypassed via environment variable at call time
74
+ should_bypass = (
75
+ bypass_env_var is not None
76
+ and os.environ.get(bypass_env_var, "").lower() == "true"
77
+ )
78
+
79
+ if should_bypass:
80
+ # Bypass completely - no warning, no error
81
+ pass
82
+ elif block_usage:
83
+ raise RuntimeError(msg)
84
+ else:
85
+ warnings.warn(msg, category=UserWarning, stacklevel=2)
86
+ return orig_init(self, *args, **kwargs)
87
+
88
+ obj.__init__ = new_init # type: ignore[attr-defined]
89
+ return cast(T, obj)
90
+
91
+ elif callable(obj): # decorating a function or method
92
+
93
+ @functools.wraps(obj)
94
+ def wrapper(*args, **kwargs):
95
+ # Load .env file if dotenv is available
96
+ load_dotenv()
97
+
98
+ # Check if usage should be bypassed via environment variable at call time
99
+ should_bypass = (
100
+ bypass_env_var is not None
101
+ and os.environ.get(bypass_env_var, "").lower() == "true"
102
+ )
103
+
104
+ if should_bypass:
105
+ # Bypass completely - no warning, no error
106
+ pass
107
+ elif block_usage:
108
+ raise RuntimeError(msg)
109
+ else:
110
+ warnings.warn(msg, category=UserWarning, stacklevel=2)
111
+ return obj(*args, **kwargs)
112
+
113
+ return cast(T, wrapper)
114
+
115
+ else:
116
+ raise TypeError(
117
+ f"@{label} can only be applied to classes or callable objects"
118
+ )
119
+
120
+ return decorator
121
+
122
+
123
+ working_in_progress = _make_feature_decorator(
124
+ label="WIP",
125
+ default_message=(
126
+ "This feature is a work in progress and is not working completely. ADK"
127
+ " users are not supposed to use it."
128
+ ),
129
+ block_usage=True,
130
+ bypass_env_var="ADK_ALLOW_WIP_FEATURES",
131
+ )
132
+ """Mark a class or function as a work in progress.
133
+
134
+ By default, decorated functions/classes will raise RuntimeError when used.
135
+ Set ADK_ALLOW_WIP_FEATURES=true environment variable to bypass this restriction.
136
+ ADK users are not supposed to set this environment variable.
137
+
138
+ Sample usage:
139
+
140
+ ```
141
+ @working_in_progress("This feature is not ready for production use.")
142
+ def my_wip_function():
143
+ pass
144
+ ```
145
+ """
146
+
147
+ experimental = _make_feature_decorator(
148
+ label="EXPERIMENTAL",
149
+ default_message=(
150
+ "This feature is experimental and may change or be removed in future"
151
+ " versions without notice. It may introduce breaking changes at any"
152
+ " time."
153
+ ),
154
+ )
155
+ """Mark a class or a function as an experimental feature.
156
+
157
+ Sample usage:
158
+
159
+ ```
160
+ # Use with default message
161
+ @experimental
162
+ class ExperimentalClass:
163
+ pass
164
+
165
+ # Use with custom message
166
+ @experimental("This API may have breaking change in the future.")
167
+ class CustomExperimentalClass:
168
+ pass
169
+
170
+ # Use with empty parentheses (same as default message)
171
+ @experimental()
172
+ def experimental_function():
173
+ pass
174
+ ```
175
+ """
google/adk/version.py CHANGED
@@ -13,4 +13,4 @@
13
13
  # limitations under the License.
14
14
 
15
15
  # version: major.minor.patch
16
- __version__ = "1.2.1"
16
+ __version__ = "1.4.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-adk
3
- Version: 1.2.1
3
+ Version: 1.4.1
4
4
  Summary: Agent Development Kit
5
5
  Author-email: Google LLC <googleapis-packages@google.com>
6
6
  Requires-Python: >=3.9
@@ -19,6 +19,7 @@ Classifier: Operating System :: OS Independent
19
19
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
20
  Classifier: License :: OSI Approved :: Apache Software License
21
21
  License-File: LICENSE
22
+ Requires-Dist: anyio>=4.9.0;python_version>='3.10'
22
23
  Requires-Dist: authlib>=1.5.1
23
24
  Requires-Dist: click>=8.1.8
24
25
  Requires-Dist: fastapi>=0.115.0
@@ -34,12 +35,17 @@ Requires-Dist: opentelemetry-api>=1.31.0
34
35
  Requires-Dist: opentelemetry-exporter-gcp-trace>=1.9.0
35
36
  Requires-Dist: opentelemetry-sdk>=1.31.0
36
37
  Requires-Dist: pydantic>=2.0, <3.0.0
38
+ Requires-Dist: python-dateutil>=2.9.0.post0
37
39
  Requires-Dist: python-dotenv>=1.0.0
38
40
  Requires-Dist: PyYAML>=6.0.2
41
+ Requires-Dist: requests>=2.32.4
39
42
  Requires-Dist: sqlalchemy>=2.0
40
- Requires-Dist: tzlocal>=5.3
43
+ Requires-Dist: starlette>=0.46.2
41
44
  Requires-Dist: typing-extensions>=4.5, <5
45
+ Requires-Dist: tzlocal>=5.3
42
46
  Requires-Dist: uvicorn>=0.34.0
47
+ Requires-Dist: websockets>=15.0.1
48
+ Requires-Dist: a2a-sdk>=0.2.7 ; extra == "a2a" and (python_version>='3.10')
43
49
  Requires-Dist: flit>=3.10.0 ; extra == "dev"
44
50
  Requires-Dist: isort>=6.0.0 ; extra == "dev"
45
51
  Requires-Dist: pyink>=24.10.0 ; extra == "dev"
@@ -76,6 +82,7 @@ Project-URL: changelog, https://github.com/google/adk-python/blob/main/CHANGELOG
76
82
  Project-URL: documentation, https://google.github.io/adk-docs/
77
83
  Project-URL: homepage, https://google.github.io/adk-docs/
78
84
  Project-URL: repository, https://github.com/google/adk-python
85
+ Provides-Extra: a2a
79
86
  Provides-Extra: dev
80
87
  Provides-Extra: docs
81
88
  Provides-Extra: eval
@@ -87,6 +94,7 @@ Provides-Extra: test
87
94
  [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
88
95
  [![Python Unit Tests](https://github.com/google/adk-python/actions/workflows/python-unit-tests.yml/badge.svg)](https://github.com/google/adk-python/actions/workflows/python-unit-tests.yml)
89
96
  [![r/agentdevelopmentkit](https://img.shields.io/badge/Reddit-r%2Fagentdevelopmentkit-FF4500?style=flat&logo=reddit&logoColor=white)](https://www.reddit.com/r/agentdevelopmentkit/)
97
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/google/adk-python)
90
98
 
91
99
  <html>
92
100
  <h2 align="center">