google-adk 1.0.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/callback_context.py +2 -1
- google/adk/agents/readonly_context.py +3 -1
- google/adk/auth/auth_credential.py +4 -1
- google/adk/cli/browser/index.html +4 -4
- google/adk/cli/browser/{main-QOEMUXM4.js → main-PKDNKWJE.js} +59 -59
- google/adk/cli/browser/polyfills-B6TNHZQ6.js +17 -0
- google/adk/cli/cli.py +3 -2
- google/adk/cli/cli_eval.py +6 -85
- google/adk/cli/cli_tools_click.py +39 -10
- google/adk/cli/fast_api.py +53 -184
- google/adk/cli/utils/agent_loader.py +137 -0
- google/adk/cli/utils/cleanup.py +40 -0
- google/adk/cli/utils/evals.py +2 -1
- google/adk/cli/utils/logs.py +2 -7
- 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/eval_case.py +3 -1
- google/adk/evaluation/eval_metrics.py +74 -0
- google/adk/evaluation/eval_result.py +86 -0
- google/adk/evaluation/eval_set.py +2 -0
- google/adk/evaluation/eval_set_results_manager.py +47 -0
- google/adk/evaluation/eval_sets_manager.py +2 -1
- google/adk/evaluation/evaluator.py +2 -0
- google/adk/evaluation/local_eval_set_results_manager.py +113 -0
- google/adk/evaluation/local_eval_sets_manager.py +4 -4
- google/adk/evaluation/response_evaluator.py +2 -1
- google/adk/evaluation/trajectory_evaluator.py +3 -2
- google/adk/examples/base_example_provider.py +1 -0
- google/adk/flows/llm_flows/base_llm_flow.py +4 -6
- google/adk/flows/llm_flows/contents.py +3 -1
- google/adk/flows/llm_flows/instructions.py +7 -77
- google/adk/flows/llm_flows/single_flow.py +1 -1
- google/adk/models/base_llm.py +2 -1
- google/adk/models/base_llm_connection.py +2 -0
- google/adk/models/google_llm.py +4 -1
- google/adk/models/lite_llm.py +3 -2
- google/adk/models/llm_response.py +2 -1
- google/adk/runners.py +36 -4
- google/adk/sessions/_session_util.py +2 -1
- google/adk/sessions/database_session_service.py +5 -8
- google/adk/sessions/vertex_ai_session_service.py +28 -13
- google/adk/telemetry.py +4 -2
- google/adk/tools/agent_tool.py +1 -1
- google/adk/tools/apihub_tool/apihub_toolset.py +1 -1
- 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 +6 -2
- google/adk/tools/application_integration_tool/clients/connections_client.py +8 -1
- google/adk/tools/application_integration_tool/clients/integration_client.py +3 -1
- google/adk/tools/application_integration_tool/integration_connector_tool.py +1 -1
- google/adk/tools/base_toolset.py +40 -2
- 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/function_parameter_parse_util.py +7 -0
- 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 +17 -11
- google/adk/tools/google_api_tool/google_api_tool.py +1 -1
- google/adk/tools/google_api_tool/google_api_toolset.py +0 -14
- google/adk/tools/google_api_tool/google_api_toolsets.py +8 -2
- google/adk/tools/google_search_tool.py +2 -2
- google/adk/tools/mcp_tool/conversion_utils.py +6 -2
- google/adk/tools/mcp_tool/mcp_session_manager.py +62 -188
- google/adk/tools/mcp_tool/mcp_tool.py +27 -24
- google/adk/tools/mcp_tool/mcp_toolset.py +76 -131
- 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 +2 -7
- google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +5 -1
- google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +11 -1
- google/adk/tools/toolbox_toolset.py +31 -3
- google/adk/utils/__init__.py +13 -0
- google/adk/utils/instructions_utils.py +131 -0
- google/adk/version.py +1 -1
- {google_adk-1.0.0.dist-info → google_adk-1.1.0.dist-info}/METADATA +12 -15
- {google_adk-1.0.0.dist-info → google_adk-1.1.0.dist-info}/RECORD +83 -78
- google/adk/agents/base_agent.py.orig +0 -330
- google/adk/cli/browser/polyfills-FFHMD2TL.js +0 -18
- google/adk/cli/fast_api.py.orig +0 -822
- google/adk/memory/base_memory_service.py.orig +0 -76
- google/adk/models/google_llm.py.orig +0 -305
- google/adk/tools/_built_in_code_execution_tool.py +0 -70
- google/adk/tools/mcp_tool/mcp_session_manager.py.orig +0 -322
- {google_adk-1.0.0.dist-info → google_adk-1.1.0.dist-info}/WHEEL +0 -0
- {google_adk-1.0.0.dist-info → google_adk-1.1.0.dist-info}/entry_points.txt +0 -0
- {google_adk-1.0.0.dist-info → google_adk-1.1.0.dist-info}/licenses/LICENSE +0 -0
google/adk/models/google_llm.py
CHANGED
@@ -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=
|
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:
|
google/adk/models/lite_llm.py
CHANGED
@@ -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
|
-
|
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
|
)
|
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.
|
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
|
413
|
-
self.agent.
|
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.
|
@@ -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
|
-
|
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.
|
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
|
-
|
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
|
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
|
-
|
94
|
+
await asyncio.sleep(1)
|
93
95
|
max_retry_attempt -= 1
|
94
96
|
|
95
97
|
# Get session resource
|
96
|
-
get_session_api_response = await
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
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(
|
google/adk/tools/agent_tool.py
CHANGED
@@ -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
|
15
|
+
from abc import ABC
|
16
|
+
from abc import abstractmethod
|
16
17
|
import base64
|
17
18
|
import json
|
18
|
-
from typing import Any
|
19
|
-
from
|
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
|
@@ -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
|
-
|
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
|
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
|
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
|
google/adk/tools/base_toolset.py
CHANGED
@@ -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
|
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,
|
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,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
|
+
"""
|