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.
Files changed (71) hide show
  1. google/adk/agents/active_streaming_tool.py +1 -0
  2. google/adk/agents/base_agent.py +27 -29
  3. google/adk/agents/callback_context.py +4 -4
  4. google/adk/agents/invocation_context.py +1 -0
  5. google/adk/agents/langgraph_agent.py +1 -0
  6. google/adk/agents/live_request_queue.py +1 -0
  7. google/adk/agents/llm_agent.py +54 -14
  8. google/adk/agents/run_config.py +4 -0
  9. google/adk/agents/transcription_entry.py +1 -0
  10. google/adk/artifacts/base_artifact_service.py +5 -10
  11. google/adk/artifacts/gcs_artifact_service.py +8 -8
  12. google/adk/artifacts/in_memory_artifact_service.py +5 -5
  13. google/adk/auth/auth_credential.py +4 -5
  14. google/adk/cli/browser/index.html +1 -1
  15. google/adk/cli/browser/{main-HWIBUY2R.js → main-ULN5R5I5.js} +40 -39
  16. google/adk/cli/cli.py +54 -47
  17. google/adk/cli/cli_eval.py +13 -11
  18. google/adk/cli/cli_tools_click.py +58 -7
  19. google/adk/cli/fast_api.py +11 -11
  20. google/adk/cli/fast_api.py.orig +728 -0
  21. google/adk/evaluation/agent_evaluator.py +3 -3
  22. google/adk/evaluation/evaluation_constants.py +1 -0
  23. google/adk/evaluation/evaluation_generator.py +5 -5
  24. google/adk/evaluation/response_evaluator.py +1 -1
  25. google/adk/events/event.py +1 -0
  26. google/adk/events/event_actions.py +10 -4
  27. google/adk/examples/example.py +1 -0
  28. google/adk/flows/__init__.py +0 -1
  29. google/adk/flows/llm_flows/_code_execution.py +10 -10
  30. google/adk/flows/llm_flows/base_llm_flow.py +40 -15
  31. google/adk/flows/llm_flows/basic.py +3 -0
  32. google/adk/flows/llm_flows/contents.py +9 -5
  33. google/adk/flows/llm_flows/functions.py +38 -16
  34. google/adk/flows/llm_flows/instructions.py +17 -6
  35. google/adk/memory/base_memory_service.py +4 -2
  36. google/adk/memory/in_memory_memory_service.py +2 -2
  37. google/adk/memory/vertex_ai_rag_memory_service.py +2 -2
  38. google/adk/models/anthropic_llm.py +20 -2
  39. google/adk/models/base_llm.py +45 -4
  40. google/adk/models/gemini_llm_connection.py +14 -1
  41. google/adk/models/google_llm.py +0 -42
  42. google/adk/models/lite_llm.py +17 -17
  43. google/adk/models/llm_request.py +1 -1
  44. google/adk/models/llm_response.py +1 -1
  45. google/adk/runners.py +5 -5
  46. google/adk/sessions/_session_util.py +43 -0
  47. google/adk/sessions/base_session_service.py +3 -0
  48. google/adk/sessions/database_session_service.py +63 -46
  49. google/adk/sessions/in_memory_session_service.py +3 -3
  50. google/adk/sessions/session.py +1 -0
  51. google/adk/sessions/vertex_ai_session_service.py +7 -5
  52. google/adk/tools/agent_tool.py +7 -4
  53. google/adk/tools/application_integration_tool/__init__.py +2 -0
  54. google/adk/tools/application_integration_tool/application_integration_toolset.py +48 -26
  55. google/adk/tools/application_integration_tool/clients/connections_client.py +33 -77
  56. google/adk/tools/application_integration_tool/integration_connector_tool.py +159 -0
  57. google/adk/tools/function_tool.py +42 -0
  58. google/adk/tools/load_artifacts_tool.py +4 -4
  59. google/adk/tools/load_memory_tool.py +4 -2
  60. google/adk/tools/mcp_tool/conversion_utils.py +1 -1
  61. google/adk/tools/mcp_tool/mcp_session_manager.py +14 -0
  62. google/adk/tools/openapi_tool/common/common.py +2 -5
  63. google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +13 -3
  64. google/adk/tools/preload_memory_tool.py +1 -1
  65. google/adk/tools/tool_context.py +4 -4
  66. google/adk/version.py +1 -1
  67. {google_adk-0.3.0.dist-info → google_adk-0.5.0.dist-info}/METADATA +3 -7
  68. {google_adk-0.3.0.dist-info → google_adk-0.5.0.dist-info}/RECORD +71 -68
  69. {google_adk-0.3.0.dist-info → google_adk-0.5.0.dist-info}/WHEEL +0 -0
  70. {google_adk-0.3.0.dist-info → google_adk-0.5.0.dist-info}/entry_points.txt +0 -0
  71. {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
- tools = OpenAPIToolset(
222
- spec_dict=spec_dict,
223
- auth_credential=auth_credential,
224
- auth_scheme=auth_scheme,
225
- ).get_tools()
226
- for tool in tools:
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
- f"Returns all entities of type {entity}. Use this tool with"
351
- + f' entity = "{entity}" and'
352
- + ' operation = "LIST_ENTITIES" only. Dont ask these values'
353
- " from"
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"Create {entity}",
449
- "description": (
450
- (
451
- f"Creates a new entity of type {entity}. Use this tool with"
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"Update {entity}",
495
- "description": (
496
- (
497
- f"Updates an entity of type {entity}. Use this tool with"
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
- f"Deletes an entity of type {entity}. Use this tool with"
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(query: str, tool_context: ToolContext) -> 'list[MemoryResult]':
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 Control Plane) system.
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 = (