google-adk 0.3.0__py3-none-any.whl → 0.5.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/active_streaming_tool.py +1 -0
- google/adk/agents/base_agent.py +27 -29
- google/adk/agents/callback_context.py +4 -4
- google/adk/agents/invocation_context.py +1 -0
- google/adk/agents/langgraph_agent.py +1 -0
- google/adk/agents/live_request_queue.py +1 -0
- google/adk/agents/llm_agent.py +54 -14
- google/adk/agents/run_config.py +4 -0
- google/adk/agents/transcription_entry.py +1 -0
- google/adk/artifacts/base_artifact_service.py +5 -10
- google/adk/artifacts/gcs_artifact_service.py +8 -8
- google/adk/artifacts/in_memory_artifact_service.py +5 -5
- google/adk/auth/auth_credential.py +4 -5
- google/adk/cli/browser/index.html +1 -1
- google/adk/cli/browser/{main-HWIBUY2R.js → main-ULN5R5I5.js} +40 -39
- google/adk/cli/cli.py +54 -47
- google/adk/cli/cli_eval.py +13 -11
- google/adk/cli/cli_tools_click.py +58 -7
- google/adk/cli/fast_api.py +11 -11
- google/adk/cli/fast_api.py.orig +728 -0
- google/adk/evaluation/agent_evaluator.py +3 -3
- google/adk/evaluation/evaluation_constants.py +1 -0
- google/adk/evaluation/evaluation_generator.py +5 -5
- google/adk/evaluation/response_evaluator.py +1 -1
- google/adk/events/event.py +1 -0
- google/adk/events/event_actions.py +10 -4
- google/adk/examples/example.py +1 -0
- google/adk/flows/__init__.py +0 -1
- google/adk/flows/llm_flows/_code_execution.py +10 -10
- google/adk/flows/llm_flows/base_llm_flow.py +40 -15
- google/adk/flows/llm_flows/basic.py +3 -0
- google/adk/flows/llm_flows/contents.py +9 -5
- google/adk/flows/llm_flows/functions.py +38 -16
- google/adk/flows/llm_flows/instructions.py +17 -6
- google/adk/memory/base_memory_service.py +4 -2
- google/adk/memory/in_memory_memory_service.py +2 -2
- google/adk/memory/vertex_ai_rag_memory_service.py +2 -2
- google/adk/models/anthropic_llm.py +20 -2
- google/adk/models/base_llm.py +45 -4
- google/adk/models/gemini_llm_connection.py +14 -1
- google/adk/models/google_llm.py +0 -42
- google/adk/models/lite_llm.py +17 -17
- google/adk/models/llm_request.py +1 -1
- google/adk/models/llm_response.py +1 -1
- google/adk/runners.py +5 -5
- google/adk/sessions/_session_util.py +43 -0
- google/adk/sessions/base_session_service.py +3 -0
- google/adk/sessions/database_session_service.py +63 -46
- google/adk/sessions/in_memory_session_service.py +3 -3
- google/adk/sessions/session.py +1 -0
- google/adk/sessions/vertex_ai_session_service.py +7 -5
- google/adk/tools/agent_tool.py +7 -4
- google/adk/tools/application_integration_tool/__init__.py +2 -0
- google/adk/tools/application_integration_tool/application_integration_toolset.py +48 -26
- google/adk/tools/application_integration_tool/clients/connections_client.py +33 -77
- google/adk/tools/application_integration_tool/integration_connector_tool.py +159 -0
- google/adk/tools/function_tool.py +42 -0
- google/adk/tools/load_artifacts_tool.py +4 -4
- google/adk/tools/load_memory_tool.py +4 -2
- google/adk/tools/mcp_tool/conversion_utils.py +1 -1
- google/adk/tools/mcp_tool/mcp_session_manager.py +14 -0
- google/adk/tools/openapi_tool/common/common.py +2 -5
- google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +13 -3
- google/adk/tools/preload_memory_tool.py +1 -1
- google/adk/tools/tool_context.py +4 -4
- google/adk/version.py +1 -1
- {google_adk-0.3.0.dist-info → google_adk-0.5.0.dist-info}/METADATA +3 -7
- {google_adk-0.3.0.dist-info → google_adk-0.5.0.dist-info}/RECORD +71 -68
- {google_adk-0.3.0.dist-info → google_adk-0.5.0.dist-info}/WHEEL +0 -0
- {google_adk-0.3.0.dist-info → google_adk-0.5.0.dist-info}/entry_points.txt +0 -0
- {google_adk-0.3.0.dist-info → google_adk-0.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -13,7 +13,9 @@
|
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
15
|
from .application_integration_toolset import ApplicationIntegrationToolset
|
16
|
+
from .integration_connector_tool import IntegrationConnectorTool
|
16
17
|
|
17
18
|
__all__ = [
|
18
19
|
'ApplicationIntegrationToolset',
|
20
|
+
'IntegrationConnectorTool',
|
19
21
|
]
|
@@ -12,21 +12,21 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
from typing import Dict
|
16
|
-
from typing import List
|
17
|
-
from typing import Optional
|
15
|
+
from typing import Dict, List, Optional
|
18
16
|
|
19
17
|
from fastapi.openapi.models import HTTPBearer
|
20
|
-
from google.adk.tools.application_integration_tool.clients.connections_client import ConnectionsClient
|
21
|
-
from google.adk.tools.application_integration_tool.clients.integration_client import IntegrationClient
|
22
|
-
from google.adk.tools.openapi_tool.auth.auth_helpers import service_account_scheme_credential
|
23
|
-
from google.adk.tools.openapi_tool.openapi_spec_parser.openapi_toolset import OpenAPIToolset
|
24
|
-
from google.adk.tools.openapi_tool.openapi_spec_parser.rest_api_tool import RestApiTool
|
25
18
|
|
26
19
|
from ...auth.auth_credential import AuthCredential
|
27
20
|
from ...auth.auth_credential import AuthCredentialTypes
|
28
21
|
from ...auth.auth_credential import ServiceAccount
|
29
22
|
from ...auth.auth_credential import ServiceAccountCredential
|
23
|
+
from ..openapi_tool.auth.auth_helpers import service_account_scheme_credential
|
24
|
+
from ..openapi_tool.openapi_spec_parser.openapi_spec_parser import OpenApiSpecParser
|
25
|
+
from ..openapi_tool.openapi_spec_parser.openapi_toolset import OpenAPIToolset
|
26
|
+
from ..openapi_tool.openapi_spec_parser.rest_api_tool import RestApiTool
|
27
|
+
from .clients.connections_client import ConnectionsClient
|
28
|
+
from .clients.integration_client import IntegrationClient
|
29
|
+
from .integration_connector_tool import IntegrationConnectorTool
|
30
30
|
|
31
31
|
|
32
32
|
# TODO(cheliu): Apply a common toolset interface
|
@@ -168,6 +168,7 @@ class ApplicationIntegrationToolset:
|
|
168
168
|
actions,
|
169
169
|
service_account_json,
|
170
170
|
)
|
171
|
+
connection_details = {}
|
171
172
|
if integration and trigger:
|
172
173
|
spec = integration_client.get_openapi_spec_for_integration()
|
173
174
|
elif connection and (entity_operations or actions):
|
@@ -175,16 +176,6 @@ class ApplicationIntegrationToolset:
|
|
175
176
|
project, location, connection, service_account_json
|
176
177
|
)
|
177
178
|
connection_details = connections_client.get_connection_details()
|
178
|
-
tool_instructions += (
|
179
|
-
"ALWAYS use serviceName = "
|
180
|
-
+ connection_details["serviceName"]
|
181
|
-
+ ", host = "
|
182
|
-
+ connection_details["host"]
|
183
|
-
+ " and the connection name = "
|
184
|
-
+ f"projects/{project}/locations/{location}/connections/{connection} when"
|
185
|
-
" using this tool"
|
186
|
-
+ ". DONOT ask the user for these values as you already have those."
|
187
|
-
)
|
188
179
|
spec = integration_client.get_openapi_spec_for_connection(
|
189
180
|
tool_name,
|
190
181
|
tool_instructions,
|
@@ -194,9 +185,9 @@ class ApplicationIntegrationToolset:
|
|
194
185
|
"Either (integration and trigger) or (connection and"
|
195
186
|
" (entity_operations or actions)) should be provided."
|
196
187
|
)
|
197
|
-
self._parse_spec_to_tools(spec)
|
188
|
+
self._parse_spec_to_tools(spec, connection_details)
|
198
189
|
|
199
|
-
def _parse_spec_to_tools(self, spec_dict):
|
190
|
+
def _parse_spec_to_tools(self, spec_dict, connection_details):
|
200
191
|
"""Parses the spec dict to a list of RestApiTool."""
|
201
192
|
if self.service_account_json:
|
202
193
|
sa_credential = ServiceAccountCredential.model_validate_json(
|
@@ -218,12 +209,43 @@ class ApplicationIntegrationToolset:
|
|
218
209
|
),
|
219
210
|
)
|
220
211
|
auth_scheme = HTTPBearer(bearerFormat="JWT")
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
212
|
+
|
213
|
+
if self.integration and self.trigger:
|
214
|
+
tools = OpenAPIToolset(
|
215
|
+
spec_dict=spec_dict,
|
216
|
+
auth_credential=auth_credential,
|
217
|
+
auth_scheme=auth_scheme,
|
218
|
+
).get_tools()
|
219
|
+
for tool in tools:
|
220
|
+
self.generated_tools[tool.name] = tool
|
221
|
+
return
|
222
|
+
|
223
|
+
operations = OpenApiSpecParser().parse(spec_dict)
|
224
|
+
|
225
|
+
for open_api_operation in operations:
|
226
|
+
operation = getattr(open_api_operation.operation, "x-operation")
|
227
|
+
entity = None
|
228
|
+
action = None
|
229
|
+
if hasattr(open_api_operation.operation, "x-entity"):
|
230
|
+
entity = getattr(open_api_operation.operation, "x-entity")
|
231
|
+
elif hasattr(open_api_operation.operation, "x-action"):
|
232
|
+
action = getattr(open_api_operation.operation, "x-action")
|
233
|
+
rest_api_tool = RestApiTool.from_parsed_operation(open_api_operation)
|
234
|
+
if auth_scheme:
|
235
|
+
rest_api_tool.configure_auth_scheme(auth_scheme)
|
236
|
+
if auth_credential:
|
237
|
+
rest_api_tool.configure_auth_credential(auth_credential)
|
238
|
+
tool = IntegrationConnectorTool(
|
239
|
+
name=rest_api_tool.name,
|
240
|
+
description=rest_api_tool.description,
|
241
|
+
connection_name=connection_details["name"],
|
242
|
+
connection_host=connection_details["host"],
|
243
|
+
connection_service_name=connection_details["serviceName"],
|
244
|
+
entity=entity,
|
245
|
+
action=action,
|
246
|
+
operation=operation,
|
247
|
+
rest_api_tool=rest_api_tool,
|
248
|
+
)
|
227
249
|
self.generated_tools[tool.name] = tool
|
228
250
|
|
229
251
|
def get_tools(self) -> List[RestApiTool]:
|
@@ -68,12 +68,14 @@ class ConnectionsClient:
|
|
68
68
|
response = self._execute_api_call(url)
|
69
69
|
|
70
70
|
connection_data = response.json()
|
71
|
+
connection_name = connection_data.get("name", "")
|
71
72
|
service_name = connection_data.get("serviceDirectory", "")
|
72
73
|
host = connection_data.get("host", "")
|
73
74
|
if host:
|
74
75
|
service_name = connection_data.get("tlsServiceDirectory", "")
|
75
76
|
auth_override_enabled = connection_data.get("authOverrideEnabled", False)
|
76
77
|
return {
|
78
|
+
"name": connection_name,
|
77
79
|
"serviceName": service_name,
|
78
80
|
"host": host,
|
79
81
|
"authOverrideEnabled": auth_override_enabled,
|
@@ -291,13 +293,9 @@ class ConnectionsClient:
|
|
291
293
|
tool_name: str = "",
|
292
294
|
tool_instructions: str = "",
|
293
295
|
) -> Dict[str, Any]:
|
294
|
-
description =
|
295
|
-
f"Use this tool with" f' action = "{action}" and'
|
296
|
-
) + f' operation = "{operation}" only. Dont ask these values from user.'
|
296
|
+
description = f"Use this tool to execute {action}"
|
297
297
|
if operation == "EXECUTE_QUERY":
|
298
|
-
description
|
299
|
-
(f"Use this tool with" f' action = "{action}" and')
|
300
|
-
+ f' operation = "{operation}" only. Dont ask these values from user.'
|
298
|
+
description += (
|
301
299
|
" Use pageSize = 50 and timeout = 120 until user specifies a"
|
302
300
|
" different value otherwise. If user provides a query in natural"
|
303
301
|
" language, convert it to SQL query and then execute it using the"
|
@@ -308,13 +306,13 @@ class ConnectionsClient:
|
|
308
306
|
"summary": f"{action_display_name}",
|
309
307
|
"description": f"{description} {tool_instructions}",
|
310
308
|
"operationId": f"{tool_name}_{action_display_name}",
|
309
|
+
"x-action": f"{action}",
|
310
|
+
"x-operation": f"{operation}",
|
311
311
|
"requestBody": {
|
312
312
|
"content": {
|
313
313
|
"application/json": {
|
314
314
|
"schema": {
|
315
|
-
"$ref":
|
316
|
-
f"#/components/schemas/{action_display_name}_Request"
|
317
|
-
)
|
315
|
+
"$ref": f"#/components/schemas/{action_display_name}_Request"
|
318
316
|
}
|
319
317
|
}
|
320
318
|
}
|
@@ -325,9 +323,7 @@ class ConnectionsClient:
|
|
325
323
|
"content": {
|
326
324
|
"application/json": {
|
327
325
|
"schema": {
|
328
|
-
"$ref":
|
329
|
-
f"#/components/schemas/{action_display_name}_Response"
|
330
|
-
),
|
326
|
+
"$ref": f"#/components/schemas/{action_display_name}_Response",
|
331
327
|
}
|
332
328
|
}
|
333
329
|
},
|
@@ -346,17 +342,11 @@ class ConnectionsClient:
|
|
346
342
|
return {
|
347
343
|
"post": {
|
348
344
|
"summary": f"List {entity}",
|
349
|
-
"description":
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
+ ' user. Always use ""'
|
355
|
-
+ ' as filter clause and ""'
|
356
|
-
+ " as page token and 50 as page size until user specifies a"
|
357
|
-
" different value otherwise. Use single quotes for strings in"
|
358
|
-
f" filter clause. {tool_instructions}"
|
359
|
-
),
|
345
|
+
"description": f"""Returns the list of {entity} data. If the page token was available in the response, let users know there are more records available. Ask if the user wants to fetch the next page of results. When passing filter use the
|
346
|
+
following format: `field_name1='value1' AND field_name2='value2'
|
347
|
+
`. {tool_instructions}""",
|
348
|
+
"x-operation": "LIST_ENTITIES",
|
349
|
+
"x-entity": f"{entity}",
|
360
350
|
"operationId": f"{tool_name}_list_{entity}",
|
361
351
|
"requestBody": {
|
362
352
|
"content": {
|
@@ -379,9 +369,7 @@ class ConnectionsClient:
|
|
379
369
|
f"Returns a list of {entity} of json"
|
380
370
|
f" schema: {schema_as_string}"
|
381
371
|
),
|
382
|
-
"$ref":
|
383
|
-
"#/components/schemas/execute-connector_Response"
|
384
|
-
),
|
372
|
+
"$ref": "#/components/schemas/execute-connector_Response",
|
385
373
|
}
|
386
374
|
}
|
387
375
|
},
|
@@ -401,14 +389,11 @@ class ConnectionsClient:
|
|
401
389
|
"post": {
|
402
390
|
"summary": f"Get {entity}",
|
403
391
|
"description": (
|
404
|
-
|
405
|
-
f"Returns the details of the {entity}. Use this tool with"
|
406
|
-
f' entity = "{entity}" and'
|
407
|
-
)
|
408
|
-
+ ' operation = "GET_ENTITY" only. Dont ask these values from'
|
409
|
-
f" user. {tool_instructions}"
|
392
|
+
f"Returns the details of the {entity}. {tool_instructions}"
|
410
393
|
),
|
411
394
|
"operationId": f"{tool_name}_get_{entity}",
|
395
|
+
"x-operation": "GET_ENTITY",
|
396
|
+
"x-entity": f"{entity}",
|
412
397
|
"requestBody": {
|
413
398
|
"content": {
|
414
399
|
"application/json": {
|
@@ -428,9 +413,7 @@ class ConnectionsClient:
|
|
428
413
|
f"Returns {entity} of json schema:"
|
429
414
|
f" {schema_as_string}"
|
430
415
|
),
|
431
|
-
"$ref":
|
432
|
-
"#/components/schemas/execute-connector_Response"
|
433
|
-
),
|
416
|
+
"$ref": "#/components/schemas/execute-connector_Response",
|
434
417
|
}
|
435
418
|
}
|
436
419
|
},
|
@@ -445,17 +428,10 @@ class ConnectionsClient:
|
|
445
428
|
) -> Dict[str, Any]:
|
446
429
|
return {
|
447
430
|
"post": {
|
448
|
-
"summary": f"
|
449
|
-
"description":
|
450
|
-
|
451
|
-
|
452
|
-
f' entity = "{entity}" and'
|
453
|
-
)
|
454
|
-
+ ' operation = "CREATE_ENTITY" only. Dont ask these values'
|
455
|
-
" from"
|
456
|
-
+ " user. Follow the schema of the entity provided in the"
|
457
|
-
f" instructions to create {entity}. {tool_instructions}"
|
458
|
-
),
|
431
|
+
"summary": f"Creates a new {entity}",
|
432
|
+
"description": f"Creates a new {entity}. {tool_instructions}",
|
433
|
+
"x-operation": "CREATE_ENTITY",
|
434
|
+
"x-entity": f"{entity}",
|
459
435
|
"operationId": f"{tool_name}_create_{entity}",
|
460
436
|
"requestBody": {
|
461
437
|
"content": {
|
@@ -474,9 +450,7 @@ class ConnectionsClient:
|
|
474
450
|
"content": {
|
475
451
|
"application/json": {
|
476
452
|
"schema": {
|
477
|
-
"$ref":
|
478
|
-
"#/components/schemas/execute-connector_Response"
|
479
|
-
)
|
453
|
+
"$ref": "#/components/schemas/execute-connector_Response"
|
480
454
|
}
|
481
455
|
}
|
482
456
|
},
|
@@ -491,18 +465,10 @@ class ConnectionsClient:
|
|
491
465
|
) -> Dict[str, Any]:
|
492
466
|
return {
|
493
467
|
"post": {
|
494
|
-
"summary": f"
|
495
|
-
"description":
|
496
|
-
|
497
|
-
|
498
|
-
f' entity = "{entity}" and'
|
499
|
-
)
|
500
|
-
+ ' operation = "UPDATE_ENTITY" only. Dont ask these values'
|
501
|
-
" from"
|
502
|
-
+ " user. Use entityId to uniquely identify the entity to"
|
503
|
-
" update. Follow the schema of the entity provided in the"
|
504
|
-
f" instructions to update {entity}. {tool_instructions}"
|
505
|
-
),
|
468
|
+
"summary": f"Updates the {entity}",
|
469
|
+
"description": f"Updates the {entity}. {tool_instructions}",
|
470
|
+
"x-operation": "UPDATE_ENTITY",
|
471
|
+
"x-entity": f"{entity}",
|
506
472
|
"operationId": f"{tool_name}_update_{entity}",
|
507
473
|
"requestBody": {
|
508
474
|
"content": {
|
@@ -521,9 +487,7 @@ class ConnectionsClient:
|
|
521
487
|
"content": {
|
522
488
|
"application/json": {
|
523
489
|
"schema": {
|
524
|
-
"$ref":
|
525
|
-
"#/components/schemas/execute-connector_Response"
|
526
|
-
)
|
490
|
+
"$ref": "#/components/schemas/execute-connector_Response"
|
527
491
|
}
|
528
492
|
}
|
529
493
|
},
|
@@ -538,16 +502,10 @@ class ConnectionsClient:
|
|
538
502
|
) -> Dict[str, Any]:
|
539
503
|
return {
|
540
504
|
"post": {
|
541
|
-
"summary": f"Delete {entity}",
|
542
|
-
"description":
|
543
|
-
|
544
|
-
|
545
|
-
f' entity = "{entity}" and'
|
546
|
-
)
|
547
|
-
+ ' operation = "DELETE_ENTITY" only. Dont ask these values'
|
548
|
-
" from"
|
549
|
-
f" user. {tool_instructions}"
|
550
|
-
),
|
505
|
+
"summary": f"Delete the {entity}",
|
506
|
+
"description": f"Deletes the {entity}. {tool_instructions}",
|
507
|
+
"x-operation": "DELETE_ENTITY",
|
508
|
+
"x-entity": f"{entity}",
|
551
509
|
"operationId": f"{tool_name}_delete_{entity}",
|
552
510
|
"requestBody": {
|
553
511
|
"content": {
|
@@ -566,9 +524,7 @@ class ConnectionsClient:
|
|
566
524
|
"content": {
|
567
525
|
"application/json": {
|
568
526
|
"schema": {
|
569
|
-
"$ref":
|
570
|
-
"#/components/schemas/execute-connector_Response"
|
571
|
-
)
|
527
|
+
"$ref": "#/components/schemas/execute-connector_Response"
|
572
528
|
}
|
573
529
|
}
|
574
530
|
},
|
@@ -0,0 +1,159 @@
|
|
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 logging
|
17
|
+
from typing import Any
|
18
|
+
from typing import Dict
|
19
|
+
from typing import Optional
|
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
|
+
from google.genai.types import FunctionDeclaration
|
24
|
+
from typing_extensions import override
|
25
|
+
|
26
|
+
from .. import BaseTool
|
27
|
+
from ..tool_context import ToolContext
|
28
|
+
|
29
|
+
logger = logging.getLogger(__name__)
|
30
|
+
|
31
|
+
|
32
|
+
class IntegrationConnectorTool(BaseTool):
|
33
|
+
"""A tool that wraps a RestApiTool to interact with a specific Application Integration endpoint.
|
34
|
+
|
35
|
+
This tool adds Application Integration specific context like connection
|
36
|
+
details, entity, operation, and action to the underlying REST API call
|
37
|
+
handled by RestApiTool. It prepares the arguments and then delegates the
|
38
|
+
actual API call execution to the contained RestApiTool instance.
|
39
|
+
|
40
|
+
* Generates request params and body
|
41
|
+
* Attaches auth credentials to API call.
|
42
|
+
|
43
|
+
Example:
|
44
|
+
```
|
45
|
+
# Each API operation in the spec will be turned into its own tool
|
46
|
+
# Name of the tool is the operationId of that operation, in snake case
|
47
|
+
operations = OperationGenerator().parse(openapi_spec_dict)
|
48
|
+
tool = [RestApiTool.from_parsed_operation(o) for o in operations]
|
49
|
+
```
|
50
|
+
"""
|
51
|
+
|
52
|
+
EXCLUDE_FIELDS = [
|
53
|
+
'connection_name',
|
54
|
+
'service_name',
|
55
|
+
'host',
|
56
|
+
'entity',
|
57
|
+
'operation',
|
58
|
+
'action',
|
59
|
+
]
|
60
|
+
|
61
|
+
OPTIONAL_FIELDS = [
|
62
|
+
'page_size',
|
63
|
+
'page_token',
|
64
|
+
'filter',
|
65
|
+
]
|
66
|
+
|
67
|
+
def __init__(
|
68
|
+
self,
|
69
|
+
name: str,
|
70
|
+
description: str,
|
71
|
+
connection_name: str,
|
72
|
+
connection_host: str,
|
73
|
+
connection_service_name: str,
|
74
|
+
entity: str,
|
75
|
+
operation: str,
|
76
|
+
action: str,
|
77
|
+
rest_api_tool: RestApiTool,
|
78
|
+
):
|
79
|
+
"""Initializes the ApplicationIntegrationTool.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
name: The name of the tool, typically derived from the API operation.
|
83
|
+
Should be unique and adhere to Gemini function naming conventions
|
84
|
+
(e.g., less than 64 characters).
|
85
|
+
description: A description of what the tool does, usually based on the
|
86
|
+
API operation's summary or description.
|
87
|
+
connection_name: The name of the Integration Connector connection.
|
88
|
+
connection_host: The hostname or IP address for the connection.
|
89
|
+
connection_service_name: The specific service name within the host.
|
90
|
+
entity: The Integration Connector entity being targeted.
|
91
|
+
operation: The specific operation being performed on the entity.
|
92
|
+
action: The action associated with the operation (e.g., 'execute').
|
93
|
+
rest_api_tool: An initialized RestApiTool instance that handles the
|
94
|
+
underlying REST API communication based on an OpenAPI specification
|
95
|
+
operation. This tool will be called by ApplicationIntegrationTool with
|
96
|
+
added connection and context arguments. tool =
|
97
|
+
[RestApiTool.from_parsed_operation(o) for o in operations]
|
98
|
+
"""
|
99
|
+
# Gemini restrict the length of function name to be less than 64 characters
|
100
|
+
super().__init__(
|
101
|
+
name=name,
|
102
|
+
description=description,
|
103
|
+
)
|
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
|
111
|
+
|
112
|
+
@override
|
113
|
+
def _get_declaration(self) -> FunctionDeclaration:
|
114
|
+
"""Returns the function declaration in the Gemini Schema format."""
|
115
|
+
schema_dict = self.rest_api_tool._operation_parser.get_json_schema()
|
116
|
+
for field in self.EXCLUDE_FIELDS:
|
117
|
+
if field in schema_dict['properties']:
|
118
|
+
del schema_dict['properties'][field]
|
119
|
+
for field in self.OPTIONAL_FIELDS + self.EXCLUDE_FIELDS:
|
120
|
+
if field in schema_dict['required']:
|
121
|
+
schema_dict['required'].remove(field)
|
122
|
+
|
123
|
+
parameters = to_gemini_schema(schema_dict)
|
124
|
+
function_decl = FunctionDeclaration(
|
125
|
+
name=self.name, description=self.description, parameters=parameters
|
126
|
+
)
|
127
|
+
return function_decl
|
128
|
+
|
129
|
+
@override
|
130
|
+
async def run_async(
|
131
|
+
self, *, args: dict[str, Any], tool_context: Optional[ToolContext]
|
132
|
+
) -> 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
|
139
|
+
logger.info('Running tool: %s with args: %s', self.name, args)
|
140
|
+
return self.rest_api_tool.call(args=args, tool_context=tool_context)
|
141
|
+
|
142
|
+
def __str__(self):
|
143
|
+
return (
|
144
|
+
f'ApplicationIntegrationTool(name="{self.name}",'
|
145
|
+
f' description="{self.description}",'
|
146
|
+
f' connection_name="{self.connection_name}", entity="{self.entity}",'
|
147
|
+
f' operation="{self.operation}", action="{self.action}")'
|
148
|
+
)
|
149
|
+
|
150
|
+
def __repr__(self):
|
151
|
+
return (
|
152
|
+
f'ApplicationIntegrationTool(name="{self.name}",'
|
153
|
+
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)})'
|
159
|
+
)
|
@@ -59,6 +59,23 @@ class FunctionTool(BaseTool):
|
|
59
59
|
if 'tool_context' in signature.parameters:
|
60
60
|
args_to_call['tool_context'] = tool_context
|
61
61
|
|
62
|
+
# Before invoking the function, we check for if the list of args passed in
|
63
|
+
# has all the mandatory arguments or not.
|
64
|
+
# If the check fails, then we don't invoke the tool and let the Agent know
|
65
|
+
# that there was a missing a input parameter. This will basically help
|
66
|
+
# the underlying model fix the issue and retry.
|
67
|
+
mandatory_args = self._get_mandatory_args()
|
68
|
+
missing_mandatory_args = [
|
69
|
+
arg for arg in mandatory_args if arg not in args_to_call
|
70
|
+
]
|
71
|
+
|
72
|
+
if missing_mandatory_args:
|
73
|
+
missing_mandatory_args_str = '\n'.join(missing_mandatory_args)
|
74
|
+
error_str = f"""Invoking `{self.name}()` failed as the following mandatory input parameters are not present:
|
75
|
+
{missing_mandatory_args_str}
|
76
|
+
You could retry calling this tool, but it is IMPORTANT for you to provide all the mandatory parameters."""
|
77
|
+
return {'error': error_str}
|
78
|
+
|
62
79
|
if inspect.iscoroutinefunction(self.func):
|
63
80
|
return await self.func(**args_to_call) or {}
|
64
81
|
else:
|
@@ -85,3 +102,28 @@ class FunctionTool(BaseTool):
|
|
85
102
|
args_to_call['tool_context'] = tool_context
|
86
103
|
async for item in self.func(**args_to_call):
|
87
104
|
yield item
|
105
|
+
|
106
|
+
def _get_mandatory_args(
|
107
|
+
self,
|
108
|
+
) -> list[str]:
|
109
|
+
"""Identifies mandatory parameters (those without default values) for a function.
|
110
|
+
|
111
|
+
Returns:
|
112
|
+
A list of strings, where each string is the name of a mandatory parameter.
|
113
|
+
"""
|
114
|
+
signature = inspect.signature(self.func)
|
115
|
+
mandatory_params = []
|
116
|
+
|
117
|
+
for name, param in signature.parameters.items():
|
118
|
+
# A parameter is mandatory if:
|
119
|
+
# 1. It has no default value (param.default is inspect.Parameter.empty)
|
120
|
+
# 2. It's not a variable positional (*args) or variable keyword (**kwargs) parameter
|
121
|
+
#
|
122
|
+
# For more refer to: https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind
|
123
|
+
if param.default == inspect.Parameter.empty and param.kind not in (
|
124
|
+
inspect.Parameter.VAR_POSITIONAL,
|
125
|
+
inspect.Parameter.VAR_KEYWORD,
|
126
|
+
):
|
127
|
+
mandatory_params.append(name)
|
128
|
+
|
129
|
+
return mandatory_params
|
@@ -69,14 +69,14 @@ class LoadArtifactsTool(BaseTool):
|
|
69
69
|
tool_context=tool_context,
|
70
70
|
llm_request=llm_request,
|
71
71
|
)
|
72
|
-
self._append_artifacts_to_llm_request(
|
72
|
+
await self._append_artifacts_to_llm_request(
|
73
73
|
tool_context=tool_context, llm_request=llm_request
|
74
74
|
)
|
75
75
|
|
76
|
-
def _append_artifacts_to_llm_request(
|
76
|
+
async def _append_artifacts_to_llm_request(
|
77
77
|
self, *, tool_context: ToolContext, llm_request: LlmRequest
|
78
78
|
):
|
79
|
-
artifact_names = tool_context.list_artifacts()
|
79
|
+
artifact_names = await tool_context.list_artifacts()
|
80
80
|
if not artifact_names:
|
81
81
|
return
|
82
82
|
|
@@ -96,7 +96,7 @@ class LoadArtifactsTool(BaseTool):
|
|
96
96
|
if function_response and function_response.name == 'load_artifacts':
|
97
97
|
artifact_names = function_response.response['artifact_names']
|
98
98
|
for artifact_name in artifact_names:
|
99
|
-
artifact = tool_context.load_artifact(artifact_name)
|
99
|
+
artifact = await tool_context.load_artifact(artifact_name)
|
100
100
|
llm_request.contents.append(
|
101
101
|
types.Content(
|
102
102
|
role='user',
|
@@ -27,7 +27,9 @@ if TYPE_CHECKING:
|
|
27
27
|
from ..models import LlmRequest
|
28
28
|
|
29
29
|
|
30
|
-
def load_memory(
|
30
|
+
async def load_memory(
|
31
|
+
query: str, tool_context: ToolContext
|
32
|
+
) -> 'list[MemoryResult]':
|
31
33
|
"""Loads the memory for the current user.
|
32
34
|
|
33
35
|
Args:
|
@@ -36,7 +38,7 @@ def load_memory(query: str, tool_context: ToolContext) -> 'list[MemoryResult]':
|
|
36
38
|
Returns:
|
37
39
|
A list of memory results.
|
38
40
|
"""
|
39
|
-
response = tool_context.search_memory(query)
|
41
|
+
response = await tool_context.search_memory(query)
|
40
42
|
return response.memories
|
41
43
|
|
42
44
|
|
@@ -22,7 +22,7 @@ def adk_to_mcp_tool_type(tool: BaseTool) -> mcp_types.Tool:
|
|
22
22
|
"""Convert a Tool in ADK into MCP tool type.
|
23
23
|
|
24
24
|
This function transforms an ADK tool definition into its equivalent
|
25
|
-
representation in the MCP (Model
|
25
|
+
representation in the MCP (Model Context Protocol) system.
|
26
26
|
|
27
27
|
Args:
|
28
28
|
tool: The ADK tool to convert. It should be an instance of a class derived
|
@@ -1,3 +1,17 @@
|
|
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
|
+
|
1
15
|
from contextlib import AsyncExitStack
|
2
16
|
import functools
|
3
17
|
import sys
|
@@ -14,11 +14,7 @@
|
|
14
14
|
|
15
15
|
import keyword
|
16
16
|
import re
|
17
|
-
from typing import Any
|
18
|
-
from typing import Dict
|
19
|
-
from typing import List
|
20
|
-
from typing import Optional
|
21
|
-
from typing import Union
|
17
|
+
from typing import Any, Dict, List, Optional, Union
|
22
18
|
|
23
19
|
from fastapi.openapi.models import Response
|
24
20
|
from fastapi.openapi.models import Schema
|
@@ -100,6 +96,7 @@ class ApiParameter(BaseModel):
|
|
100
96
|
py_name: Optional[str] = ''
|
101
97
|
type_value: type[Any] = Field(default=None, init_var=False)
|
102
98
|
type_hint: str = Field(default=None, init_var=False)
|
99
|
+
required: bool = False
|
103
100
|
|
104
101
|
def model_post_init(self, _: Any):
|
105
102
|
self.py_name = (
|