google-adk 0.5.0__py3-none-any.whl → 1.1.0__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/agents/base_agent.py +76 -30
- google/adk/agents/callback_context.py +2 -6
- google/adk/agents/llm_agent.py +122 -30
- google/adk/agents/loop_agent.py +1 -1
- google/adk/agents/parallel_agent.py +7 -0
- google/adk/agents/readonly_context.py +8 -0
- google/adk/agents/run_config.py +1 -1
- google/adk/agents/sequential_agent.py +31 -0
- google/adk/agents/transcription_entry.py +4 -2
- google/adk/artifacts/gcs_artifact_service.py +1 -1
- google/adk/artifacts/in_memory_artifact_service.py +1 -1
- google/adk/auth/auth_credential.py +10 -2
- google/adk/auth/auth_preprocessor.py +7 -1
- google/adk/auth/auth_tool.py +3 -4
- google/adk/cli/agent_graph.py +5 -5
- google/adk/cli/browser/index.html +4 -4
- google/adk/cli/browser/{main-ULN5R5I5.js → main-PKDNKWJE.js} +59 -60
- google/adk/cli/browser/polyfills-B6TNHZQ6.js +17 -0
- google/adk/cli/cli.py +10 -9
- google/adk/cli/cli_deploy.py +7 -2
- google/adk/cli/cli_eval.py +109 -115
- google/adk/cli/cli_tools_click.py +179 -67
- google/adk/cli/fast_api.py +248 -197
- google/adk/cli/utils/agent_loader.py +137 -0
- google/adk/cli/utils/cleanup.py +40 -0
- google/adk/cli/utils/common.py +23 -0
- google/adk/cli/utils/evals.py +83 -0
- google/adk/cli/utils/logs.py +8 -5
- google/adk/code_executors/__init__.py +3 -1
- google/adk/code_executors/built_in_code_executor.py +52 -0
- google/adk/code_executors/code_execution_utils.py +2 -1
- google/adk/code_executors/container_code_executor.py +0 -1
- google/adk/code_executors/vertex_ai_code_executor.py +6 -8
- google/adk/evaluation/__init__.py +1 -1
- google/adk/evaluation/agent_evaluator.py +168 -128
- google/adk/evaluation/eval_case.py +104 -0
- google/adk/evaluation/eval_metrics.py +74 -0
- google/adk/evaluation/eval_result.py +86 -0
- google/adk/evaluation/eval_set.py +39 -0
- google/adk/evaluation/eval_set_results_manager.py +47 -0
- google/adk/evaluation/eval_sets_manager.py +43 -0
- google/adk/evaluation/evaluation_generator.py +88 -113
- google/adk/evaluation/evaluator.py +58 -0
- google/adk/evaluation/local_eval_set_results_manager.py +113 -0
- google/adk/evaluation/local_eval_sets_manager.py +264 -0
- google/adk/evaluation/response_evaluator.py +106 -1
- google/adk/evaluation/trajectory_evaluator.py +84 -2
- google/adk/events/event.py +6 -1
- google/adk/events/event_actions.py +6 -1
- google/adk/examples/base_example_provider.py +1 -0
- google/adk/examples/example_util.py +3 -2
- google/adk/flows/llm_flows/_code_execution.py +9 -1
- google/adk/flows/llm_flows/audio_transcriber.py +4 -3
- google/adk/flows/llm_flows/base_llm_flow.py +58 -21
- google/adk/flows/llm_flows/contents.py +3 -1
- google/adk/flows/llm_flows/functions.py +9 -8
- google/adk/flows/llm_flows/instructions.py +18 -80
- google/adk/flows/llm_flows/single_flow.py +2 -2
- google/adk/memory/__init__.py +1 -1
- google/adk/memory/_utils.py +23 -0
- google/adk/memory/base_memory_service.py +23 -21
- google/adk/memory/in_memory_memory_service.py +57 -25
- google/adk/memory/memory_entry.py +37 -0
- google/adk/memory/vertex_ai_rag_memory_service.py +38 -15
- google/adk/models/anthropic_llm.py +16 -9
- google/adk/models/base_llm.py +2 -1
- google/adk/models/base_llm_connection.py +2 -0
- google/adk/models/gemini_llm_connection.py +11 -11
- google/adk/models/google_llm.py +12 -2
- google/adk/models/lite_llm.py +80 -23
- google/adk/models/llm_response.py +16 -3
- google/adk/models/registry.py +1 -1
- google/adk/runners.py +98 -42
- google/adk/sessions/__init__.py +1 -1
- google/adk/sessions/_session_util.py +2 -1
- google/adk/sessions/base_session_service.py +6 -33
- google/adk/sessions/database_session_service.py +57 -67
- google/adk/sessions/in_memory_session_service.py +106 -24
- google/adk/sessions/session.py +3 -0
- google/adk/sessions/vertex_ai_session_service.py +44 -51
- google/adk/telemetry.py +7 -2
- google/adk/tools/__init__.py +4 -7
- google/adk/tools/_memory_entry_utils.py +30 -0
- google/adk/tools/agent_tool.py +10 -10
- google/adk/tools/apihub_tool/apihub_toolset.py +55 -74
- google/adk/tools/apihub_tool/clients/apihub_client.py +10 -3
- google/adk/tools/apihub_tool/clients/secret_client.py +1 -0
- google/adk/tools/application_integration_tool/application_integration_toolset.py +111 -85
- google/adk/tools/application_integration_tool/clients/connections_client.py +28 -1
- google/adk/tools/application_integration_tool/clients/integration_client.py +7 -5
- google/adk/tools/application_integration_tool/integration_connector_tool.py +69 -26
- google/adk/tools/base_toolset.py +96 -0
- google/adk/tools/bigquery/__init__.py +28 -0
- google/adk/tools/bigquery/bigquery_credentials.py +216 -0
- google/adk/tools/bigquery/bigquery_tool.py +116 -0
- google/adk/tools/{built_in_code_execution_tool.py → enterprise_search_tool.py} +17 -11
- google/adk/tools/function_parameter_parse_util.py +9 -2
- google/adk/tools/function_tool.py +33 -3
- google/adk/tools/get_user_choice_tool.py +1 -0
- google/adk/tools/google_api_tool/__init__.py +24 -70
- google/adk/tools/google_api_tool/google_api_tool.py +12 -6
- google/adk/tools/google_api_tool/{google_api_tool_set.py → google_api_toolset.py} +57 -55
- google/adk/tools/google_api_tool/google_api_toolsets.py +108 -0
- google/adk/tools/google_api_tool/googleapi_to_openapi_converter.py +40 -42
- google/adk/tools/google_search_tool.py +2 -2
- google/adk/tools/langchain_tool.py +96 -49
- google/adk/tools/load_memory_tool.py +14 -5
- google/adk/tools/mcp_tool/__init__.py +3 -2
- google/adk/tools/mcp_tool/conversion_utils.py +6 -2
- google/adk/tools/mcp_tool/mcp_session_manager.py +80 -69
- google/adk/tools/mcp_tool/mcp_tool.py +35 -32
- google/adk/tools/mcp_tool/mcp_toolset.py +99 -194
- google/adk/tools/openapi_tool/auth/credential_exchangers/base_credential_exchanger.py +1 -3
- google/adk/tools/openapi_tool/auth/credential_exchangers/service_account_exchanger.py +6 -7
- google/adk/tools/openapi_tool/common/common.py +5 -1
- google/adk/tools/openapi_tool/openapi_spec_parser/__init__.py +7 -2
- google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +27 -7
- google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +36 -32
- google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +11 -1
- google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py +1 -1
- google/adk/tools/preload_memory_tool.py +27 -18
- google/adk/tools/retrieval/__init__.py +1 -1
- google/adk/tools/retrieval/vertex_ai_rag_retrieval.py +1 -1
- google/adk/tools/toolbox_toolset.py +107 -0
- google/adk/tools/transfer_to_agent_tool.py +0 -1
- google/adk/utils/__init__.py +13 -0
- google/adk/utils/instructions_utils.py +131 -0
- google/adk/version.py +1 -1
- {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/METADATA +18 -19
- google_adk-1.1.0.dist-info/RECORD +200 -0
- google/adk/agents/remote_agent.py +0 -50
- google/adk/cli/browser/polyfills-FFHMD2TL.js +0 -18
- google/adk/cli/fast_api.py.orig +0 -728
- google/adk/tools/google_api_tool/google_api_tool_sets.py +0 -112
- google/adk/tools/toolbox_tool.py +0 -46
- google_adk-0.5.0.dist-info/RECORD +0 -180
- {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/WHEEL +0 -0
- {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/entry_points.txt +0 -0
- {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -12,21 +12,24 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
|
16
15
|
import logging
|
17
16
|
from typing import Any
|
18
17
|
from typing import Dict
|
19
18
|
from typing import Optional
|
19
|
+
from typing import Union
|
20
20
|
|
21
|
-
from google.adk.tools.openapi_tool.openapi_spec_parser.rest_api_tool import RestApiTool
|
22
|
-
from google.adk.tools.openapi_tool.openapi_spec_parser.rest_api_tool import to_gemini_schema
|
23
21
|
from google.genai.types import FunctionDeclaration
|
24
22
|
from typing_extensions import override
|
25
23
|
|
26
24
|
from .. import BaseTool
|
25
|
+
from ...auth.auth_credential import AuthCredential
|
26
|
+
from ...auth.auth_schemes import AuthScheme
|
27
|
+
from ..openapi_tool.openapi_spec_parser.rest_api_tool import RestApiTool
|
28
|
+
from ..openapi_tool.openapi_spec_parser.rest_api_tool import to_gemini_schema
|
29
|
+
from ..openapi_tool.openapi_spec_parser.tool_auth_handler import ToolAuthHandler
|
27
30
|
from ..tool_context import ToolContext
|
28
31
|
|
29
|
-
logger = logging.getLogger(__name__)
|
32
|
+
logger = logging.getLogger('google_adk.' + __name__)
|
30
33
|
|
31
34
|
|
32
35
|
class IntegrationConnectorTool(BaseTool):
|
@@ -56,6 +59,7 @@ class IntegrationConnectorTool(BaseTool):
|
|
56
59
|
'entity',
|
57
60
|
'operation',
|
58
61
|
'action',
|
62
|
+
'dynamic_auth_config',
|
59
63
|
]
|
60
64
|
|
61
65
|
OPTIONAL_FIELDS = [
|
@@ -75,6 +79,8 @@ class IntegrationConnectorTool(BaseTool):
|
|
75
79
|
operation: str,
|
76
80
|
action: str,
|
77
81
|
rest_api_tool: RestApiTool,
|
82
|
+
auth_scheme: Optional[Union[AuthScheme, str]] = None,
|
83
|
+
auth_credential: Optional[Union[AuthCredential, str]] = None,
|
78
84
|
):
|
79
85
|
"""Initializes the ApplicationIntegrationTool.
|
80
86
|
|
@@ -101,18 +107,20 @@ class IntegrationConnectorTool(BaseTool):
|
|
101
107
|
name=name,
|
102
108
|
description=description,
|
103
109
|
)
|
104
|
-
self.
|
105
|
-
self.
|
106
|
-
self.
|
107
|
-
self.
|
108
|
-
self.
|
109
|
-
self.
|
110
|
-
self.
|
110
|
+
self._connection_name = connection_name
|
111
|
+
self._connection_host = connection_host
|
112
|
+
self._connection_service_name = connection_service_name
|
113
|
+
self._entity = entity
|
114
|
+
self._operation = operation
|
115
|
+
self._action = action
|
116
|
+
self._rest_api_tool = rest_api_tool
|
117
|
+
self._auth_scheme = auth_scheme
|
118
|
+
self._auth_credential = auth_credential
|
111
119
|
|
112
120
|
@override
|
113
121
|
def _get_declaration(self) -> FunctionDeclaration:
|
114
122
|
"""Returns the function declaration in the Gemini Schema format."""
|
115
|
-
schema_dict = self.
|
123
|
+
schema_dict = self._rest_api_tool._operation_parser.get_json_schema()
|
116
124
|
for field in self.EXCLUDE_FIELDS:
|
117
125
|
if field in schema_dict['properties']:
|
118
126
|
del schema_dict['properties'][field]
|
@@ -126,34 +134,69 @@ class IntegrationConnectorTool(BaseTool):
|
|
126
134
|
)
|
127
135
|
return function_decl
|
128
136
|
|
137
|
+
def _prepare_dynamic_euc(self, auth_credential: AuthCredential) -> str:
|
138
|
+
if (
|
139
|
+
auth_credential
|
140
|
+
and auth_credential.http
|
141
|
+
and auth_credential.http.credentials
|
142
|
+
and auth_credential.http.credentials.token
|
143
|
+
):
|
144
|
+
return auth_credential.http.credentials.token
|
145
|
+
return None
|
146
|
+
|
129
147
|
@override
|
130
148
|
async def run_async(
|
131
149
|
self, *, args: dict[str, Any], tool_context: Optional[ToolContext]
|
132
150
|
) -> Dict[str, Any]:
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
151
|
+
|
152
|
+
tool_auth_handler = ToolAuthHandler.from_tool_context(
|
153
|
+
tool_context, self._auth_scheme, self._auth_credential
|
154
|
+
)
|
155
|
+
auth_result = tool_auth_handler.prepare_auth_credentials()
|
156
|
+
|
157
|
+
if auth_result.state == 'pending':
|
158
|
+
return {
|
159
|
+
'pending': True,
|
160
|
+
'message': 'Needs your authorization to access your data.',
|
161
|
+
}
|
162
|
+
|
163
|
+
# Attach parameters from auth into main parameters list
|
164
|
+
if auth_result.auth_credential:
|
165
|
+
# Attach parameters from auth into main parameters list
|
166
|
+
auth_credential_token = self._prepare_dynamic_euc(
|
167
|
+
auth_result.auth_credential
|
168
|
+
)
|
169
|
+
if auth_credential_token:
|
170
|
+
args['dynamic_auth_config'] = {
|
171
|
+
'oauth2_auth_code_flow.access_token': auth_credential_token
|
172
|
+
}
|
173
|
+
else:
|
174
|
+
args['dynamic_auth_config'] = {'oauth2_auth_code_flow.access_token': {}}
|
175
|
+
|
176
|
+
args['connection_name'] = self._connection_name
|
177
|
+
args['service_name'] = self._connection_service_name
|
178
|
+
args['host'] = self._connection_host
|
179
|
+
args['entity'] = self._entity
|
180
|
+
args['operation'] = self._operation
|
181
|
+
args['action'] = self._action
|
139
182
|
logger.info('Running tool: %s with args: %s', self.name, args)
|
140
|
-
return self.
|
183
|
+
return self._rest_api_tool.call(args=args, tool_context=tool_context)
|
141
184
|
|
142
185
|
def __str__(self):
|
143
186
|
return (
|
144
187
|
f'ApplicationIntegrationTool(name="{self.name}",'
|
145
188
|
f' description="{self.description}",'
|
146
|
-
f' connection_name="{self.
|
147
|
-
f' operation="{self.
|
189
|
+
f' connection_name="{self._connection_name}", entity="{self._entity}",'
|
190
|
+
f' operation="{self._operation}", action="{self._action}")'
|
148
191
|
)
|
149
192
|
|
150
193
|
def __repr__(self):
|
151
194
|
return (
|
152
195
|
f'ApplicationIntegrationTool(name="{self.name}",'
|
153
196
|
f' description="{self.description}",'
|
154
|
-
f' connection_name="{self.
|
155
|
-
f' connection_host="{self.
|
156
|
-
f' connection_service_name="{self.
|
157
|
-
f' entity="{self.
|
158
|
-
f' action="{self.
|
197
|
+
f' connection_name="{self._connection_name}",'
|
198
|
+
f' connection_host="{self._connection_host}",'
|
199
|
+
f' connection_service_name="{self._connection_service_name}",'
|
200
|
+
f' entity="{self._entity}", operation="{self._operation}",'
|
201
|
+
f' action="{self._action}", rest_api_tool={repr(self._rest_api_tool)})'
|
159
202
|
)
|
@@ -0,0 +1,96 @@
|
|
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
|
+
|
16
|
+
from abc import ABC
|
17
|
+
from abc import abstractmethod
|
18
|
+
from typing import List
|
19
|
+
from typing import Optional
|
20
|
+
from typing import Protocol
|
21
|
+
from typing import runtime_checkable
|
22
|
+
from typing import Union
|
23
|
+
|
24
|
+
from ..agents.readonly_context import ReadonlyContext
|
25
|
+
from .base_tool import BaseTool
|
26
|
+
|
27
|
+
|
28
|
+
@runtime_checkable
|
29
|
+
class ToolPredicate(Protocol):
|
30
|
+
"""Base class for a predicate that defines the interface to decide whether a
|
31
|
+
|
32
|
+
tool should be exposed to LLM. Toolset implementer could consider whether to
|
33
|
+
accept such instance in the toolset's constructor and apply the predicate in
|
34
|
+
get_tools method.
|
35
|
+
"""
|
36
|
+
|
37
|
+
def __call__(
|
38
|
+
self, tool: BaseTool, readonly_context: Optional[ReadonlyContext] = None
|
39
|
+
) -> bool:
|
40
|
+
"""Decide whether the passed-in tool should be exposed to LLM based on the
|
41
|
+
|
42
|
+
current context. True if the tool is usable by the LLM.
|
43
|
+
|
44
|
+
It's used to filter tools in the toolset.
|
45
|
+
"""
|
46
|
+
|
47
|
+
|
48
|
+
class BaseToolset(ABC):
|
49
|
+
"""Base class for toolset.
|
50
|
+
|
51
|
+
A toolset is a collection of tools that can be used by an agent.
|
52
|
+
"""
|
53
|
+
|
54
|
+
def __init__(
|
55
|
+
self, *, tool_filter: Optional[Union[ToolPredicate, List[str]]] = None
|
56
|
+
):
|
57
|
+
self.tool_filter = tool_filter
|
58
|
+
|
59
|
+
@abstractmethod
|
60
|
+
async def get_tools(
|
61
|
+
self,
|
62
|
+
readonly_context: Optional[ReadonlyContext] = None,
|
63
|
+
) -> list[BaseTool]:
|
64
|
+
"""Return all tools in the toolset based on the provided context.
|
65
|
+
|
66
|
+
Args:
|
67
|
+
readony_context (ReadonlyContext, optional): Context used to filter tools
|
68
|
+
available to the agent. If None, all tools in the toolset are returned.
|
69
|
+
|
70
|
+
Returns:
|
71
|
+
list[BaseTool]: A list of tools available under the specified context.
|
72
|
+
"""
|
73
|
+
|
74
|
+
@abstractmethod
|
75
|
+
async def close(self) -> None:
|
76
|
+
"""Performs cleanup and releases resources held by the toolset.
|
77
|
+
|
78
|
+
NOTE: This method is invoked, for example, at the end of an agent server's
|
79
|
+
lifecycle or when the toolset is no longer needed. Implementations
|
80
|
+
should ensure that any open connections, files, or other managed
|
81
|
+
resources are properly released to prevent leaks.
|
82
|
+
"""
|
83
|
+
|
84
|
+
def _is_tool_selected(
|
85
|
+
self, tool: BaseTool, readonly_context: ReadonlyContext
|
86
|
+
) -> bool:
|
87
|
+
if not self.tool_filter:
|
88
|
+
return True
|
89
|
+
|
90
|
+
if isinstance(self.tool_filter, ToolPredicate):
|
91
|
+
return self.tool_filter(tool, readonly_context)
|
92
|
+
|
93
|
+
if isinstance(self.tool_filter, list):
|
94
|
+
return tool.name in self.tool_filter
|
95
|
+
|
96
|
+
return False
|
@@ -0,0 +1,28 @@
|
|
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
|
+
"""BigQuery Tools. (Experimental)
|
16
|
+
|
17
|
+
BigQuery Tools under this module are hand crafted and customized while the tools
|
18
|
+
under google.adk.tools.google_api_tool are auto generated based on API
|
19
|
+
definition. The rationales to have customized tool are:
|
20
|
+
|
21
|
+
1. BigQuery APIs have functions overlaps and LLM can't tell what tool to use
|
22
|
+
2. BigQuery APIs have a lot of parameters with some rarely used, which are not
|
23
|
+
LLM-friendly
|
24
|
+
3. We want to provide more high-level tools like forecasting, RAG, segmentation,
|
25
|
+
etc.
|
26
|
+
4. We want to provide extra access guardrails in those tools. For example,
|
27
|
+
execute_sql can't arbitrarily mutate existing data.
|
28
|
+
"""
|
@@ -0,0 +1,216 @@
|
|
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 List
|
18
|
+
from typing import Optional
|
19
|
+
|
20
|
+
from fastapi.openapi.models import OAuth2
|
21
|
+
from fastapi.openapi.models import OAuthFlowAuthorizationCode
|
22
|
+
from fastapi.openapi.models import OAuthFlows
|
23
|
+
from google.auth.exceptions import RefreshError
|
24
|
+
from google.auth.transport.requests import Request
|
25
|
+
from google.oauth2.credentials import Credentials
|
26
|
+
from pydantic import BaseModel
|
27
|
+
from pydantic import model_validator
|
28
|
+
|
29
|
+
from ...auth import AuthConfig
|
30
|
+
from ...auth import AuthCredential
|
31
|
+
from ...auth import AuthCredentialTypes
|
32
|
+
from ...auth import OAuth2Auth
|
33
|
+
from ..tool_context import ToolContext
|
34
|
+
|
35
|
+
BIGQUERY_TOKEN_CACHE_KEY = "bigquery_token_cache"
|
36
|
+
|
37
|
+
|
38
|
+
class BigQueryCredentialsConfig(BaseModel):
|
39
|
+
"""Configuration for Google API tools. (Experimental)"""
|
40
|
+
|
41
|
+
# Configure the model to allow arbitrary types like Credentials
|
42
|
+
model_config = {"arbitrary_types_allowed": True}
|
43
|
+
|
44
|
+
credentials: Optional[Credentials] = None
|
45
|
+
"""the existing oauth credentials to use. If set,this credential will be used
|
46
|
+
for every end user, end users don't need to be involved in the oauthflow. This
|
47
|
+
field is mutually exclusive with client_id, client_secret and scopes.
|
48
|
+
Don't set this field unless you are sure this credential has the permission to
|
49
|
+
access every end user's data.
|
50
|
+
|
51
|
+
Example usage: when the agent is deployed in Google Cloud environment and
|
52
|
+
the service account (used as application default credentials) has access to
|
53
|
+
all the required BigQuery resource. Setting this credential to allow user to
|
54
|
+
access the BigQuery resource without end users going through oauth flow.
|
55
|
+
|
56
|
+
To get application default credential: `google.auth.default(...)`. See more
|
57
|
+
details in https://cloud.google.com/docs/authentication/application-default-credentials.
|
58
|
+
|
59
|
+
When the deployed environment cannot provide a pre-existing credential,
|
60
|
+
consider setting below client_id, client_secret and scope for end users to go
|
61
|
+
through oauth flow, so that agent can access the user data.
|
62
|
+
"""
|
63
|
+
client_id: Optional[str] = None
|
64
|
+
"""the oauth client ID to use."""
|
65
|
+
client_secret: Optional[str] = None
|
66
|
+
"""the oauth client secret to use."""
|
67
|
+
scopes: Optional[List[str]] = None
|
68
|
+
"""the scopes to use.
|
69
|
+
"""
|
70
|
+
|
71
|
+
@model_validator(mode="after")
|
72
|
+
def __post_init__(self) -> BigQueryCredentialsConfig:
|
73
|
+
"""Validate that either credentials or client ID/secret are provided."""
|
74
|
+
if not self.credentials and (not self.client_id or not self.client_secret):
|
75
|
+
raise ValueError(
|
76
|
+
"Must provide either credentials or client_id abd client_secret pair."
|
77
|
+
)
|
78
|
+
if self.credentials and (
|
79
|
+
self.client_id or self.client_secret or self.scopes
|
80
|
+
):
|
81
|
+
raise ValueError(
|
82
|
+
"Cannot provide both existing credentials and"
|
83
|
+
" client_id/client_secret/scopes."
|
84
|
+
)
|
85
|
+
|
86
|
+
if self.credentials:
|
87
|
+
self.client_id = self.credentials.client_id
|
88
|
+
self.client_secret = self.credentials.client_secret
|
89
|
+
self.scopes = self.credentials.scopes
|
90
|
+
return self
|
91
|
+
|
92
|
+
|
93
|
+
class BigQueryCredentialsManager:
|
94
|
+
"""Manages Google API credentials with automatic refresh and OAuth flow handling.
|
95
|
+
|
96
|
+
This class centralizes credential management so multiple tools can share
|
97
|
+
the same authenticated session without duplicating OAuth logic.
|
98
|
+
"""
|
99
|
+
|
100
|
+
def __init__(self, credentials_config: BigQueryCredentialsConfig):
|
101
|
+
"""Initialize the credential manager.
|
102
|
+
|
103
|
+
Args:
|
104
|
+
credentials_config: Credentials containing client id and client secrete
|
105
|
+
or default credentials
|
106
|
+
"""
|
107
|
+
self.credentials_config = credentials_config
|
108
|
+
|
109
|
+
async def get_valid_credentials(
|
110
|
+
self, tool_context: ToolContext
|
111
|
+
) -> Optional[Credentials]:
|
112
|
+
"""Get valid credentials, handling refresh and OAuth flow as needed.
|
113
|
+
|
114
|
+
Args:
|
115
|
+
tool_context: The tool context for OAuth flow and state management
|
116
|
+
|
117
|
+
Returns:
|
118
|
+
Valid Credentials object, or None if OAuth flow is needed
|
119
|
+
"""
|
120
|
+
# First, try to get credentials from the tool context
|
121
|
+
creds_json = tool_context.state.get(BIGQUERY_TOKEN_CACHE_KEY, None)
|
122
|
+
creds = (
|
123
|
+
Credentials.from_authorized_user_info(
|
124
|
+
creds_json, self.credentials_config.scopes
|
125
|
+
)
|
126
|
+
if creds_json
|
127
|
+
else None
|
128
|
+
)
|
129
|
+
|
130
|
+
# If credentails are empty use the default credential
|
131
|
+
if not creds:
|
132
|
+
creds = self.credentials_config.credentials
|
133
|
+
|
134
|
+
# Check if we have valid credentials
|
135
|
+
if creds and creds.valid:
|
136
|
+
return creds
|
137
|
+
|
138
|
+
# Try to refresh expired credentials
|
139
|
+
if creds and creds.expired and creds.refresh_token:
|
140
|
+
try:
|
141
|
+
creds.refresh(Request())
|
142
|
+
if creds.valid:
|
143
|
+
# Cache the refreshed credentials
|
144
|
+
tool_context.state[BIGQUERY_TOKEN_CACHE_KEY] = creds.to_json()
|
145
|
+
return creds
|
146
|
+
except RefreshError:
|
147
|
+
# Refresh failed, need to re-authenticate
|
148
|
+
pass
|
149
|
+
|
150
|
+
# Need to perform OAuth flow
|
151
|
+
return await self._perform_oauth_flow(tool_context)
|
152
|
+
|
153
|
+
async def _perform_oauth_flow(
|
154
|
+
self, tool_context: ToolContext
|
155
|
+
) -> Optional[Credentials]:
|
156
|
+
"""Perform OAuth flow to get new credentials.
|
157
|
+
|
158
|
+
Args:
|
159
|
+
tool_context: The tool context for OAuth flow
|
160
|
+
required_scopes: Set of required OAuth scopes
|
161
|
+
|
162
|
+
Returns:
|
163
|
+
New Credentials object, or None if flow is in progress
|
164
|
+
"""
|
165
|
+
|
166
|
+
# Create OAuth configuration
|
167
|
+
auth_scheme = OAuth2(
|
168
|
+
flows=OAuthFlows(
|
169
|
+
authorizationCode=OAuthFlowAuthorizationCode(
|
170
|
+
authorizationUrl="https://accounts.google.com/o/oauth2/auth",
|
171
|
+
tokenUrl="https://oauth2.googleapis.com/token",
|
172
|
+
scopes={
|
173
|
+
scope: f"Access to {scope}"
|
174
|
+
for scope in self.credentials_config.scopes
|
175
|
+
},
|
176
|
+
)
|
177
|
+
)
|
178
|
+
)
|
179
|
+
|
180
|
+
auth_credential = AuthCredential(
|
181
|
+
auth_type=AuthCredentialTypes.OAUTH2,
|
182
|
+
oauth2=OAuth2Auth(
|
183
|
+
client_id=self.credentials_config.client_id,
|
184
|
+
client_secret=self.credentials_config.client_secret,
|
185
|
+
),
|
186
|
+
)
|
187
|
+
|
188
|
+
# Check if OAuth response is available
|
189
|
+
auth_response = tool_context.get_auth_response(
|
190
|
+
AuthConfig(auth_scheme=auth_scheme, raw_auth_credential=auth_credential)
|
191
|
+
)
|
192
|
+
|
193
|
+
if auth_response:
|
194
|
+
# OAuth flow completed, create credentials
|
195
|
+
creds = Credentials(
|
196
|
+
token=auth_response.oauth2.access_token,
|
197
|
+
refresh_token=auth_response.oauth2.refresh_token,
|
198
|
+
token_uri=auth_scheme.flows.authorizationCode.tokenUrl,
|
199
|
+
client_id=self.credentials_config.client_id,
|
200
|
+
client_secret=self.credentials_config.client_secret,
|
201
|
+
scopes=list(self.credentials_config.scopes),
|
202
|
+
)
|
203
|
+
|
204
|
+
# Cache the new credentials
|
205
|
+
tool_context.state[BIGQUERY_TOKEN_CACHE_KEY] = creds.to_json()
|
206
|
+
|
207
|
+
return creds
|
208
|
+
else:
|
209
|
+
# Request OAuth flow
|
210
|
+
tool_context.request_credential(
|
211
|
+
AuthConfig(
|
212
|
+
auth_scheme=auth_scheme,
|
213
|
+
raw_auth_credential=auth_credential,
|
214
|
+
)
|
215
|
+
)
|
216
|
+
return None
|
@@ -0,0 +1,116 @@
|
|
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
|
+
|
16
|
+
import inspect
|
17
|
+
from typing import Any
|
18
|
+
from typing import Callable
|
19
|
+
from typing import Optional
|
20
|
+
|
21
|
+
from google.oauth2.credentials import Credentials
|
22
|
+
from typing_extensions import override
|
23
|
+
|
24
|
+
from ..function_tool import FunctionTool
|
25
|
+
from ..tool_context import ToolContext
|
26
|
+
from .bigquery_credentials import BigQueryCredentialsConfig
|
27
|
+
from .bigquery_credentials import BigQueryCredentialsManager
|
28
|
+
|
29
|
+
|
30
|
+
class BigQueryTool(FunctionTool):
|
31
|
+
"""GoogleApiTool class for tools that call Google APIs.
|
32
|
+
|
33
|
+
This class is for developers to handcraft customized Google API tools rather
|
34
|
+
than auto generate Google API tools based on API specs.
|
35
|
+
|
36
|
+
This class handles all the OAuth complexity, credential management,
|
37
|
+
and common Google API patterns so subclasses can focus on their
|
38
|
+
specific functionality.
|
39
|
+
"""
|
40
|
+
|
41
|
+
def __init__(
|
42
|
+
self,
|
43
|
+
func: Callable[..., Any],
|
44
|
+
credentials: Optional[BigQueryCredentialsConfig] = None,
|
45
|
+
):
|
46
|
+
"""Initialize the Google API tool.
|
47
|
+
|
48
|
+
Args:
|
49
|
+
func: callable that impelments the tool's logic, can accept one
|
50
|
+
'credential" parameter
|
51
|
+
credentials: credentials used to call Google API. If None, then we don't
|
52
|
+
hanlde the auth logic
|
53
|
+
"""
|
54
|
+
super().__init__(func=func)
|
55
|
+
self._ignore_params.append("credentials")
|
56
|
+
self.credentials_manager = (
|
57
|
+
BigQueryCredentialsManager(credentials) if credentials else None
|
58
|
+
)
|
59
|
+
|
60
|
+
@override
|
61
|
+
async def run_async(
|
62
|
+
self, *, args: dict[str, Any], tool_context: ToolContext
|
63
|
+
) -> Any:
|
64
|
+
"""Main entry point for tool execution with credential handling.
|
65
|
+
|
66
|
+
This method handles all the OAuth complexity and then delegates
|
67
|
+
to the subclass's run_async_with_credential method.
|
68
|
+
"""
|
69
|
+
try:
|
70
|
+
# Get valid credentials
|
71
|
+
credentials = (
|
72
|
+
await self.credentials_manager.get_valid_credentials(tool_context)
|
73
|
+
if self.credentials_manager
|
74
|
+
else None
|
75
|
+
)
|
76
|
+
|
77
|
+
if credentials is None and self.credentials_manager:
|
78
|
+
# OAuth flow in progress
|
79
|
+
return (
|
80
|
+
"User authorization is required to access Google services for"
|
81
|
+
f" {self.name}. Please complete the authorization flow."
|
82
|
+
)
|
83
|
+
|
84
|
+
# Execute the tool's specific logic with valid credentials
|
85
|
+
|
86
|
+
return await self._run_async_with_credential(
|
87
|
+
credentials, args, tool_context
|
88
|
+
)
|
89
|
+
|
90
|
+
except Exception as ex:
|
91
|
+
return {
|
92
|
+
"status": "ERROR",
|
93
|
+
"error_details": str(ex),
|
94
|
+
}
|
95
|
+
|
96
|
+
async def _run_async_with_credential(
|
97
|
+
self,
|
98
|
+
credentials: Credentials,
|
99
|
+
args: dict[str, Any],
|
100
|
+
tool_context: ToolContext,
|
101
|
+
) -> Any:
|
102
|
+
"""Execute the tool's specific logic with valid credentials.
|
103
|
+
|
104
|
+
Args:
|
105
|
+
credentials: Valid Google OAuth credentials
|
106
|
+
args: Arguments passed to the tool
|
107
|
+
tool_context: Tool execution context
|
108
|
+
|
109
|
+
Returns:
|
110
|
+
The result of the tool execution
|
111
|
+
"""
|
112
|
+
args_to_call = args.copy()
|
113
|
+
signature = inspect.signature(self.func)
|
114
|
+
if "credentials" in signature.parameters:
|
115
|
+
args_to_call["credentials"] = credentials
|
116
|
+
return await super().run_async(args=args_to_call, tool_context=tool_context)
|
@@ -26,16 +26,19 @@ if TYPE_CHECKING:
|
|
26
26
|
from ..models import LlmRequest
|
27
27
|
|
28
28
|
|
29
|
-
class
|
30
|
-
"""A built-in
|
29
|
+
class EnterpriseWebSearchTool(BaseTool):
|
30
|
+
"""A Gemini 2+ built-in tool using web grounding for Enterprise compliance.
|
31
31
|
|
32
|
-
|
33
|
-
|
32
|
+
See the documentation for more details:
|
33
|
+
https://cloud.google.com/vertex-ai/generative-ai/docs/grounding/web-grounding-enterprise.
|
34
34
|
"""
|
35
35
|
|
36
36
|
def __init__(self):
|
37
|
+
"""Initializes the Vertex AI Search tool."""
|
37
38
|
# Name and description are not used because this is a model built-in tool.
|
38
|
-
super().__init__(
|
39
|
+
super().__init__(
|
40
|
+
name='enterprise_web_search', description='enterprise_web_search'
|
41
|
+
)
|
39
42
|
|
40
43
|
@override
|
41
44
|
async def process_llm_request(
|
@@ -44,16 +47,19 @@ class BuiltInCodeExecutionTool(BaseTool):
|
|
44
47
|
tool_context: ToolContext,
|
45
48
|
llm_request: LlmRequest,
|
46
49
|
) -> None:
|
47
|
-
if llm_request.model and llm_request.model.startswith('gemini-
|
50
|
+
if llm_request.model and llm_request.model.startswith('gemini-'):
|
51
|
+
if llm_request.model.startswith('gemini-1') and llm_request.config.tools:
|
52
|
+
raise ValueError(
|
53
|
+
'Enterprise web search tool can not be used with other tools in'
|
54
|
+
' Gemini 1.x.'
|
55
|
+
)
|
48
56
|
llm_request.config = llm_request.config or types.GenerateContentConfig()
|
49
57
|
llm_request.config.tools = llm_request.config.tools or []
|
50
58
|
llm_request.config.tools.append(
|
51
|
-
types.Tool(
|
59
|
+
types.Tool(enterprise_web_search=types.EnterpriseWebSearch())
|
52
60
|
)
|
53
61
|
else:
|
54
62
|
raise ValueError(
|
55
|
-
|
63
|
+
'Enterprise web search tool is not supported for model'
|
64
|
+
f' {llm_request.model}'
|
56
65
|
)
|
57
|
-
|
58
|
-
|
59
|
-
built_in_code_execution = BuiltInCodeExecutionTool()
|