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.
- google/adk/agents/active_streaming_tool.py +1 -0
- google/adk/agents/base_agent.py +91 -47
- google/adk/agents/base_agent.py.orig +330 -0
- google/adk/agents/callback_context.py +4 -9
- 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 +172 -35
- google/adk/agents/loop_agent.py +1 -1
- google/adk/agents/parallel_agent.py +7 -0
- google/adk/agents/readonly_context.py +7 -1
- google/adk/agents/run_config.py +5 -1
- google/adk/agents/sequential_agent.py +31 -0
- google/adk/agents/transcription_entry.py +5 -2
- google/adk/artifacts/base_artifact_service.py +5 -10
- google/adk/artifacts/gcs_artifact_service.py +9 -9
- google/adk/artifacts/in_memory_artifact_service.py +6 -6
- google/adk/auth/auth_credential.py +9 -5
- google/adk/auth/auth_preprocessor.py +7 -1
- google/adk/auth/auth_tool.py +3 -4
- google/adk/cli/agent_graph.py +5 -5
- google/adk/cli/browser/index.html +2 -2
- google/adk/cli/browser/{main-HWIBUY2R.js → main-QOEMUXM4.js} +58 -58
- google/adk/cli/cli.py +7 -7
- google/adk/cli/cli_deploy.py +7 -2
- google/adk/cli/cli_eval.py +181 -106
- google/adk/cli/cli_tools_click.py +147 -62
- google/adk/cli/fast_api.py +340 -158
- google/adk/cli/fast_api.py.orig +822 -0
- google/adk/cli/utils/common.py +23 -0
- google/adk/cli/utils/evals.py +83 -1
- google/adk/cli/utils/logs.py +13 -5
- google/adk/code_executors/__init__.py +3 -1
- google/adk/code_executors/built_in_code_executor.py +52 -0
- google/adk/evaluation/__init__.py +1 -1
- google/adk/evaluation/agent_evaluator.py +168 -128
- google/adk/evaluation/eval_case.py +102 -0
- google/adk/evaluation/eval_set.py +37 -0
- google/adk/evaluation/eval_sets_manager.py +42 -0
- google/adk/evaluation/evaluation_constants.py +1 -0
- google/adk/evaluation/evaluation_generator.py +89 -114
- google/adk/evaluation/evaluator.py +56 -0
- google/adk/evaluation/local_eval_sets_manager.py +264 -0
- google/adk/evaluation/response_evaluator.py +107 -3
- google/adk/evaluation/trajectory_evaluator.py +83 -2
- google/adk/events/event.py +7 -1
- google/adk/events/event_actions.py +7 -1
- google/adk/examples/example.py +1 -0
- google/adk/examples/example_util.py +3 -2
- google/adk/flows/__init__.py +0 -1
- google/adk/flows/llm_flows/_code_execution.py +19 -11
- google/adk/flows/llm_flows/audio_transcriber.py +4 -3
- google/adk/flows/llm_flows/base_llm_flow.py +86 -22
- google/adk/flows/llm_flows/basic.py +3 -0
- google/adk/flows/llm_flows/functions.py +10 -9
- google/adk/flows/llm_flows/instructions.py +28 -9
- google/adk/flows/llm_flows/single_flow.py +1 -1
- google/adk/memory/__init__.py +1 -1
- google/adk/memory/_utils.py +23 -0
- google/adk/memory/base_memory_service.py +25 -21
- google/adk/memory/base_memory_service.py.orig +76 -0
- google/adk/memory/in_memory_memory_service.py +59 -27
- google/adk/memory/memory_entry.py +37 -0
- google/adk/memory/vertex_ai_rag_memory_service.py +40 -17
- google/adk/models/anthropic_llm.py +36 -11
- google/adk/models/base_llm.py +45 -4
- google/adk/models/gemini_llm_connection.py +15 -2
- google/adk/models/google_llm.py +9 -44
- google/adk/models/google_llm.py.orig +305 -0
- google/adk/models/lite_llm.py +94 -38
- google/adk/models/llm_request.py +1 -1
- google/adk/models/llm_response.py +15 -3
- google/adk/models/registry.py +1 -1
- google/adk/runners.py +68 -44
- google/adk/sessions/__init__.py +1 -1
- google/adk/sessions/_session_util.py +14 -0
- google/adk/sessions/base_session_service.py +8 -32
- google/adk/sessions/database_session_service.py +58 -61
- google/adk/sessions/in_memory_session_service.py +108 -26
- google/adk/sessions/session.py +4 -0
- google/adk/sessions/vertex_ai_session_service.py +23 -45
- google/adk/telemetry.py +3 -0
- google/adk/tools/__init__.py +4 -7
- google/adk/tools/{built_in_code_execution_tool.py → _built_in_code_execution_tool.py} +11 -0
- google/adk/tools/_memory_entry_utils.py +30 -0
- google/adk/tools/agent_tool.py +16 -13
- google/adk/tools/apihub_tool/apihub_toolset.py +55 -74
- google/adk/tools/application_integration_tool/application_integration_toolset.py +107 -85
- google/adk/tools/application_integration_tool/clients/connections_client.py +29 -25
- google/adk/tools/application_integration_tool/clients/integration_client.py +6 -6
- google/adk/tools/application_integration_tool/integration_connector_tool.py +69 -26
- google/adk/tools/base_toolset.py +58 -0
- google/adk/tools/enterprise_search_tool.py +65 -0
- google/adk/tools/function_parameter_parse_util.py +2 -2
- google/adk/tools/google_api_tool/__init__.py +18 -70
- google/adk/tools/google_api_tool/google_api_tool.py +11 -5
- google/adk/tools/google_api_tool/google_api_toolset.py +126 -0
- google/adk/tools/google_api_tool/google_api_toolsets.py +102 -0
- google/adk/tools/google_api_tool/googleapi_to_openapi_converter.py +40 -42
- google/adk/tools/langchain_tool.py +96 -49
- google/adk/tools/load_artifacts_tool.py +4 -4
- google/adk/tools/load_memory_tool.py +16 -5
- google/adk/tools/mcp_tool/__init__.py +3 -2
- google/adk/tools/mcp_tool/conversion_utils.py +1 -1
- google/adk/tools/mcp_tool/mcp_session_manager.py +167 -16
- google/adk/tools/mcp_tool/mcp_session_manager.py.orig +322 -0
- google/adk/tools/mcp_tool/mcp_tool.py +12 -12
- google/adk/tools/mcp_tool/mcp_toolset.py +155 -195
- google/adk/tools/openapi_tool/common/common.py +2 -5
- google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +32 -7
- google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +43 -33
- google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py +1 -1
- google/adk/tools/preload_memory_tool.py +27 -18
- google/adk/tools/retrieval/__init__.py +1 -1
- google/adk/tools/retrieval/vertex_ai_rag_retrieval.py +1 -1
- google/adk/tools/tool_context.py +4 -4
- google/adk/tools/toolbox_toolset.py +79 -0
- google/adk/tools/transfer_to_agent_tool.py +0 -1
- google/adk/version.py +1 -1
- {google_adk-0.4.0.dist-info → google_adk-1.0.0.dist-info}/METADATA +7 -5
- google_adk-1.0.0.dist-info/RECORD +195 -0
- google/adk/agents/remote_agent.py +0 -50
- google/adk/tools/google_api_tool/google_api_tool_set.py +0 -110
- google/adk/tools/google_api_tool/google_api_tool_sets.py +0 -112
- google/adk/tools/toolbox_tool.py +0 -46
- google_adk-0.4.0.dist-info/RECORD +0 -179
- {google_adk-0.4.0.dist-info → google_adk-1.0.0.dist-info}/WHEEL +0 -0
- {google_adk-0.4.0.dist-info → google_adk-1.0.0.dist-info}/entry_points.txt +0 -0
- {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.
|
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.
|
47
|
-
self.
|
48
|
-
self.
|
49
|
-
self.
|
50
|
-
self.
|
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",
|
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.
|
61
|
+
self._google_api_resource = build(self._api_name, self._api_version)
|
66
62
|
|
67
63
|
# Access the underlying API discovery document
|
68
|
-
self.
|
64
|
+
self._google_api_spec = self._google_api_resource._rootDesc
|
69
65
|
|
70
|
-
if not self.
|
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.
|
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.
|
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.
|
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.
|
102
|
+
self._convert_methods(self._google_api_spec.get("methods", {}), "/")
|
107
103
|
|
108
|
-
return self.
|
104
|
+
return self._openapi_spec
|
109
105
|
|
110
106
|
def _convert_info(self) -> None:
|
111
107
|
"""Convert basic API information."""
|
112
|
-
self.
|
113
|
-
"title": self.
|
114
|
-
"description": self.
|
115
|
-
"version": self.
|
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.
|
113
|
+
"termsOfService": self._google_api_spec.get("documentationLink", ""),
|
118
114
|
}
|
119
115
|
|
120
116
|
# Add documentation links if available
|
121
|
-
docs_link = self.
|
117
|
+
docs_link = self._google_api_spec.get("documentationLink")
|
122
118
|
if docs_link:
|
123
|
-
self.
|
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.
|
126
|
+
base_url = self._google_api_spec.get(
|
131
127
|
"rootUrl", ""
|
132
|
-
) + self.
|
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.
|
134
|
+
self._openapi_spec["servers"] = [{
|
139
135
|
"url": base_url,
|
140
|
-
"description": f"{self.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
324
|
-
self.
|
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.
|
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.
|
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
|
16
|
+
from typing import Optional
|
17
|
+
from typing import Union
|
17
18
|
|
18
19
|
from google.genai import types
|
19
|
-
from
|
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
|
-
"""
|
29
|
+
"""Adapter class that wraps a Langchain tool for use with ADK.
|
28
30
|
|
29
|
-
|
30
|
-
|
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
|
-
|
52
|
+
_langchain_tool: Union[BaseTool, object]
|
34
53
|
"""The wrapped langchain tool."""
|
35
54
|
|
36
|
-
def __init__(
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
42
|
-
self.description = tool.description
|
76
|
+
# else: keep default from FunctionTool
|
43
77
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
)
|
67
|
-
if self.
|
68
|
-
tool_wrapper
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
82
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
40
|
-
return
|
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)
|