google-adk 0.4.0__py3-none-any.whl → 1.0.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 (129) hide show
  1. google/adk/agents/active_streaming_tool.py +1 -0
  2. google/adk/agents/base_agent.py +91 -47
  3. google/adk/agents/base_agent.py.orig +330 -0
  4. google/adk/agents/callback_context.py +4 -9
  5. google/adk/agents/invocation_context.py +1 -0
  6. google/adk/agents/langgraph_agent.py +1 -0
  7. google/adk/agents/live_request_queue.py +1 -0
  8. google/adk/agents/llm_agent.py +172 -35
  9. google/adk/agents/loop_agent.py +1 -1
  10. google/adk/agents/parallel_agent.py +7 -0
  11. google/adk/agents/readonly_context.py +7 -1
  12. google/adk/agents/run_config.py +5 -1
  13. google/adk/agents/sequential_agent.py +31 -0
  14. google/adk/agents/transcription_entry.py +5 -2
  15. google/adk/artifacts/base_artifact_service.py +5 -10
  16. google/adk/artifacts/gcs_artifact_service.py +9 -9
  17. google/adk/artifacts/in_memory_artifact_service.py +6 -6
  18. google/adk/auth/auth_credential.py +9 -5
  19. google/adk/auth/auth_preprocessor.py +7 -1
  20. google/adk/auth/auth_tool.py +3 -4
  21. google/adk/cli/agent_graph.py +5 -5
  22. google/adk/cli/browser/index.html +2 -2
  23. google/adk/cli/browser/{main-HWIBUY2R.js → main-QOEMUXM4.js} +58 -58
  24. google/adk/cli/cli.py +7 -7
  25. google/adk/cli/cli_deploy.py +7 -2
  26. google/adk/cli/cli_eval.py +181 -106
  27. google/adk/cli/cli_tools_click.py +147 -62
  28. google/adk/cli/fast_api.py +340 -158
  29. google/adk/cli/fast_api.py.orig +822 -0
  30. google/adk/cli/utils/common.py +23 -0
  31. google/adk/cli/utils/evals.py +83 -1
  32. google/adk/cli/utils/logs.py +13 -5
  33. google/adk/code_executors/__init__.py +3 -1
  34. google/adk/code_executors/built_in_code_executor.py +52 -0
  35. google/adk/evaluation/__init__.py +1 -1
  36. google/adk/evaluation/agent_evaluator.py +168 -128
  37. google/adk/evaluation/eval_case.py +102 -0
  38. google/adk/evaluation/eval_set.py +37 -0
  39. google/adk/evaluation/eval_sets_manager.py +42 -0
  40. google/adk/evaluation/evaluation_constants.py +1 -0
  41. google/adk/evaluation/evaluation_generator.py +89 -114
  42. google/adk/evaluation/evaluator.py +56 -0
  43. google/adk/evaluation/local_eval_sets_manager.py +264 -0
  44. google/adk/evaluation/response_evaluator.py +107 -3
  45. google/adk/evaluation/trajectory_evaluator.py +83 -2
  46. google/adk/events/event.py +7 -1
  47. google/adk/events/event_actions.py +7 -1
  48. google/adk/examples/example.py +1 -0
  49. google/adk/examples/example_util.py +3 -2
  50. google/adk/flows/__init__.py +0 -1
  51. google/adk/flows/llm_flows/_code_execution.py +19 -11
  52. google/adk/flows/llm_flows/audio_transcriber.py +4 -3
  53. google/adk/flows/llm_flows/base_llm_flow.py +86 -22
  54. google/adk/flows/llm_flows/basic.py +3 -0
  55. google/adk/flows/llm_flows/functions.py +10 -9
  56. google/adk/flows/llm_flows/instructions.py +28 -9
  57. google/adk/flows/llm_flows/single_flow.py +1 -1
  58. google/adk/memory/__init__.py +1 -1
  59. google/adk/memory/_utils.py +23 -0
  60. google/adk/memory/base_memory_service.py +25 -21
  61. google/adk/memory/base_memory_service.py.orig +76 -0
  62. google/adk/memory/in_memory_memory_service.py +59 -27
  63. google/adk/memory/memory_entry.py +37 -0
  64. google/adk/memory/vertex_ai_rag_memory_service.py +40 -17
  65. google/adk/models/anthropic_llm.py +36 -11
  66. google/adk/models/base_llm.py +45 -4
  67. google/adk/models/gemini_llm_connection.py +15 -2
  68. google/adk/models/google_llm.py +9 -44
  69. google/adk/models/google_llm.py.orig +305 -0
  70. google/adk/models/lite_llm.py +94 -38
  71. google/adk/models/llm_request.py +1 -1
  72. google/adk/models/llm_response.py +15 -3
  73. google/adk/models/registry.py +1 -1
  74. google/adk/runners.py +68 -44
  75. google/adk/sessions/__init__.py +1 -1
  76. google/adk/sessions/_session_util.py +14 -0
  77. google/adk/sessions/base_session_service.py +8 -32
  78. google/adk/sessions/database_session_service.py +58 -61
  79. google/adk/sessions/in_memory_session_service.py +108 -26
  80. google/adk/sessions/session.py +4 -0
  81. google/adk/sessions/vertex_ai_session_service.py +23 -45
  82. google/adk/telemetry.py +3 -0
  83. google/adk/tools/__init__.py +4 -7
  84. google/adk/tools/{built_in_code_execution_tool.py → _built_in_code_execution_tool.py} +11 -0
  85. google/adk/tools/_memory_entry_utils.py +30 -0
  86. google/adk/tools/agent_tool.py +16 -13
  87. google/adk/tools/apihub_tool/apihub_toolset.py +55 -74
  88. google/adk/tools/application_integration_tool/application_integration_toolset.py +107 -85
  89. google/adk/tools/application_integration_tool/clients/connections_client.py +29 -25
  90. google/adk/tools/application_integration_tool/clients/integration_client.py +6 -6
  91. google/adk/tools/application_integration_tool/integration_connector_tool.py +69 -26
  92. google/adk/tools/base_toolset.py +58 -0
  93. google/adk/tools/enterprise_search_tool.py +65 -0
  94. google/adk/tools/function_parameter_parse_util.py +2 -2
  95. google/adk/tools/google_api_tool/__init__.py +18 -70
  96. google/adk/tools/google_api_tool/google_api_tool.py +11 -5
  97. google/adk/tools/google_api_tool/google_api_toolset.py +126 -0
  98. google/adk/tools/google_api_tool/google_api_toolsets.py +102 -0
  99. google/adk/tools/google_api_tool/googleapi_to_openapi_converter.py +40 -42
  100. google/adk/tools/langchain_tool.py +96 -49
  101. google/adk/tools/load_artifacts_tool.py +4 -4
  102. google/adk/tools/load_memory_tool.py +16 -5
  103. google/adk/tools/mcp_tool/__init__.py +3 -2
  104. google/adk/tools/mcp_tool/conversion_utils.py +1 -1
  105. google/adk/tools/mcp_tool/mcp_session_manager.py +167 -16
  106. google/adk/tools/mcp_tool/mcp_session_manager.py.orig +322 -0
  107. google/adk/tools/mcp_tool/mcp_tool.py +12 -12
  108. google/adk/tools/mcp_tool/mcp_toolset.py +155 -195
  109. google/adk/tools/openapi_tool/common/common.py +2 -5
  110. google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +32 -7
  111. google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +43 -33
  112. google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py +1 -1
  113. google/adk/tools/preload_memory_tool.py +27 -18
  114. google/adk/tools/retrieval/__init__.py +1 -1
  115. google/adk/tools/retrieval/vertex_ai_rag_retrieval.py +1 -1
  116. google/adk/tools/tool_context.py +4 -4
  117. google/adk/tools/toolbox_toolset.py +79 -0
  118. google/adk/tools/transfer_to_agent_tool.py +0 -1
  119. google/adk/version.py +1 -1
  120. {google_adk-0.4.0.dist-info → google_adk-1.0.0.dist-info}/METADATA +7 -5
  121. google_adk-1.0.0.dist-info/RECORD +195 -0
  122. google/adk/agents/remote_agent.py +0 -50
  123. google/adk/tools/google_api_tool/google_api_tool_set.py +0 -110
  124. google/adk/tools/google_api_tool/google_api_tool_sets.py +0 -112
  125. google/adk/tools/toolbox_tool.py +0 -46
  126. google_adk-0.4.0.dist-info/RECORD +0 -179
  127. {google_adk-0.4.0.dist-info → google_adk-1.0.0.dist-info}/WHEEL +0 -0
  128. {google_adk-0.4.0.dist-info → google_adk-1.0.0.dist-info}/entry_points.txt +0 -0
  129. {google_adk-0.4.0.dist-info → google_adk-1.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,126 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ import inspect
18
+ import os
19
+ from typing import Any
20
+ from typing import List
21
+ from typing import Optional
22
+ from typing import Type
23
+ from typing import Union
24
+
25
+ from typing_extensions import override
26
+
27
+ from ...agents.readonly_context import ReadonlyContext
28
+ from ...auth import OpenIdConnectWithConfig
29
+ from ...tools.base_toolset import BaseToolset
30
+ from ...tools.base_toolset import ToolPredicate
31
+ from ..openapi_tool import OpenAPIToolset
32
+ from .google_api_tool import GoogleApiTool
33
+ from .googleapi_to_openapi_converter import GoogleApiToOpenApiConverter
34
+
35
+
36
+ class GoogleApiToolset(BaseToolset):
37
+ """Google API Toolset contains tools for interacting with Google APIs.
38
+
39
+ Usually one toolsets will contains tools only related to one Google API, e.g.
40
+ Google Bigquery API toolset will contains tools only related to Google
41
+ Bigquery API, like list dataset tool, list table tool etc.
42
+ """
43
+
44
+ def __init__(
45
+ self,
46
+ api_name: str,
47
+ api_version: str,
48
+ client_id: Optional[str] = None,
49
+ client_secret: Optional[str] = None,
50
+ tool_filter: Optional[Union[ToolPredicate, List[str]]] = None,
51
+ ):
52
+ self.api_name = api_name
53
+ self.api_version = api_version
54
+ self._client_id = client_id
55
+ self._client_secret = client_secret
56
+ self._openapi_toolset = self._load_toolset_with_oidc_auth()
57
+ self.tool_filter = tool_filter
58
+
59
+ def _is_tool_selected(
60
+ self, tool: GoogleApiTool, readonly_context: ReadonlyContext
61
+ ) -> bool:
62
+ if not self.tool_filter:
63
+ return True
64
+
65
+ if isinstance(self.tool_filter, ToolPredicate):
66
+ return self.tool_filter(tool, readonly_context)
67
+
68
+ if isinstance(self.tool_filter, list):
69
+ return tool.name in self.tool_filter
70
+
71
+ return False
72
+
73
+ @override
74
+ async def get_tools(
75
+ self, readonly_context: Optional[ReadonlyContext] = None
76
+ ) -> List[GoogleApiTool]:
77
+ """Get all tools in the toolset."""
78
+ tools = []
79
+
80
+ return [
81
+ GoogleApiTool(tool, self._client_id, self._client_secret)
82
+ for tool in await self._openapi_toolset.get_tools(readonly_context)
83
+ if self._is_tool_selected(tool, readonly_context)
84
+ ]
85
+
86
+ def set_tool_filter(self, tool_filter: Union[ToolPredicate, List[str]]):
87
+ self.tool_filter = tool_filter
88
+
89
+ def _load_toolset_with_oidc_auth(self) -> OpenAPIToolset:
90
+ spec_dict = GoogleApiToOpenApiConverter(
91
+ self.api_name, self.api_version
92
+ ).convert()
93
+ scope = list(
94
+ spec_dict['components']['securitySchemes']['oauth2']['flows'][
95
+ 'authorizationCode'
96
+ ]['scopes'].keys()
97
+ )[0]
98
+ return OpenAPIToolset(
99
+ spec_dict=spec_dict,
100
+ spec_str_type='yaml',
101
+ auth_scheme=OpenIdConnectWithConfig(
102
+ authorization_endpoint=(
103
+ 'https://accounts.google.com/o/oauth2/v2/auth'
104
+ ),
105
+ token_endpoint='https://oauth2.googleapis.com/token',
106
+ userinfo_endpoint=(
107
+ 'https://openidconnect.googleapis.com/v1/userinfo'
108
+ ),
109
+ revocation_endpoint='https://oauth2.googleapis.com/revoke',
110
+ token_endpoint_auth_methods_supported=[
111
+ 'client_secret_post',
112
+ 'client_secret_basic',
113
+ ],
114
+ grant_types_supported=['authorization_code'],
115
+ scopes=[scope],
116
+ ),
117
+ )
118
+
119
+ def configure_auth(self, client_id: str, client_secret: str):
120
+ self._client_id = client_id
121
+ self._client_secret = client_secret
122
+
123
+ @override
124
+ async def close(self):
125
+ if self._openapi_toolset:
126
+ await self._openapi_toolset.close()
@@ -0,0 +1,102 @@
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 List
18
+ from typing import Optional
19
+ from typing import Union
20
+
21
+ from google.adk.tools.base_toolset import ToolPredicate
22
+
23
+ from .google_api_toolset import GoogleApiToolset
24
+
25
+ logger = logging.getLogger("google_adk." + __name__)
26
+
27
+
28
+ class BigQueryToolset(GoogleApiToolset):
29
+
30
+ def __init__(
31
+ self,
32
+ client_id: str = None,
33
+ client_secret: str = None,
34
+ tool_filter: Optional[Union[ToolPredicate, List[str]]] = None,
35
+ ):
36
+ super().__init__("bigquery", "v2", client_id, client_secret, tool_filter)
37
+
38
+
39
+ class CalendarToolset(GoogleApiToolset):
40
+
41
+ def __init__(
42
+ self,
43
+ client_id: str = None,
44
+ client_secret: str = None,
45
+ tool_filter: Optional[Union[ToolPredicate, List[str]]] = None,
46
+ ):
47
+ super().__init__("calendar", "v3", client_id, client_secret, tool_filter)
48
+
49
+
50
+ class GmailToolset(GoogleApiToolset):
51
+
52
+ def __init__(
53
+ self,
54
+ client_id: str = None,
55
+ client_secret: str = None,
56
+ tool_filter: Optional[Union[ToolPredicate, List[str]]] = None,
57
+ ):
58
+ super().__init__("gmail", "v1", client_id, client_secret, tool_filter)
59
+
60
+
61
+ class YoutubeToolset(GoogleApiToolset):
62
+
63
+ def __init__(
64
+ self,
65
+ client_id: str = None,
66
+ client_secret: str = None,
67
+ tool_filter: Optional[Union[ToolPredicate, List[str]]] = None,
68
+ ):
69
+ super().__init__("youtube", "v3", client_id, client_secret, tool_filter)
70
+
71
+
72
+ class SlidesToolset(GoogleApiToolset):
73
+
74
+ def __init__(
75
+ self,
76
+ client_id: str = None,
77
+ client_secret: str = None,
78
+ tool_filter: Optional[Union[ToolPredicate, List[str]]] = None,
79
+ ):
80
+ super().__init__("slides", "v1", client_id, client_secret, tool_filter)
81
+
82
+
83
+ class SheetsToolset(GoogleApiToolset):
84
+
85
+ def __init__(
86
+ self,
87
+ client_id: str = None,
88
+ client_secret: str = None,
89
+ tool_filter: Optional[Union[ToolPredicate, List[str]]] = None,
90
+ ):
91
+ super().__init__("sheets", "v4", client_id, client_secret, tool_filter)
92
+
93
+
94
+ class DocsToolset(GoogleApiToolset):
95
+
96
+ def __init__(
97
+ self,
98
+ client_id: str = None,
99
+ client_secret: str = None,
100
+ tool_filter: Optional[Union[ToolPredicate, List[str]]] = None,
101
+ ):
102
+ super().__init__("docs", "v1", client_id, client_secret, tool_filter)
@@ -18,19 +18,13 @@ import logging
18
18
  from typing import Any
19
19
  from typing import Dict
20
20
  from typing import List
21
- from typing import Optional
22
- from typing import Union
23
21
 
24
22
  # Google API client
25
23
  from googleapiclient.discovery import build
26
- from googleapiclient.discovery import Resource
27
24
  from googleapiclient.errors import HttpError
28
25
 
29
26
  # Configure logging
30
- logging.basicConfig(
31
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
32
- )
33
- logger = logging.getLogger(__name__)
27
+ logger = logging.getLogger("google_adk." + __name__)
34
28
 
35
29
 
36
30
  class GoogleApiToOpenApiConverter:
@@ -43,11 +37,11 @@ class GoogleApiToOpenApiConverter:
43
37
  api_name: The name of the Google API (e.g., "calendar")
44
38
  api_version: The version of the API (e.g., "v3")
45
39
  """
46
- self.api_name = api_name
47
- self.api_version = api_version
48
- self.google_api_resource = None
49
- self.google_api_spec = None
50
- self.openapi_spec = {
40
+ self._api_name = api_name
41
+ self._api_version = api_version
42
+ self._google_api_resource = None
43
+ self._google_api_spec = None
44
+ self._openapi_spec = {
51
45
  "openapi": "3.0.0",
52
46
  "info": {},
53
47
  "servers": [],
@@ -59,18 +53,20 @@ class GoogleApiToOpenApiConverter:
59
53
  """Fetches the Google API specification using discovery service."""
60
54
  try:
61
55
  logger.info(
62
- "Fetching Google API spec for %s %s", self.api_name, self.api_version
56
+ "Fetching Google API spec for %s %s",
57
+ self._api_name,
58
+ self._api_version,
63
59
  )
64
60
  # Build a resource object for the specified API
65
- self.google_api_resource = build(self.api_name, self.api_version)
61
+ self._google_api_resource = build(self._api_name, self._api_version)
66
62
 
67
63
  # Access the underlying API discovery document
68
- self.google_api_spec = self.google_api_resource._rootDesc
64
+ self._google_api_spec = self._google_api_resource._rootDesc
69
65
 
70
- if not self.google_api_spec:
66
+ if not self._google_api_spec:
71
67
  raise ValueError("Failed to retrieve API specification")
72
68
 
73
- logger.info("Successfully fetched %s API specification", self.api_name)
69
+ logger.info("Successfully fetched %s API specification", self._api_name)
74
70
  except HttpError as e:
75
71
  logger.error("HTTP Error: %s", e)
76
72
  raise
@@ -84,7 +80,7 @@ class GoogleApiToOpenApiConverter:
84
80
  Returns:
85
81
  Dict containing the converted OpenAPI v3 specification
86
82
  """
87
- if not self.google_api_spec:
83
+ if not self._google_api_spec:
88
84
  self.fetch_google_api_spec()
89
85
 
90
86
  # Convert basic API information
@@ -100,49 +96,49 @@ class GoogleApiToOpenApiConverter:
100
96
  self._convert_schemas()
101
97
 
102
98
  # Convert endpoints/paths
103
- self._convert_resources(self.google_api_spec.get("resources", {}))
99
+ self._convert_resources(self._google_api_spec.get("resources", {}))
104
100
 
105
101
  # Convert top-level methods, if any
106
- self._convert_methods(self.google_api_spec.get("methods", {}), "/")
102
+ self._convert_methods(self._google_api_spec.get("methods", {}), "/")
107
103
 
108
- return self.openapi_spec
104
+ return self._openapi_spec
109
105
 
110
106
  def _convert_info(self) -> None:
111
107
  """Convert basic API information."""
112
- self.openapi_spec["info"] = {
113
- "title": self.google_api_spec.get("title", f"{self.api_name} API"),
114
- "description": self.google_api_spec.get("description", ""),
115
- "version": self.google_api_spec.get("version", self.api_version),
108
+ self._openapi_spec["info"] = {
109
+ "title": self._google_api_spec.get("title", f"{self._api_name} API"),
110
+ "description": self._google_api_spec.get("description", ""),
111
+ "version": self._google_api_spec.get("version", self._api_version),
116
112
  "contact": {},
117
- "termsOfService": self.google_api_spec.get("documentationLink", ""),
113
+ "termsOfService": self._google_api_spec.get("documentationLink", ""),
118
114
  }
119
115
 
120
116
  # Add documentation links if available
121
- docs_link = self.google_api_spec.get("documentationLink")
117
+ docs_link = self._google_api_spec.get("documentationLink")
122
118
  if docs_link:
123
- self.openapi_spec["externalDocs"] = {
119
+ self._openapi_spec["externalDocs"] = {
124
120
  "description": "API Documentation",
125
121
  "url": docs_link,
126
122
  }
127
123
 
128
124
  def _convert_servers(self) -> None:
129
125
  """Convert server information."""
130
- base_url = self.google_api_spec.get(
126
+ base_url = self._google_api_spec.get(
131
127
  "rootUrl", ""
132
- ) + self.google_api_spec.get("servicePath", "")
128
+ ) + self._google_api_spec.get("servicePath", "")
133
129
 
134
130
  # Remove trailing slash if present
135
131
  if base_url.endswith("/"):
136
132
  base_url = base_url[:-1]
137
133
 
138
- self.openapi_spec["servers"] = [{
134
+ self._openapi_spec["servers"] = [{
139
135
  "url": base_url,
140
- "description": f"{self.api_name} {self.api_version} API",
136
+ "description": f"{self._api_name} {self._api_version} API",
141
137
  }]
142
138
 
143
139
  def _convert_security_schemes(self) -> None:
144
140
  """Convert authentication and authorization schemes."""
145
- auth = self.google_api_spec.get("auth", {})
141
+ auth = self._google_api_spec.get("auth", {})
146
142
  oauth2 = auth.get("oauth2", {})
147
143
 
148
144
  if oauth2:
@@ -153,7 +149,7 @@ class GoogleApiToOpenApiConverter:
153
149
  for scope, scope_info in scopes.items():
154
150
  formatted_scopes[scope] = scope_info.get("description", "")
155
151
 
156
- self.openapi_spec["components"]["securitySchemes"]["oauth2"] = {
152
+ self._openapi_spec["components"]["securitySchemes"]["oauth2"] = {
157
153
  "type": "oauth2",
158
154
  "description": "OAuth 2.0 authentication",
159
155
  "flows": {
@@ -168,7 +164,7 @@ class GoogleApiToOpenApiConverter:
168
164
  }
169
165
 
170
166
  # Add API key authentication (most Google APIs support this)
171
- self.openapi_spec["components"]["securitySchemes"]["apiKey"] = {
167
+ self._openapi_spec["components"]["securitySchemes"]["apiKey"] = {
172
168
  "type": "apiKey",
173
169
  "in": "query",
174
170
  "name": "key",
@@ -176,18 +172,20 @@ class GoogleApiToOpenApiConverter:
176
172
  }
177
173
 
178
174
  # Create global security requirement
179
- self.openapi_spec["security"] = [
175
+ self._openapi_spec["security"] = [
180
176
  {"oauth2": list(formatted_scopes.keys())} if oauth2 else {},
181
177
  {"apiKey": []},
182
178
  ]
183
179
 
184
180
  def _convert_schemas(self) -> None:
185
181
  """Convert schema definitions (models)."""
186
- schemas = self.google_api_spec.get("schemas", {})
182
+ schemas = self._google_api_spec.get("schemas", {})
187
183
 
188
184
  for schema_name, schema_def in schemas.items():
189
185
  converted_schema = self._convert_schema_object(schema_def)
190
- self.openapi_spec["components"]["schemas"][schema_name] = converted_schema
186
+ self._openapi_spec["components"]["schemas"][
187
+ schema_name
188
+ ] = converted_schema
191
189
 
192
190
  def _convert_schema_object(
193
191
  self, schema_def: Dict[str, Any]
@@ -320,11 +318,11 @@ class GoogleApiToOpenApiConverter:
320
318
  path_params = self._extract_path_parameters(rest_path)
321
319
 
322
320
  # Create path entry if it doesn't exist
323
- if rest_path not in self.openapi_spec["paths"]:
324
- self.openapi_spec["paths"][rest_path] = {}
321
+ if rest_path not in self._openapi_spec["paths"]:
322
+ self._openapi_spec["paths"][rest_path] = {}
325
323
 
326
324
  # Add the operation for this method
327
- self.openapi_spec["paths"][rest_path][http_method] = (
325
+ self._openapi_spec["paths"][rest_path][http_method] = (
328
326
  self._convert_operation(method_data, path_params)
329
327
  )
330
328
 
@@ -478,7 +476,7 @@ class GoogleApiToOpenApiConverter:
478
476
  output_path: Path where the OpenAPI spec should be saved
479
477
  """
480
478
  with open(output_path, "w", encoding="utf-8") as f:
481
- json.dump(self.openapi_spec, f, indent=2)
479
+ json.dump(self._openapi_spec, f, indent=2)
482
480
  logger.info("OpenAPI specification saved to %s", output_path)
483
481
 
484
482
 
@@ -13,10 +13,12 @@
13
13
  # limitations under the License.
14
14
 
15
15
  from typing import Any
16
- from typing import Callable
16
+ from typing import Optional
17
+ from typing import Union
17
18
 
18
19
  from google.genai import types
19
- from pydantic import model_validator
20
+ from langchain.agents import Tool
21
+ from langchain_core.tools import BaseTool
20
22
  from typing_extensions import override
21
23
 
22
24
  from . import _automatic_function_calling_util
@@ -24,63 +26,108 @@ from .function_tool import FunctionTool
24
26
 
25
27
 
26
28
  class LangchainTool(FunctionTool):
27
- """Use this class to wrap a langchain tool.
29
+ """Adapter class that wraps a Langchain tool for use with ADK.
28
30
 
29
- If the original tool name and description are not suitable, you can override
30
- them in the constructor.
31
+ This adapter converts Langchain tools into a format compatible with Google's
32
+ generative AI function calling interface. It preserves the tool's name,
33
+ description, and functionality while adapting its schema.
34
+
35
+ The original tool's name and description can be overridden if needed.
36
+
37
+ Args:
38
+ tool: A Langchain tool to wrap (BaseTool or a tool with a .run method)
39
+ name: Optional override for the tool's name
40
+ description: Optional override for the tool's description
41
+
42
+ Examples:
43
+ ```python
44
+ from langchain.tools import DuckDuckGoSearchTool
45
+ from google.genai.tools import LangchainTool
46
+
47
+ search_tool = DuckDuckGoSearchTool()
48
+ wrapped_tool = LangchainTool(search_tool)
49
+ ```
31
50
  """
32
51
 
33
- tool: Any
52
+ _langchain_tool: Union[BaseTool, object]
34
53
  """The wrapped langchain tool."""
35
54
 
36
- def __init__(self, tool: Any):
37
- super().__init__(tool._run)
38
- self.tool = tool
39
- if tool.name:
55
+ def __init__(
56
+ self,
57
+ tool: Union[BaseTool, object],
58
+ name: Optional[str] = None,
59
+ description: Optional[str] = None,
60
+ ):
61
+ # Check if the tool has a 'run' method
62
+ if not hasattr(tool, 'run') and not hasattr(tool, '_run'):
63
+ raise ValueError("Langchain tool must have a 'run' or '_run' method")
64
+
65
+ # Determine which function to use
66
+ func = tool._run if hasattr(tool, '_run') else tool.run
67
+ super().__init__(func)
68
+
69
+ self._langchain_tool = tool
70
+
71
+ # Set name: priority is 1) explicitly provided name, 2) tool's name, 3) default
72
+ if name is not None:
73
+ self.name = name
74
+ elif hasattr(tool, 'name') and tool.name:
40
75
  self.name = tool.name
41
- if tool.description:
42
- self.description = tool.description
76
+ # else: keep default from FunctionTool
43
77
 
44
- @model_validator(mode='before')
45
- @classmethod
46
- def populate_name(cls, data: Any) -> Any:
47
- # Override this to not use function's signature name as it's
48
- # mostly "run" or "invoke" for thir-party tools.
49
- return data
78
+ # Set description: similar priority
79
+ if description is not None:
80
+ self.description = description
81
+ elif hasattr(tool, 'description') and tool.description:
82
+ self.description = tool.description
83
+ # else: keep default from FunctionTool
50
84
 
51
85
  @override
52
86
  def _get_declaration(self) -> types.FunctionDeclaration:
53
- """Build the function declaration for the tool."""
54
- from langchain.agents import Tool
55
- from langchain_core.tools import BaseTool
56
-
57
- # There are two types of tools:
58
- # 1. BaseTool: the tool is defined in langchain.tools.
59
- # 2. Other tools: the tool doesn't inherit any class but follow some
60
- # conventions, like having a "run" method.
61
- if isinstance(self.tool, BaseTool):
62
- tool_wrapper = Tool(
63
- name=self.name,
64
- func=self.func,
65
- description=self.description,
66
- )
67
- if self.tool.args_schema:
68
- tool_wrapper.args_schema = self.tool.args_schema
69
- function_declaration = _automatic_function_calling_util.build_function_declaration_for_langchain(
70
- False,
71
- self.name,
72
- self.description,
73
- tool_wrapper.func,
74
- tool_wrapper.args,
75
- )
76
- return function_declaration
77
- else:
87
+ """Build the function declaration for the tool.
88
+
89
+ Returns:
90
+ A FunctionDeclaration object that describes the tool's interface.
91
+
92
+ Raises:
93
+ ValueError: If the tool schema cannot be correctly parsed.
94
+ """
95
+ try:
96
+ # There are two types of tools:
97
+ # 1. BaseTool: the tool is defined in langchain_core.tools.
98
+ # 2. Other tools: the tool doesn't inherit any class but follow some
99
+ # conventions, like having a "run" method.
100
+ # Handle BaseTool type (preferred Langchain approach)
101
+ if isinstance(self._langchain_tool, BaseTool):
102
+ tool_wrapper = Tool(
103
+ name=self.name,
104
+ func=self.func,
105
+ description=self.description,
106
+ )
107
+
108
+ # Add schema if available
109
+ if (
110
+ hasattr(self._langchain_tool, 'args_schema')
111
+ and self._langchain_tool.args_schema
112
+ ):
113
+ tool_wrapper.args_schema = self._langchain_tool.args_schema
114
+
115
+ return _automatic_function_calling_util.build_function_declaration_for_langchain(
116
+ False,
117
+ self.name,
118
+ self.description,
119
+ tool_wrapper.func,
120
+ getattr(tool_wrapper, 'args', None),
121
+ )
122
+
78
123
  # Need to provide a way to override the function names and descriptions
79
124
  # as the original function names are mostly ".run" and the descriptions
80
- # may not meet users' needs.
81
- function_declaration = (
82
- _automatic_function_calling_util.build_function_declaration(
83
- func=self.tool.run,
84
- )
125
+ # may not meet users' needs
126
+ return _automatic_function_calling_util.build_function_declaration(
127
+ func=self._langchain_tool.run,
85
128
  )
86
- return function_declaration
129
+
130
+ except Exception as e:
131
+ raise ValueError(
132
+ f'Failed to build function declaration for Langchain tool: {e}'
133
+ ) from e
@@ -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',
@@ -17,17 +17,25 @@ from __future__ import annotations
17
17
  from typing import TYPE_CHECKING
18
18
 
19
19
  from google.genai import types
20
+ from pydantic import BaseModel
21
+ from pydantic import Field
20
22
  from typing_extensions import override
21
23
 
24
+ from ..memory.memory_entry import MemoryEntry
22
25
  from .function_tool import FunctionTool
23
26
  from .tool_context import ToolContext
24
27
 
25
28
  if TYPE_CHECKING:
26
- from ..memory.base_memory_service import MemoryResult
27
29
  from ..models import LlmRequest
28
30
 
29
31
 
30
- def load_memory(query: str, tool_context: ToolContext) -> 'list[MemoryResult]':
32
+ class LoadMemoryResponse(BaseModel):
33
+ memories: list[MemoryEntry] = Field(default_factory=list)
34
+
35
+
36
+ async def load_memory(
37
+ query: str, tool_context: ToolContext
38
+ ) -> LoadMemoryResponse:
31
39
  """Loads the memory for the current user.
32
40
 
33
41
  Args:
@@ -36,12 +44,15 @@ def load_memory(query: str, tool_context: ToolContext) -> 'list[MemoryResult]':
36
44
  Returns:
37
45
  A list of memory results.
38
46
  """
39
- response = tool_context.search_memory(query)
40
- return response.memories
47
+ search_memory_response = await tool_context.search_memory(query)
48
+ return LoadMemoryResponse(memories=search_memory_response.memories)
41
49
 
42
50
 
43
51
  class LoadMemoryTool(FunctionTool):
44
- """A tool that loads the memory for the current user."""
52
+ """A tool that loads the memory for the current user.
53
+
54
+ NOTE: Currently this tool only uses text part from the memory.
55
+ """
45
56
 
46
57
  def __init__(self):
47
58
  super().__init__(load_memory)