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.
Files changed (139) hide show
  1. google/adk/agents/base_agent.py +76 -30
  2. google/adk/agents/callback_context.py +2 -6
  3. google/adk/agents/llm_agent.py +122 -30
  4. google/adk/agents/loop_agent.py +1 -1
  5. google/adk/agents/parallel_agent.py +7 -0
  6. google/adk/agents/readonly_context.py +8 -0
  7. google/adk/agents/run_config.py +1 -1
  8. google/adk/agents/sequential_agent.py +31 -0
  9. google/adk/agents/transcription_entry.py +4 -2
  10. google/adk/artifacts/gcs_artifact_service.py +1 -1
  11. google/adk/artifacts/in_memory_artifact_service.py +1 -1
  12. google/adk/auth/auth_credential.py +10 -2
  13. google/adk/auth/auth_preprocessor.py +7 -1
  14. google/adk/auth/auth_tool.py +3 -4
  15. google/adk/cli/agent_graph.py +5 -5
  16. google/adk/cli/browser/index.html +4 -4
  17. google/adk/cli/browser/{main-ULN5R5I5.js → main-PKDNKWJE.js} +59 -60
  18. google/adk/cli/browser/polyfills-B6TNHZQ6.js +17 -0
  19. google/adk/cli/cli.py +10 -9
  20. google/adk/cli/cli_deploy.py +7 -2
  21. google/adk/cli/cli_eval.py +109 -115
  22. google/adk/cli/cli_tools_click.py +179 -67
  23. google/adk/cli/fast_api.py +248 -197
  24. google/adk/cli/utils/agent_loader.py +137 -0
  25. google/adk/cli/utils/cleanup.py +40 -0
  26. google/adk/cli/utils/common.py +23 -0
  27. google/adk/cli/utils/evals.py +83 -0
  28. google/adk/cli/utils/logs.py +8 -5
  29. google/adk/code_executors/__init__.py +3 -1
  30. google/adk/code_executors/built_in_code_executor.py +52 -0
  31. google/adk/code_executors/code_execution_utils.py +2 -1
  32. google/adk/code_executors/container_code_executor.py +0 -1
  33. google/adk/code_executors/vertex_ai_code_executor.py +6 -8
  34. google/adk/evaluation/__init__.py +1 -1
  35. google/adk/evaluation/agent_evaluator.py +168 -128
  36. google/adk/evaluation/eval_case.py +104 -0
  37. google/adk/evaluation/eval_metrics.py +74 -0
  38. google/adk/evaluation/eval_result.py +86 -0
  39. google/adk/evaluation/eval_set.py +39 -0
  40. google/adk/evaluation/eval_set_results_manager.py +47 -0
  41. google/adk/evaluation/eval_sets_manager.py +43 -0
  42. google/adk/evaluation/evaluation_generator.py +88 -113
  43. google/adk/evaluation/evaluator.py +58 -0
  44. google/adk/evaluation/local_eval_set_results_manager.py +113 -0
  45. google/adk/evaluation/local_eval_sets_manager.py +264 -0
  46. google/adk/evaluation/response_evaluator.py +106 -1
  47. google/adk/evaluation/trajectory_evaluator.py +84 -2
  48. google/adk/events/event.py +6 -1
  49. google/adk/events/event_actions.py +6 -1
  50. google/adk/examples/base_example_provider.py +1 -0
  51. google/adk/examples/example_util.py +3 -2
  52. google/adk/flows/llm_flows/_code_execution.py +9 -1
  53. google/adk/flows/llm_flows/audio_transcriber.py +4 -3
  54. google/adk/flows/llm_flows/base_llm_flow.py +58 -21
  55. google/adk/flows/llm_flows/contents.py +3 -1
  56. google/adk/flows/llm_flows/functions.py +9 -8
  57. google/adk/flows/llm_flows/instructions.py +18 -80
  58. google/adk/flows/llm_flows/single_flow.py +2 -2
  59. google/adk/memory/__init__.py +1 -1
  60. google/adk/memory/_utils.py +23 -0
  61. google/adk/memory/base_memory_service.py +23 -21
  62. google/adk/memory/in_memory_memory_service.py +57 -25
  63. google/adk/memory/memory_entry.py +37 -0
  64. google/adk/memory/vertex_ai_rag_memory_service.py +38 -15
  65. google/adk/models/anthropic_llm.py +16 -9
  66. google/adk/models/base_llm.py +2 -1
  67. google/adk/models/base_llm_connection.py +2 -0
  68. google/adk/models/gemini_llm_connection.py +11 -11
  69. google/adk/models/google_llm.py +12 -2
  70. google/adk/models/lite_llm.py +80 -23
  71. google/adk/models/llm_response.py +16 -3
  72. google/adk/models/registry.py +1 -1
  73. google/adk/runners.py +98 -42
  74. google/adk/sessions/__init__.py +1 -1
  75. google/adk/sessions/_session_util.py +2 -1
  76. google/adk/sessions/base_session_service.py +6 -33
  77. google/adk/sessions/database_session_service.py +57 -67
  78. google/adk/sessions/in_memory_session_service.py +106 -24
  79. google/adk/sessions/session.py +3 -0
  80. google/adk/sessions/vertex_ai_session_service.py +44 -51
  81. google/adk/telemetry.py +7 -2
  82. google/adk/tools/__init__.py +4 -7
  83. google/adk/tools/_memory_entry_utils.py +30 -0
  84. google/adk/tools/agent_tool.py +10 -10
  85. google/adk/tools/apihub_tool/apihub_toolset.py +55 -74
  86. google/adk/tools/apihub_tool/clients/apihub_client.py +10 -3
  87. google/adk/tools/apihub_tool/clients/secret_client.py +1 -0
  88. google/adk/tools/application_integration_tool/application_integration_toolset.py +111 -85
  89. google/adk/tools/application_integration_tool/clients/connections_client.py +28 -1
  90. google/adk/tools/application_integration_tool/clients/integration_client.py +7 -5
  91. google/adk/tools/application_integration_tool/integration_connector_tool.py +69 -26
  92. google/adk/tools/base_toolset.py +96 -0
  93. google/adk/tools/bigquery/__init__.py +28 -0
  94. google/adk/tools/bigquery/bigquery_credentials.py +216 -0
  95. google/adk/tools/bigquery/bigquery_tool.py +116 -0
  96. google/adk/tools/{built_in_code_execution_tool.py → enterprise_search_tool.py} +17 -11
  97. google/adk/tools/function_parameter_parse_util.py +9 -2
  98. google/adk/tools/function_tool.py +33 -3
  99. google/adk/tools/get_user_choice_tool.py +1 -0
  100. google/adk/tools/google_api_tool/__init__.py +24 -70
  101. google/adk/tools/google_api_tool/google_api_tool.py +12 -6
  102. google/adk/tools/google_api_tool/{google_api_tool_set.py → google_api_toolset.py} +57 -55
  103. google/adk/tools/google_api_tool/google_api_toolsets.py +108 -0
  104. google/adk/tools/google_api_tool/googleapi_to_openapi_converter.py +40 -42
  105. google/adk/tools/google_search_tool.py +2 -2
  106. google/adk/tools/langchain_tool.py +96 -49
  107. google/adk/tools/load_memory_tool.py +14 -5
  108. google/adk/tools/mcp_tool/__init__.py +3 -2
  109. google/adk/tools/mcp_tool/conversion_utils.py +6 -2
  110. google/adk/tools/mcp_tool/mcp_session_manager.py +80 -69
  111. google/adk/tools/mcp_tool/mcp_tool.py +35 -32
  112. google/adk/tools/mcp_tool/mcp_toolset.py +99 -194
  113. google/adk/tools/openapi_tool/auth/credential_exchangers/base_credential_exchanger.py +1 -3
  114. google/adk/tools/openapi_tool/auth/credential_exchangers/service_account_exchanger.py +6 -7
  115. google/adk/tools/openapi_tool/common/common.py +5 -1
  116. google/adk/tools/openapi_tool/openapi_spec_parser/__init__.py +7 -2
  117. google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +27 -7
  118. google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +36 -32
  119. google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +11 -1
  120. google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py +1 -1
  121. google/adk/tools/preload_memory_tool.py +27 -18
  122. google/adk/tools/retrieval/__init__.py +1 -1
  123. google/adk/tools/retrieval/vertex_ai_rag_retrieval.py +1 -1
  124. google/adk/tools/toolbox_toolset.py +107 -0
  125. google/adk/tools/transfer_to_agent_tool.py +0 -1
  126. google/adk/utils/__init__.py +13 -0
  127. google/adk/utils/instructions_utils.py +131 -0
  128. google/adk/version.py +1 -1
  129. {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/METADATA +18 -19
  130. google_adk-1.1.0.dist-info/RECORD +200 -0
  131. google/adk/agents/remote_agent.py +0 -50
  132. google/adk/cli/browser/polyfills-FFHMD2TL.js +0 -18
  133. google/adk/cli/fast_api.py.orig +0 -728
  134. google/adk/tools/google_api_tool/google_api_tool_sets.py +0 -112
  135. google/adk/tools/toolbox_tool.py +0 -46
  136. google_adk-0.5.0.dist-info/RECORD +0 -180
  137. {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/WHEEL +0 -0
  138. {google_adk-0.5.0.dist-info → google_adk-1.1.0.dist-info}/entry_points.txt +0 -0
  139. {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.connection_name = connection_name
105
- self.connection_host = connection_host
106
- self.connection_service_name = connection_service_name
107
- self.entity = entity
108
- self.operation = operation
109
- self.action = action
110
- self.rest_api_tool = rest_api_tool
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.rest_api_tool._operation_parser.get_json_schema()
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
- args['connection_name'] = self.connection_name
134
- args['service_name'] = self.connection_service_name
135
- args['host'] = self.connection_host
136
- args['entity'] = self.entity
137
- args['operation'] = self.operation
138
- args['action'] = self.action
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.rest_api_tool.call(args=args, tool_context=tool_context)
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.connection_name}", entity="{self.entity}",'
147
- f' operation="{self.operation}", action="{self.action}")'
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.connection_name}",'
155
- f' connection_host="{self.connection_host}",'
156
- f' connection_service_name="{self.connection_service_name}",'
157
- f' entity="{self.entity}", operation="{self.operation}",'
158
- f' action="{self.action}", rest_api_tool={repr(self.rest_api_tool)})'
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 BuiltInCodeExecutionTool(BaseTool):
30
- """A built-in code execution tool that is automatically invoked by Gemini 2 models.
29
+ class EnterpriseWebSearchTool(BaseTool):
30
+ """A Gemini 2+ built-in tool using web grounding for Enterprise compliance.
31
31
 
32
- This tool operates internally within the model and does not require or perform
33
- local code execution.
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__(name='code_execution', description='code_execution')
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-2'):
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(code_execution=types.ToolCodeExecution())
59
+ types.Tool(enterprise_web_search=types.EnterpriseWebSearch())
52
60
  )
53
61
  else:
54
62
  raise ValueError(
55
- f'Code execution tool is not supported for model {llm_request.model}'
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()