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.
- google/adk/a2a/__init__.py +13 -0
- google/adk/a2a/converters/__init__.py +13 -0
- google/adk/a2a/converters/part_converter.py +177 -0
- google/adk/agents/invocation_context.py +2 -0
- google/adk/agents/llm_agent.py +1 -6
- google/adk/agents/run_config.py +11 -0
- google/adk/auth/auth_credential.py +4 -0
- google/adk/auth/auth_handler.py +22 -96
- google/adk/auth/auth_preprocessor.py +3 -3
- google/adk/auth/auth_tool.py +46 -0
- google/adk/auth/credential_manager.py +261 -0
- google/adk/auth/credential_service/__init__.py +13 -0
- google/adk/auth/credential_service/base_credential_service.py +75 -0
- google/adk/auth/credential_service/in_memory_credential_service.py +64 -0
- google/adk/auth/exchanger/__init__.py +21 -0
- google/adk/auth/exchanger/base_credential_exchanger.py +57 -0
- google/adk/auth/exchanger/credential_exchanger_registry.py +58 -0
- google/adk/auth/exchanger/oauth2_credential_exchanger.py +104 -0
- google/adk/auth/oauth2_credential_util.py +107 -0
- google/adk/auth/refresher/__init__.py +21 -0
- google/adk/auth/refresher/base_credential_refresher.py +74 -0
- google/adk/auth/refresher/credential_refresher_registry.py +59 -0
- google/adk/auth/refresher/oauth2_credential_refresher.py +126 -0
- google/adk/cli/agent_graph.py +34 -32
- google/adk/cli/browser/index.html +2 -2
- google/adk/cli/browser/main-JAAWEV7F.js +92 -0
- google/adk/cli/browser/polyfills-B6TNHZQ6.js +17 -0
- google/adk/cli/cli.py +10 -0
- google/adk/cli/cli_deploy.py +80 -21
- google/adk/cli/cli_tools_click.py +132 -61
- google/adk/cli/fast_api.py +46 -41
- google/adk/cli/utils/agent_loader.py +15 -2
- google/adk/code_executors/container_code_executor.py +10 -6
- google/adk/code_executors/vertex_ai_code_executor.py +8 -2
- google/adk/evaluation/_eval_set_results_manager_utils.py +44 -0
- google/adk/evaluation/_eval_sets_manager_utils.py +108 -0
- google/adk/evaluation/eval_metrics.py +0 -5
- google/adk/evaluation/eval_result.py +12 -7
- google/adk/evaluation/eval_set_results_manager.py +6 -1
- google/adk/evaluation/gcs_eval_set_results_manager.py +121 -0
- google/adk/evaluation/gcs_eval_sets_manager.py +196 -0
- google/adk/evaluation/local_eval_set_results_manager.py +6 -18
- google/adk/evaluation/local_eval_sets_manager.py +27 -78
- google/adk/flows/llm_flows/basic.py +9 -0
- google/adk/flows/llm_flows/functions.py +1 -2
- google/adk/models/anthropic_llm.py +1 -1
- google/adk/models/gemini_llm_connection.py +2 -0
- google/adk/models/google_llm.py +57 -16
- google/adk/models/lite_llm.py +2 -1
- google/adk/platform/__init__.py +13 -0
- google/adk/platform/internal/__init__.py +15 -0
- google/adk/platform/internal/thread.py +30 -0
- google/adk/platform/thread.py +31 -0
- google/adk/runners.py +8 -2
- google/adk/sessions/in_memory_session_service.py +12 -1
- google/adk/sessions/vertex_ai_session_service.py +71 -50
- google/adk/tools/__init__.py +2 -0
- google/adk/tools/_automatic_function_calling_util.py +1 -0
- google/adk/tools/_forwarding_artifact_service.py +96 -0
- google/adk/tools/_function_parameter_parse_util.py +1 -0
- google/adk/tools/agent_tool.py +5 -39
- google/adk/tools/application_integration_tool/integration_connector_tool.py +2 -2
- google/adk/tools/authenticated_function_tool.py +107 -0
- google/adk/tools/base_authenticated_tool.py +107 -0
- google/adk/tools/bigquery/bigquery_credentials.py +6 -4
- google/adk/tools/bigquery/bigquery_tool.py +22 -9
- google/adk/tools/bigquery/bigquery_toolset.py +9 -3
- google/adk/tools/bigquery/client.py +7 -3
- google/adk/tools/bigquery/config.py +46 -0
- google/adk/tools/bigquery/metadata_tool.py +114 -91
- google/adk/tools/bigquery/query_tool.py +141 -23
- google/adk/tools/google_api_tool/googleapi_to_openapi_converter.py +7 -4
- google/adk/tools/google_search_tool.py +0 -1
- google/adk/tools/mcp_tool/__init__.py +6 -0
- google/adk/tools/mcp_tool/mcp_session_manager.py +271 -149
- google/adk/tools/mcp_tool/mcp_tool.py +73 -22
- google/adk/tools/mcp_tool/mcp_toolset.py +32 -29
- google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +3 -3
- google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py +55 -33
- google/adk/tools/retrieval/files_retrieval.py +7 -1
- google/adk/tools/url_context_tool.py +61 -0
- google/adk/tools/vertex_ai_search_tool.py +13 -2
- google/adk/utils/feature_decorator.py +175 -0
- google/adk/version.py +1 -1
- {google_adk-1.2.1.dist-info → google_adk-1.4.1.dist-info}/METADATA +10 -2
- {google_adk-1.2.1.dist-info → google_adk-1.4.1.dist-info}/RECORD +89 -59
- google/adk/cli/browser/main-CS5OLUMF.js +0 -91
- google/adk/cli/browser/polyfills-FFHMD2TL.js +0 -17
- {google_adk-1.2.1.dist-info → google_adk-1.4.1.dist-info}/WHEEL +0 -0
- {google_adk-1.2.1.dist-info → google_adk-1.4.1.dist-info}/entry_points.txt +0 -0
- {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
|
31
|
-
from .mcp_session_manager import
|
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
|
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
|
-
`
|
99
|
-
`python3`); or `
|
100
|
-
`
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
99
|
-
|
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
|
149
|
+
async def _get_existing_credential(
|
150
150
|
self,
|
151
|
-
) -> Optional[
|
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
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
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
|
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
|
-
|
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
|
-
|
247
|
-
|
248
|
-
|
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
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
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
|
-
|
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
|
80
|
-
if
|
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
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: google-adk
|
3
|
-
Version: 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:
|
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)
|
88
95
|
[](https://github.com/google/adk-python/actions/workflows/python-unit-tests.yml)
|
89
96
|
[](https://www.reddit.com/r/agentdevelopmentkit/)
|
97
|
+
[](https://deepwiki.com/google/adk-python)
|
90
98
|
|
91
99
|
<html>
|
92
100
|
<h2 align="center">
|