google-adk 1.0.0__py3-none-any.whl → 1.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. google/adk/agents/callback_context.py +2 -1
  2. google/adk/agents/readonly_context.py +3 -1
  3. google/adk/auth/auth_credential.py +4 -1
  4. google/adk/cli/browser/index.html +4 -4
  5. google/adk/cli/browser/{main-QOEMUXM4.js → main-PKDNKWJE.js} +59 -59
  6. google/adk/cli/browser/polyfills-B6TNHZQ6.js +17 -0
  7. google/adk/cli/cli.py +3 -2
  8. google/adk/cli/cli_eval.py +6 -85
  9. google/adk/cli/cli_tools_click.py +39 -10
  10. google/adk/cli/fast_api.py +53 -184
  11. google/adk/cli/utils/agent_loader.py +137 -0
  12. google/adk/cli/utils/cleanup.py +40 -0
  13. google/adk/cli/utils/evals.py +2 -1
  14. google/adk/cli/utils/logs.py +2 -7
  15. google/adk/code_executors/code_execution_utils.py +2 -1
  16. google/adk/code_executors/container_code_executor.py +0 -1
  17. google/adk/code_executors/vertex_ai_code_executor.py +6 -8
  18. google/adk/evaluation/eval_case.py +3 -1
  19. google/adk/evaluation/eval_metrics.py +74 -0
  20. google/adk/evaluation/eval_result.py +86 -0
  21. google/adk/evaluation/eval_set.py +2 -0
  22. google/adk/evaluation/eval_set_results_manager.py +47 -0
  23. google/adk/evaluation/eval_sets_manager.py +2 -1
  24. google/adk/evaluation/evaluator.py +2 -0
  25. google/adk/evaluation/local_eval_set_results_manager.py +113 -0
  26. google/adk/evaluation/local_eval_sets_manager.py +4 -4
  27. google/adk/evaluation/response_evaluator.py +2 -1
  28. google/adk/evaluation/trajectory_evaluator.py +3 -2
  29. google/adk/examples/base_example_provider.py +1 -0
  30. google/adk/flows/llm_flows/base_llm_flow.py +4 -6
  31. google/adk/flows/llm_flows/contents.py +3 -1
  32. google/adk/flows/llm_flows/instructions.py +7 -77
  33. google/adk/flows/llm_flows/single_flow.py +1 -1
  34. google/adk/models/base_llm.py +2 -1
  35. google/adk/models/base_llm_connection.py +2 -0
  36. google/adk/models/google_llm.py +4 -1
  37. google/adk/models/lite_llm.py +3 -2
  38. google/adk/models/llm_response.py +2 -1
  39. google/adk/runners.py +36 -4
  40. google/adk/sessions/_session_util.py +2 -1
  41. google/adk/sessions/database_session_service.py +5 -8
  42. google/adk/sessions/vertex_ai_session_service.py +28 -13
  43. google/adk/telemetry.py +4 -2
  44. google/adk/tools/agent_tool.py +1 -1
  45. google/adk/tools/apihub_tool/apihub_toolset.py +1 -1
  46. google/adk/tools/apihub_tool/clients/apihub_client.py +10 -3
  47. google/adk/tools/apihub_tool/clients/secret_client.py +1 -0
  48. google/adk/tools/application_integration_tool/application_integration_toolset.py +6 -2
  49. google/adk/tools/application_integration_tool/clients/connections_client.py +8 -1
  50. google/adk/tools/application_integration_tool/clients/integration_client.py +3 -1
  51. google/adk/tools/application_integration_tool/integration_connector_tool.py +1 -1
  52. google/adk/tools/base_toolset.py +40 -2
  53. google/adk/tools/bigquery/__init__.py +38 -0
  54. google/adk/tools/bigquery/bigquery_credentials.py +217 -0
  55. google/adk/tools/bigquery/bigquery_tool.py +116 -0
  56. google/adk/tools/bigquery/bigquery_toolset.py +86 -0
  57. google/adk/tools/bigquery/client.py +33 -0
  58. google/adk/tools/bigquery/metadata_tool.py +249 -0
  59. google/adk/tools/bigquery/query_tool.py +76 -0
  60. google/adk/tools/function_parameter_parse_util.py +7 -0
  61. google/adk/tools/function_tool.py +33 -3
  62. google/adk/tools/get_user_choice_tool.py +1 -0
  63. google/adk/tools/google_api_tool/__init__.py +17 -11
  64. google/adk/tools/google_api_tool/google_api_tool.py +1 -1
  65. google/adk/tools/google_api_tool/google_api_toolset.py +0 -14
  66. google/adk/tools/google_api_tool/google_api_toolsets.py +8 -2
  67. google/adk/tools/google_search_tool.py +2 -2
  68. google/adk/tools/mcp_tool/conversion_utils.py +6 -2
  69. google/adk/tools/mcp_tool/mcp_session_manager.py +62 -188
  70. google/adk/tools/mcp_tool/mcp_tool.py +27 -24
  71. google/adk/tools/mcp_tool/mcp_toolset.py +76 -131
  72. google/adk/tools/openapi_tool/auth/credential_exchangers/base_credential_exchanger.py +1 -3
  73. google/adk/tools/openapi_tool/auth/credential_exchangers/service_account_exchanger.py +6 -7
  74. google/adk/tools/openapi_tool/common/common.py +5 -1
  75. google/adk/tools/openapi_tool/openapi_spec_parser/__init__.py +7 -2
  76. google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +2 -7
  77. google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +5 -1
  78. google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +11 -1
  79. google/adk/tools/toolbox_toolset.py +31 -3
  80. google/adk/utils/__init__.py +13 -0
  81. google/adk/utils/instructions_utils.py +131 -0
  82. google/adk/version.py +1 -1
  83. {google_adk-1.0.0.dist-info → google_adk-1.1.1.dist-info}/METADATA +12 -15
  84. {google_adk-1.0.0.dist-info → google_adk-1.1.1.dist-info}/RECORD +87 -78
  85. google/adk/agents/base_agent.py.orig +0 -330
  86. google/adk/cli/browser/polyfills-FFHMD2TL.js +0 -18
  87. google/adk/cli/fast_api.py.orig +0 -822
  88. google/adk/memory/base_memory_service.py.orig +0 -76
  89. google/adk/models/google_llm.py.orig +0 -305
  90. google/adk/tools/_built_in_code_execution_tool.py +0 -70
  91. google/adk/tools/mcp_tool/mcp_session_manager.py.orig +0 -322
  92. {google_adk-1.0.0.dist-info → google_adk-1.1.1.dist-info}/WHEEL +0 -0
  93. {google_adk-1.0.0.dist-info → google_adk-1.1.1.dist-info}/entry_points.txt +0 -0
  94. {google_adk-1.0.0.dist-info → google_adk-1.1.1.dist-info}/licenses/LICENSE +0 -0
@@ -98,6 +98,7 @@ class Gemini(BaseLlm):
98
98
  )
99
99
  response = None
100
100
  text = ''
101
+ usage_metadata = None
101
102
  # for sse, similar as bidi (see receive method in gemini_llm_connecton.py),
102
103
  # we need to mark those text content as partial and after all partial
103
104
  # contents are sent, we send an accumulated event which contains all the
@@ -106,6 +107,7 @@ class Gemini(BaseLlm):
106
107
  async for response in responses:
107
108
  logger.info(_build_response_log(response))
108
109
  llm_response = LlmResponse.create(response)
110
+ usage_metadata = llm_response.usage_metadata
109
111
  if (
110
112
  llm_response.content
111
113
  and llm_response.content.parts
@@ -123,7 +125,7 @@ class Gemini(BaseLlm):
123
125
  content=types.ModelContent(
124
126
  parts=[types.Part.from_text(text=text)],
125
127
  ),
126
- usage_metadata=llm_response.usage_metadata,
128
+ usage_metadata=usage_metadata,
127
129
  )
128
130
  text = ''
129
131
  yield llm_response
@@ -137,6 +139,7 @@ class Gemini(BaseLlm):
137
139
  content=types.ModelContent(
138
140
  parts=[types.Part.from_text(text=text)],
139
141
  ),
142
+ usage_metadata=usage_metadata,
140
143
  )
141
144
 
142
145
  else:
@@ -30,6 +30,7 @@ from typing import Union
30
30
  from google.genai import types
31
31
  from litellm import acompletion
32
32
  from litellm import ChatCompletionAssistantMessage
33
+ from litellm import ChatCompletionAssistantToolCall
33
34
  from litellm import ChatCompletionDeveloperMessage
34
35
  from litellm import ChatCompletionImageUrlObject
35
36
  from litellm import ChatCompletionMessageToolCall
@@ -180,12 +181,12 @@ def _content_to_message_param(
180
181
  for part in content.parts:
181
182
  if part.function_call:
182
183
  tool_calls.append(
183
- ChatCompletionMessageToolCall(
184
+ ChatCompletionAssistantToolCall(
184
185
  type="function",
185
186
  id=part.function_call.id,
186
187
  function=Function(
187
188
  name=part.function_call.name,
188
- arguments=part.function_call.args,
189
+ arguments=json.dumps(part.function_call.args),
189
190
  ),
190
191
  )
191
192
  )
@@ -14,7 +14,8 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- from typing import Any, Optional
17
+ from typing import Any
18
+ from typing import Optional
18
19
 
19
20
  from google.genai import types
20
21
  from pydantic import alias_generators
google/adk/runners.py CHANGED
@@ -34,6 +34,7 @@ from .agents.llm_agent import LlmAgent
34
34
  from .agents.run_config import RunConfig
35
35
  from .artifacts.base_artifact_service import BaseArtifactService
36
36
  from .artifacts.in_memory_artifact_service import InMemoryArtifactService
37
+ from .code_executors.built_in_code_executor import BuiltInCodeExecutor
37
38
  from .events.event import Event
38
39
  from .memory.base_memory_service import BaseMemoryService
39
40
  from .memory.in_memory_memory_service import InMemoryMemoryService
@@ -41,7 +42,7 @@ from .sessions.base_session_service import BaseSessionService
41
42
  from .sessions.in_memory_session_service import InMemorySessionService
42
43
  from .sessions.session import Session
43
44
  from .telemetry import tracer
44
- from .tools._built_in_code_execution_tool import built_in_code_execution
45
+ from .tools.base_toolset import BaseToolset
45
46
 
46
47
  logger = logging.getLogger('google_adk.' + __name__)
47
48
 
@@ -286,7 +287,7 @@ class Runner:
286
287
  stacklevel=2,
287
288
  )
288
289
  if not session:
289
- session = self.session_service.get_session(
290
+ session = await self.session_service.get_session(
290
291
  app_name=self.app_name, user_id=user_id, session_id=session_id
291
292
  )
292
293
  if not session:
@@ -409,8 +410,8 @@ class Runner:
409
410
  f'CFC is not supported for model: {model_name} in agent:'
410
411
  f' {self.agent.name}'
411
412
  )
412
- if built_in_code_execution not in self.agent.canonical_tools():
413
- self.agent.tools.append(built_in_code_execution)
413
+ if not isinstance(self.agent.code_executor, BuiltInCodeExecutor):
414
+ self.agent.code_executor = BuiltInCodeExecutor()
414
415
 
415
416
  return InvocationContext(
416
417
  artifact_service=self.artifact_service,
@@ -457,6 +458,37 @@ class Runner:
457
458
  run_config=run_config,
458
459
  )
459
460
 
461
+ def _collect_toolset(self, agent: BaseAgent) -> set[BaseToolset]:
462
+ toolsets = set()
463
+ if isinstance(agent, LlmAgent):
464
+ for tool_union in agent.tools:
465
+ if isinstance(tool_union, BaseToolset):
466
+ toolsets.add(tool_union)
467
+ for sub_agent in agent.sub_agents:
468
+ toolsets.update(self._collect_toolset(sub_agent))
469
+ return toolsets
470
+
471
+ async def _cleanup_toolsets(self, toolsets_to_close: set[BaseToolset]):
472
+ """Clean up toolsets with proper task context management."""
473
+ if not toolsets_to_close:
474
+ return
475
+
476
+ # This maintains the same task context throughout cleanup
477
+ for toolset in toolsets_to_close:
478
+ try:
479
+ logger.info('Closing toolset: %s', type(toolset).__name__)
480
+ # Use asyncio.wait_for to add timeout protection
481
+ await asyncio.wait_for(toolset.close(), timeout=10.0)
482
+ logger.info('Successfully closed toolset: %s', type(toolset).__name__)
483
+ except asyncio.TimeoutError:
484
+ logger.warning('Toolset %s cleanup timed out', type(toolset).__name__)
485
+ except Exception as e:
486
+ logger.error('Error closing toolset %s: %s', type(toolset).__name__, e)
487
+
488
+ async def close(self):
489
+ """Closes the runner."""
490
+ await self._cleanup_toolsets(self._collect_toolset(self.agent))
491
+
460
492
 
461
493
  class InMemoryRunner(Runner):
462
494
  """An in-memory Runner for testing and development.
@@ -15,7 +15,8 @@
15
15
  """Utility functions for session service."""
16
16
 
17
17
  import base64
18
- from typing import Any, Optional
18
+ from typing import Any
19
+ from typing import Optional
19
20
 
20
21
  from google.genai import types
21
22
 
@@ -13,7 +13,6 @@
13
13
  # limitations under the License.
14
14
  import copy
15
15
  from datetime import datetime
16
- from datetime import timezone
17
16
  import json
18
17
  import logging
19
18
  from typing import Any
@@ -47,8 +46,8 @@ from sqlalchemy.types import TypeDecorator
47
46
  from typing_extensions import override
48
47
  from tzlocal import get_localzone
49
48
 
50
- from ..events.event import Event
51
49
  from . import _session_util
50
+ from ..events.event import Event
52
51
  from .base_session_service import BaseSessionService
53
52
  from .base_session_service import GetSessionConfig
54
53
  from .base_session_service import ListSessionsResponse
@@ -371,10 +370,8 @@ class DatabaseSessionService(BaseSessionService):
371
370
  return None
372
371
 
373
372
  if config and config.after_timestamp:
374
- after_dt = datetime.fromtimestamp(
375
- config.after_timestamp, tz=timezone.utc
376
- )
377
- timestamp_filter = StorageEvent.timestamp > after_dt
373
+ after_dt = datetime.fromtimestamp(config.after_timestamp)
374
+ timestamp_filter = StorageEvent.timestamp >= after_dt
378
375
  else:
379
376
  timestamp_filter = True
380
377
 
@@ -382,7 +379,7 @@ class DatabaseSessionService(BaseSessionService):
382
379
  session_factory.query(StorageEvent)
383
380
  .filter(StorageEvent.session_id == storage_session.id)
384
381
  .filter(timestamp_filter)
385
- .order_by(StorageEvent.timestamp.asc())
382
+ .order_by(StorageEvent.timestamp.desc())
386
383
  .limit(
387
384
  config.num_recent_events
388
385
  if config and config.num_recent_events
@@ -429,7 +426,7 @@ class DatabaseSessionService(BaseSessionService):
429
426
  error_message=e.error_message,
430
427
  interrupted=e.interrupted,
431
428
  )
432
- for e in storage_events
429
+ for e in reversed(storage_events)
433
430
  ]
434
431
  return session
435
432
 
@@ -11,19 +11,20 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
+ import asyncio
14
15
  import logging
15
16
  import re
16
- import time
17
17
  from typing import Any
18
18
  from typing import Optional
19
19
 
20
20
  from dateutil import parser
21
- from google import genai
22
21
  from typing_extensions import override
23
22
 
23
+ from google import genai
24
+
25
+ from . import _session_util
24
26
  from ..events.event import Event
25
27
  from ..events.event_actions import EventActions
26
- from . import _session_util
27
28
  from .base_session_service import BaseSessionService
28
29
  from .base_session_service import GetSessionConfig
29
30
  from .base_session_service import ListSessionsResponse
@@ -68,7 +69,8 @@ class VertexAiSessionService(BaseSessionService):
68
69
  if state:
69
70
  session_json_dict['session_state'] = state
70
71
 
71
- api_response = await self.api_client.async_request(
72
+ api_client = _get_api_client(self.project, self.location)
73
+ api_response = await api_client.async_request(
72
74
  http_method='POST',
73
75
  path=f'reasoningEngines/{reasoning_engine_id}/sessions',
74
76
  request_dict=session_json_dict,
@@ -80,7 +82,7 @@ class VertexAiSessionService(BaseSessionService):
80
82
 
81
83
  max_retry_attempt = 5
82
84
  while max_retry_attempt >= 0:
83
- lro_response = await self.api_client.async_request(
85
+ lro_response = await api_client.async_request(
84
86
  http_method='GET',
85
87
  path=f'operations/{operation_id}',
86
88
  request_dict={},
@@ -89,11 +91,11 @@ class VertexAiSessionService(BaseSessionService):
89
91
  if lro_response.get('done', None):
90
92
  break
91
93
 
92
- time.sleep(1)
94
+ await asyncio.sleep(1)
93
95
  max_retry_attempt -= 1
94
96
 
95
97
  # Get session resource
96
- get_session_api_response = await self.api_client.async_request(
98
+ get_session_api_response = await api_client.async_request(
97
99
  http_method='GET',
98
100
  path=f'reasoningEngines/{reasoning_engine_id}/sessions/{session_id}',
99
101
  request_dict={},
@@ -123,7 +125,8 @@ class VertexAiSessionService(BaseSessionService):
123
125
  reasoning_engine_id = _parse_reasoning_engine_id(app_name)
124
126
 
125
127
  # Get session resource
126
- get_session_api_response = await self.api_client.async_request(
128
+ api_client = _get_api_client(self.project, self.location)
129
+ get_session_api_response = await api_client.async_request(
127
130
  http_method='GET',
128
131
  path=f'reasoningEngines/{reasoning_engine_id}/sessions/{session_id}',
129
132
  request_dict={},
@@ -141,7 +144,7 @@ class VertexAiSessionService(BaseSessionService):
141
144
  last_update_time=update_timestamp,
142
145
  )
143
146
 
144
- list_events_api_response = await self.api_client.async_request(
147
+ list_events_api_response = await api_client.async_request(
145
148
  http_method='GET',
146
149
  path=f'reasoningEngines/{reasoning_engine_id}/sessions/{session_id}/events',
147
150
  request_dict={},
@@ -180,7 +183,8 @@ class VertexAiSessionService(BaseSessionService):
180
183
  ) -> ListSessionsResponse:
181
184
  reasoning_engine_id = _parse_reasoning_engine_id(app_name)
182
185
 
183
- api_response = self.api_client.request(
186
+ api_client = _get_api_client(self.project, self.location)
187
+ api_response = await api_client.async_request(
184
188
  http_method='GET',
185
189
  path=f'reasoningEngines/{reasoning_engine_id}/sessions?filter=user_id={user_id}',
186
190
  request_dict={},
@@ -206,7 +210,8 @@ class VertexAiSessionService(BaseSessionService):
206
210
  self, *, app_name: str, user_id: str, session_id: str
207
211
  ) -> None:
208
212
  reasoning_engine_id = _parse_reasoning_engine_id(app_name)
209
- await self.api_client.async_request(
213
+ api_client = _get_api_client(self.project, self.location)
214
+ await api_client.async_request(
210
215
  http_method='DELETE',
211
216
  path=f'reasoningEngines/{reasoning_engine_id}/sessions/{session_id}',
212
217
  request_dict={},
@@ -218,15 +223,25 @@ class VertexAiSessionService(BaseSessionService):
218
223
  await super().append_event(session=session, event=event)
219
224
 
220
225
  reasoning_engine_id = _parse_reasoning_engine_id(session.app_name)
221
- await self.api_client.async_request(
226
+ api_client = _get_api_client(self.project, self.location)
227
+ await api_client.async_request(
222
228
  http_method='POST',
223
229
  path=f'reasoningEngines/{reasoning_engine_id}/sessions/{session.id}:appendEvent',
224
230
  request_dict=_convert_event_to_json(event),
225
231
  )
226
-
227
232
  return event
228
233
 
229
234
 
235
+ def _get_api_client(project: str, location: str):
236
+ """Instantiates an API client for the given project and location.
237
+
238
+ It needs to be instantiated inside each request so that the event loop
239
+ management.
240
+ """
241
+ client = genai.Client(vertexai=True, project=project, location=location)
242
+ return client._api_client
243
+
244
+
230
245
  def _convert_event_to_json(event: Event):
231
246
  metadata_json = {
232
247
  'partial': event.partial,
google/adk/telemetry.py CHANGED
@@ -32,7 +32,6 @@ from .events.event import Event
32
32
  from .models.llm_request import LlmRequest
33
33
  from .models.llm_response import LlmResponse
34
34
 
35
-
36
35
  tracer = trace.get_tracer('gcp.vertex.agent')
37
36
 
38
37
 
@@ -118,7 +117,10 @@ def trace_call_llm(
118
117
  # Consider removing once GenAI SDK provides a way to record this info.
119
118
  span.set_attribute(
120
119
  'gcp.vertex.agent.llm_request',
121
- json.dumps(_build_llm_request_for_trace(llm_request)),
120
+ json.dumps(
121
+ _build_llm_request_for_trace(llm_request),
122
+ default=lambda o: '<not serializable>',
123
+ ),
122
124
  )
123
125
  # Consider removing once GenAI SDK provides a way to record this info.
124
126
  span.set_attribute(
@@ -21,10 +21,10 @@ from google.genai import types
21
21
  from pydantic import model_validator
22
22
  from typing_extensions import override
23
23
 
24
+ from . import _automatic_function_calling_util
24
25
  from ..memory.in_memory_memory_service import InMemoryMemoryService
25
26
  from ..runners import Runner
26
27
  from ..sessions.in_memory_session_service import InMemorySessionService
27
- from . import _automatic_function_calling_util
28
28
  from .base_tool import BaseTool
29
29
  from .tool_context import ToolContext
30
30
 
@@ -131,6 +131,7 @@ class APIHubToolset(BaseToolset):
131
131
  be either a tool predicate or a list of tool names of the tools to
132
132
  expose.
133
133
  """
134
+ super().__init__(tool_filter=tool_filter)
134
135
  self.name = name
135
136
  self.description = description
136
137
  self._apihub_resource_name = apihub_resource_name
@@ -143,7 +144,6 @@ class APIHubToolset(BaseToolset):
143
144
  self._openapi_toolset = None
144
145
  self._auth_scheme = auth_scheme
145
146
  self._auth_credential = auth_credential
146
- self.tool_filter = tool_filter
147
147
 
148
148
  if not self._lazy_load_spec:
149
149
  self._prepare_toolset()
@@ -12,11 +12,18 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- from abc import ABC, abstractmethod
15
+ from abc import ABC
16
+ from abc import abstractmethod
16
17
  import base64
17
18
  import json
18
- from typing import Any, Dict, List, Optional, Tuple
19
- from urllib.parse import parse_qs, urlparse
19
+ from typing import Any
20
+ from typing import Dict
21
+ from typing import List
22
+ from typing import Optional
23
+ from typing import Tuple
24
+ from urllib.parse import parse_qs
25
+ from urllib.parse import urlparse
26
+
20
27
  from google.auth import default as default_service_credential
21
28
  from google.auth.transport.requests import Request
22
29
  from google.oauth2 import service_account
@@ -14,6 +14,7 @@
14
14
 
15
15
  import json
16
16
  from typing import Optional
17
+
17
18
  import google.auth
18
19
  from google.auth import default as default_service_credential
19
20
  import google.auth.transport.requests
@@ -128,6 +128,7 @@ class ApplicationIntegrationToolset(BaseToolset):
128
128
  Exception: If there is an error during the initialization of the
129
129
  integration or connection client.
130
130
  """
131
+ super().__init__(tool_filter=tool_filter)
131
132
  self.project = project
132
133
  self.location = location
133
134
  self._integration = integration
@@ -140,7 +141,6 @@ class ApplicationIntegrationToolset(BaseToolset):
140
141
  self._service_account_json = service_account_json
141
142
  self._auth_scheme = auth_scheme
142
143
  self._auth_credential = auth_credential
143
- self.tool_filter = tool_filter
144
144
 
145
145
  integration_client = IntegrationClient(
146
146
  project,
@@ -263,7 +263,11 @@ class ApplicationIntegrationToolset(BaseToolset):
263
263
  readonly_context: Optional[ReadonlyContext] = None,
264
264
  ) -> List[RestApiTool]:
265
265
  return (
266
- self._tools
266
+ [
267
+ tool
268
+ for tool in self._tools
269
+ if self._is_tool_selected(tool, readonly_context)
270
+ ]
267
271
  if self._openapi_toolset is None
268
272
  else await self._openapi_toolset.get_tools(readonly_context)
269
273
  )
@@ -14,7 +14,11 @@
14
14
 
15
15
  import json
16
16
  import time
17
- from typing import Any, Dict, List, Optional, Tuple
17
+ from typing import Any
18
+ from typing import Dict
19
+ from typing import List
20
+ from typing import Optional
21
+ from typing import Tuple
18
22
 
19
23
  import google.auth
20
24
  from google.auth import default as default_service_credential
@@ -728,6 +732,9 @@ class ConnectionsClient:
728
732
  "query": {"$ref": "#/components/schemas/query"},
729
733
  "timeout": {"$ref": "#/components/schemas/timeout"},
730
734
  "pageSize": {"$ref": "#/components/schemas/pageSize"},
735
+ "dynamicAuthConfig": {
736
+ "$ref": "#/components/schemas/dynamicAuthConfig"
737
+ },
731
738
  },
732
739
  }
733
740
 
@@ -13,7 +13,9 @@
13
13
  # limitations under the License.
14
14
 
15
15
  import json
16
- from typing import List, Optional
16
+ from typing import List
17
+ from typing import Optional
18
+
17
19
  from google.adk.tools.application_integration_tool.clients.connections_client import ConnectionsClient
18
20
  import google.auth
19
21
  from google.auth import default as default_service_credential
@@ -21,9 +21,9 @@ from typing import Union
21
21
  from google.genai.types import FunctionDeclaration
22
22
  from typing_extensions import override
23
23
 
24
+ from .. import BaseTool
24
25
  from ...auth.auth_credential import AuthCredential
25
26
  from ...auth.auth_schemes import AuthScheme
26
- from .. import BaseTool
27
27
  from ..openapi_tool.openapi_spec_parser.rest_api_tool import RestApiTool
28
28
  from ..openapi_tool.openapi_spec_parser.rest_api_tool import to_gemini_schema
29
29
  from ..openapi_tool.openapi_spec_parser.tool_auth_handler import ToolAuthHandler
@@ -1,7 +1,25 @@
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
+
1
16
  from abc import ABC
2
17
  from abc import abstractmethod
3
- from typing import Optional, runtime_checkable
18
+ from typing import List
19
+ from typing import Optional
4
20
  from typing import Protocol
21
+ from typing import runtime_checkable
22
+ from typing import Union
5
23
 
6
24
  from ..agents.readonly_context import ReadonlyContext
7
25
  from .base_tool import BaseTool
@@ -33,9 +51,15 @@ class BaseToolset(ABC):
33
51
  A toolset is a collection of tools that can be used by an agent.
34
52
  """
35
53
 
54
+ def __init__(
55
+ self, *, tool_filter: Optional[Union[ToolPredicate, List[str]]] = None
56
+ ):
57
+ self.tool_filter = tool_filter
58
+
36
59
  @abstractmethod
37
60
  async def get_tools(
38
- self, readonly_context: Optional[ReadonlyContext] = None
61
+ self,
62
+ readonly_context: Optional[ReadonlyContext] = None,
39
63
  ) -> list[BaseTool]:
40
64
  """Return all tools in the toolset based on the provided context.
41
65
 
@@ -56,3 +80,17 @@ class BaseToolset(ABC):
56
80
  should ensure that any open connections, files, or other managed
57
81
  resources are properly released to prevent leaks.
58
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,38 @@
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
+ """
29
+
30
+ from .bigquery_credentials import BigQueryCredentialsConfig
31
+ from .bigquery_tool import BigQueryTool
32
+ from .bigquery_toolset import BigQueryToolset
33
+
34
+ __all__ = [
35
+ "BigQueryTool",
36
+ "BigQueryToolset",
37
+ "BigQueryCredentialsConfig",
38
+ ]