google-adk 1.1.0__py3-none-any.whl → 1.2.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/base_agent.py +0 -2
- google/adk/agents/invocation_context.py +3 -3
- google/adk/agents/parallel_agent.py +17 -7
- google/adk/agents/sequential_agent.py +8 -8
- google/adk/auth/auth_preprocessor.py +18 -17
- google/adk/cli/agent_graph.py +165 -23
- google/adk/cli/browser/assets/ADK-512-color.svg +9 -0
- google/adk/cli/browser/index.html +2 -2
- google/adk/cli/browser/{main-PKDNKWJE.js → main-CS5OLUMF.js} +59 -59
- google/adk/cli/browser/polyfills-FFHMD2TL.js +17 -0
- google/adk/cli/cli.py +9 -9
- google/adk/cli/cli_deploy.py +157 -0
- google/adk/cli/cli_tools_click.py +228 -99
- google/adk/cli/fast_api.py +119 -34
- google/adk/cli/utils/agent_loader.py +60 -44
- google/adk/cli/utils/envs.py +1 -1
- google/adk/code_executors/unsafe_local_code_executor.py +11 -0
- google/adk/errors/__init__.py +13 -0
- google/adk/errors/not_found_error.py +28 -0
- google/adk/evaluation/agent_evaluator.py +1 -1
- google/adk/evaluation/eval_sets_manager.py +36 -6
- google/adk/evaluation/evaluation_generator.py +5 -4
- google/adk/evaluation/local_eval_sets_manager.py +101 -6
- google/adk/flows/llm_flows/agent_transfer.py +2 -2
- google/adk/flows/llm_flows/base_llm_flow.py +19 -0
- google/adk/flows/llm_flows/contents.py +4 -4
- google/adk/flows/llm_flows/functions.py +140 -127
- google/adk/memory/vertex_ai_rag_memory_service.py +2 -2
- google/adk/models/anthropic_llm.py +7 -10
- google/adk/models/google_llm.py +46 -18
- google/adk/models/lite_llm.py +63 -26
- google/adk/py.typed +0 -0
- google/adk/sessions/_session_util.py +10 -16
- google/adk/sessions/database_session_service.py +81 -66
- google/adk/sessions/vertex_ai_session_service.py +32 -6
- google/adk/telemetry.py +91 -24
- google/adk/tools/_automatic_function_calling_util.py +31 -25
- google/adk/tools/{function_parameter_parse_util.py → _function_parameter_parse_util.py} +9 -3
- google/adk/tools/_gemini_schema_util.py +158 -0
- google/adk/tools/apihub_tool/apihub_toolset.py +3 -2
- google/adk/tools/application_integration_tool/clients/connections_client.py +7 -0
- google/adk/tools/application_integration_tool/integration_connector_tool.py +5 -7
- google/adk/tools/base_tool.py +4 -8
- google/adk/tools/bigquery/__init__.py +11 -1
- google/adk/tools/bigquery/bigquery_credentials.py +9 -4
- google/adk/tools/bigquery/bigquery_toolset.py +86 -0
- google/adk/tools/bigquery/client.py +33 -0
- google/adk/tools/bigquery/metadata_tool.py +249 -0
- google/adk/tools/bigquery/query_tool.py +76 -0
- google/adk/tools/function_tool.py +4 -4
- google/adk/tools/langchain_tool.py +20 -13
- google/adk/tools/load_memory_tool.py +1 -0
- google/adk/tools/mcp_tool/conversion_utils.py +4 -2
- google/adk/tools/mcp_tool/mcp_session_manager.py +63 -5
- google/adk/tools/mcp_tool/mcp_tool.py +3 -2
- google/adk/tools/mcp_tool/mcp_toolset.py +15 -8
- google/adk/tools/openapi_tool/common/common.py +4 -43
- google/adk/tools/openapi_tool/openapi_spec_parser/__init__.py +0 -2
- google/adk/tools/openapi_tool/openapi_spec_parser/openapi_spec_parser.py +4 -2
- google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +4 -2
- google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +7 -127
- google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py +2 -7
- google/adk/tools/transfer_to_agent_tool.py +8 -1
- google/adk/tools/vertex_ai_search_tool.py +8 -1
- google/adk/utils/variant_utils.py +51 -0
- google/adk/version.py +1 -1
- {google_adk-1.1.0.dist-info → google_adk-1.2.0.dist-info}/METADATA +7 -7
- {google_adk-1.1.0.dist-info → google_adk-1.2.0.dist-info}/RECORD +71 -61
- google/adk/cli/browser/polyfills-B6TNHZQ6.js +0 -17
- {google_adk-1.1.0.dist-info → google_adk-1.2.0.dist-info}/WHEEL +0 -0
- {google_adk-1.1.0.dist-info → google_adk-1.2.0.dist-info}/entry_points.txt +0 -0
- {google_adk-1.1.0.dist-info → google_adk-1.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -12,14 +12,17 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
from __future__ import annotations
|
15
16
|
|
16
17
|
from contextlib import AsyncExitStack
|
18
|
+
from datetime import timedelta
|
17
19
|
import functools
|
18
20
|
import logging
|
19
21
|
import sys
|
20
22
|
from typing import Any
|
21
23
|
from typing import Optional
|
22
24
|
from typing import TextIO
|
25
|
+
from typing import Union
|
23
26
|
|
24
27
|
import anyio
|
25
28
|
from pydantic import BaseModel
|
@@ -29,6 +32,7 @@ try:
|
|
29
32
|
from mcp import StdioServerParameters
|
30
33
|
from mcp.client.sse import sse_client
|
31
34
|
from mcp.client.stdio import stdio_client
|
35
|
+
from mcp.client.streamable_http import streamablehttp_client
|
32
36
|
except ImportError as e:
|
33
37
|
import sys
|
34
38
|
|
@@ -56,6 +60,20 @@ class SseServerParams(BaseModel):
|
|
56
60
|
sse_read_timeout: float = 60 * 5
|
57
61
|
|
58
62
|
|
63
|
+
class StreamableHTTPServerParams(BaseModel):
|
64
|
+
"""Parameters for the MCP SSE connection.
|
65
|
+
|
66
|
+
See MCP SSE Client documentation for more details.
|
67
|
+
https://github.com/modelcontextprotocol/python-sdk/blob/main/src/mcp/client/streamable_http.py
|
68
|
+
"""
|
69
|
+
|
70
|
+
url: str
|
71
|
+
headers: dict[str, Any] | None = None
|
72
|
+
timeout: float = 5
|
73
|
+
sse_read_timeout: float = 60 * 5
|
74
|
+
terminate_on_close: bool = True
|
75
|
+
|
76
|
+
|
59
77
|
def retry_on_closed_resource(async_reinit_func_name: str):
|
60
78
|
"""Decorator to automatically reinitialize session and retry action.
|
61
79
|
|
@@ -123,13 +141,17 @@ class MCPSessionManager:
|
|
123
141
|
|
124
142
|
def __init__(
|
125
143
|
self,
|
126
|
-
connection_params:
|
144
|
+
connection_params: Union[
|
145
|
+
StdioServerParameters, SseServerParams, StreamableHTTPServerParams
|
146
|
+
],
|
127
147
|
errlog: TextIO = sys.stderr,
|
128
148
|
):
|
129
149
|
"""Initializes the MCP session manager.
|
130
150
|
|
131
151
|
Args:
|
132
|
-
connection_params: Parameters for the MCP connection (Stdio or
|
152
|
+
connection_params: Parameters for the MCP connection (Stdio, SSE or
|
153
|
+
Streamable HTTP). Stdio by default also has a 5s read timeout as other
|
154
|
+
parameters but it's not configurable for now.
|
133
155
|
errlog: (Optional) TextIO stream for error logging. Use only for
|
134
156
|
initializing a local stdio MCP session.
|
135
157
|
"""
|
@@ -153,6 +175,9 @@ class MCPSessionManager:
|
|
153
175
|
|
154
176
|
try:
|
155
177
|
if isinstance(self._connection_params, StdioServerParameters):
|
178
|
+
# So far timeout is not configurable. Given MCP is still evolving, we
|
179
|
+
# would expect stdio_client to evolve to accept timeout parameter like
|
180
|
+
# other client.
|
156
181
|
client = stdio_client(
|
157
182
|
server=self._connection_params, errlog=self._errlog
|
158
183
|
)
|
@@ -163,6 +188,16 @@ class MCPSessionManager:
|
|
163
188
|
timeout=self._connection_params.timeout,
|
164
189
|
sse_read_timeout=self._connection_params.sse_read_timeout,
|
165
190
|
)
|
191
|
+
elif isinstance(self._connection_params, StreamableHTTPServerParams):
|
192
|
+
client = streamablehttp_client(
|
193
|
+
url=self._connection_params.url,
|
194
|
+
headers=self._connection_params.headers,
|
195
|
+
timeout=timedelta(seconds=self._connection_params.timeout),
|
196
|
+
sse_read_timeout=timedelta(
|
197
|
+
seconds=self._connection_params.sse_read_timeout
|
198
|
+
),
|
199
|
+
terminate_on_close=self._connection_params.terminate_on_close,
|
200
|
+
)
|
166
201
|
else:
|
167
202
|
raise ValueError(
|
168
203
|
'Unable to initialize connection. Connection should be'
|
@@ -171,9 +206,32 @@ class MCPSessionManager:
|
|
171
206
|
)
|
172
207
|
|
173
208
|
transports = await self._exit_stack.enter_async_context(client)
|
174
|
-
|
175
|
-
|
176
|
-
|
209
|
+
# The streamable http client returns a GetSessionCallback in addition to the read/write MemoryObjectStreams
|
210
|
+
# needed to build the ClientSession, we limit then to the two first values to be compatible with all clients.
|
211
|
+
# The StdioServerParameters does not provide a timeout parameter for the
|
212
|
+
# session, so we need to set a default timeout for it. Other clients
|
213
|
+
# (SseServerParams and StreamableHTTPServerParams) already provide a
|
214
|
+
# timeout parameter in their configuration.
|
215
|
+
if isinstance(self._connection_params, StdioServerParameters):
|
216
|
+
# Default timeout for MCP session is 5 seconds, same as SseServerParams
|
217
|
+
# and StreamableHTTPServerParams.
|
218
|
+
# TODO :
|
219
|
+
# 1. make timeout configurable
|
220
|
+
# 2. Add StdioConnectionParams to include StdioServerParameters as a
|
221
|
+
# field and rename other two params to XXXXConnetionParams. Ohter
|
222
|
+
# two params are actually connection params, while stdio is
|
223
|
+
# special, stdio_client takes the resposibility of starting the
|
224
|
+
# server and working as a client.
|
225
|
+
session = await self._exit_stack.enter_async_context(
|
226
|
+
ClientSession(
|
227
|
+
*transports[:2],
|
228
|
+
read_timeout_seconds=timedelta(seconds=5),
|
229
|
+
)
|
230
|
+
)
|
231
|
+
else:
|
232
|
+
session = await self._exit_stack.enter_async_context(
|
233
|
+
ClientSession(*transports[:2])
|
234
|
+
)
|
177
235
|
await session.initialize()
|
178
236
|
|
179
237
|
self._session = session
|
@@ -12,6 +12,7 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
from __future__ import annotations
|
15
16
|
|
16
17
|
import logging
|
17
18
|
from typing import Optional
|
@@ -19,6 +20,7 @@ from typing import Optional
|
|
19
20
|
from google.genai.types import FunctionDeclaration
|
20
21
|
from typing_extensions import override
|
21
22
|
|
23
|
+
from .._gemini_schema_util import _to_gemini_schema
|
22
24
|
from .mcp_session_manager import MCPSessionManager
|
23
25
|
from .mcp_session_manager import retry_on_closed_resource
|
24
26
|
|
@@ -41,7 +43,6 @@ except ImportError as e:
|
|
41
43
|
from ...auth.auth_credential import AuthCredential
|
42
44
|
from ...auth.auth_schemes import AuthScheme
|
43
45
|
from ..base_tool import BaseTool
|
44
|
-
from ..openapi_tool.openapi_spec_parser.rest_api_tool import to_gemini_schema
|
45
46
|
from ..tool_context import ToolContext
|
46
47
|
|
47
48
|
logger = logging.getLogger("google_adk." + __name__)
|
@@ -98,7 +99,7 @@ class MCPTool(BaseTool):
|
|
98
99
|
FunctionDeclaration: The Gemini function declaration for the tool.
|
99
100
|
"""
|
100
101
|
schema_dict = self._mcp_tool.inputSchema
|
101
|
-
parameters =
|
102
|
+
parameters = _to_gemini_schema(schema_dict)
|
102
103
|
function_decl = FunctionDeclaration(
|
103
104
|
name=self.name, description=self.description, parameters=parameters
|
104
105
|
)
|
@@ -12,6 +12,8 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
from __future__ import annotations
|
16
|
+
|
15
17
|
import logging
|
16
18
|
import sys
|
17
19
|
from typing import List
|
@@ -26,6 +28,7 @@ from ..base_toolset import ToolPredicate
|
|
26
28
|
from .mcp_session_manager import MCPSessionManager
|
27
29
|
from .mcp_session_manager import retry_on_closed_resource
|
28
30
|
from .mcp_session_manager import SseServerParams
|
31
|
+
from .mcp_session_manager import StreamableHTTPServerParams
|
29
32
|
|
30
33
|
# Attempt to import MCP Tool from the MCP library, and hints user to upgrade
|
31
34
|
# their Python version to 3.10 if it fails.
|
@@ -82,20 +85,23 @@ class MCPToolset(BaseToolset):
|
|
82
85
|
def __init__(
|
83
86
|
self,
|
84
87
|
*,
|
85
|
-
connection_params:
|
88
|
+
connection_params: (
|
89
|
+
StdioServerParameters | SseServerParams | StreamableHTTPServerParams
|
90
|
+
),
|
86
91
|
tool_filter: Optional[Union[ToolPredicate, List[str]]] = None,
|
87
92
|
errlog: TextIO = sys.stderr,
|
88
93
|
):
|
89
94
|
"""Initializes the MCPToolset.
|
90
95
|
|
91
96
|
Args:
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
97
|
+
connection_params: The connection parameters to the MCP server. Can be:
|
98
|
+
`StdioServerParameters` for using local mcp server (e.g. using `npx` or
|
99
|
+
`python3`); or `SseServerParams` for a local/remote SSE server; or
|
100
|
+
`StreamableHTTPServerParams` for local/remote Streamable http server.
|
101
|
+
tool_filter: Optional filter to select specific tools. Can be either:
|
102
|
+
- A list of tool names to include
|
103
|
+
- A ToolPredicate function for custom filtering logic
|
104
|
+
errlog: TextIO stream for error logging.
|
99
105
|
"""
|
100
106
|
super().__init__(tool_filter=tool_filter)
|
101
107
|
|
@@ -110,6 +116,7 @@ class MCPToolset(BaseToolset):
|
|
110
116
|
connection_params=self._connection_params,
|
111
117
|
errlog=self._errlog,
|
112
118
|
)
|
119
|
+
|
113
120
|
self._session = None
|
114
121
|
|
115
122
|
@retry_on_closed_resource("_reinitialize_session")
|
@@ -12,8 +12,9 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
from __future__ import annotations
|
16
|
+
|
15
17
|
import keyword
|
16
|
-
import re
|
17
18
|
from typing import Any
|
18
19
|
from typing import Dict
|
19
20
|
from typing import List
|
@@ -26,47 +27,7 @@ from pydantic import BaseModel
|
|
26
27
|
from pydantic import Field
|
27
28
|
from pydantic import model_serializer
|
28
29
|
|
29
|
-
|
30
|
-
def to_snake_case(text: str) -> str:
|
31
|
-
"""Converts a string into snake_case.
|
32
|
-
|
33
|
-
Handles lowerCamelCase, UpperCamelCase, or space-separated case, acronyms
|
34
|
-
(e.g., "REST API") and consecutive uppercase letters correctly. Also handles
|
35
|
-
mixed cases with and without spaces.
|
36
|
-
|
37
|
-
Examples:
|
38
|
-
```
|
39
|
-
to_snake_case('camelCase') -> 'camel_case'
|
40
|
-
to_snake_case('UpperCamelCase') -> 'upper_camel_case'
|
41
|
-
to_snake_case('space separated') -> 'space_separated'
|
42
|
-
```
|
43
|
-
|
44
|
-
Args:
|
45
|
-
text: The input string.
|
46
|
-
|
47
|
-
Returns:
|
48
|
-
The snake_case version of the string.
|
49
|
-
"""
|
50
|
-
|
51
|
-
# Handle spaces and non-alphanumeric characters (replace with underscores)
|
52
|
-
text = re.sub(r'[^a-zA-Z0-9]+', '_', text)
|
53
|
-
|
54
|
-
# Insert underscores before uppercase letters (handling both CamelCases)
|
55
|
-
text = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', text) # lowerCamelCase
|
56
|
-
text = re.sub(
|
57
|
-
r'([A-Z]+)([A-Z][a-z])', r'\1_\2', text
|
58
|
-
) # UpperCamelCase and acronyms
|
59
|
-
|
60
|
-
# Convert to lowercase
|
61
|
-
text = text.lower()
|
62
|
-
|
63
|
-
# Remove consecutive underscores (clean up extra underscores)
|
64
|
-
text = re.sub(r'_+', '_', text)
|
65
|
-
|
66
|
-
# Remove leading and trailing underscores
|
67
|
-
text = text.strip('_')
|
68
|
-
|
69
|
-
return text
|
30
|
+
from ..._gemini_schema_util import _to_snake_case
|
70
31
|
|
71
32
|
|
72
33
|
def rename_python_keywords(s: str, prefix: str = 'param_') -> str:
|
@@ -106,7 +67,7 @@ class ApiParameter(BaseModel):
|
|
106
67
|
self.py_name = (
|
107
68
|
self.py_name
|
108
69
|
if self.py_name
|
109
|
-
else rename_python_keywords(
|
70
|
+
else rename_python_keywords(_to_snake_case(self.original_name))
|
110
71
|
)
|
111
72
|
if isinstance(self.param_schema, str):
|
112
73
|
self.param_schema = Schema.model_validate_json(self.param_schema)
|
@@ -20,7 +20,6 @@ from .operation_parser import OperationParser
|
|
20
20
|
from .rest_api_tool import AuthPreparationState
|
21
21
|
from .rest_api_tool import RestApiTool
|
22
22
|
from .rest_api_tool import snake_to_lower_camel
|
23
|
-
from .rest_api_tool import to_gemini_schema
|
24
23
|
from .tool_auth_handler import ToolAuthHandler
|
25
24
|
|
26
25
|
__all__ = [
|
@@ -30,7 +29,6 @@ __all__ = [
|
|
30
29
|
'OpenAPIToolset',
|
31
30
|
'OperationParser',
|
32
31
|
'RestApiTool',
|
33
|
-
'to_gemini_schema',
|
34
32
|
'snake_to_lower_camel',
|
35
33
|
'AuthPreparationState',
|
36
34
|
'ToolAuthHandler',
|
@@ -12,6 +12,8 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
from __future__ import annotations
|
16
|
+
|
15
17
|
import copy
|
16
18
|
from typing import Any
|
17
19
|
from typing import Dict
|
@@ -23,8 +25,8 @@ from pydantic import BaseModel
|
|
23
25
|
|
24
26
|
from ....auth.auth_credential import AuthCredential
|
25
27
|
from ....auth.auth_schemes import AuthScheme
|
28
|
+
from ..._gemini_schema_util import _to_snake_case
|
26
29
|
from ..common.common import ApiParameter
|
27
|
-
from ..common.common import to_snake_case
|
28
30
|
from .operation_parser import OperationParser
|
29
31
|
|
30
32
|
|
@@ -112,7 +114,7 @@ class OpenApiSpecParser:
|
|
112
114
|
# If operation ID is missing, assign an operation id based on path
|
113
115
|
# and method
|
114
116
|
if "operationId" not in operation_dict:
|
115
|
-
temp_id =
|
117
|
+
temp_id = _to_snake_case(f"{path}_{method}")
|
116
118
|
operation_dict["operationId"] = temp_id
|
117
119
|
|
118
120
|
url = OperationEndpoint(base_url=base_url, path=path, method=method)
|
@@ -12,6 +12,8 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
from __future__ import annotations
|
16
|
+
|
15
17
|
import inspect
|
16
18
|
from textwrap import dedent
|
17
19
|
from typing import Any
|
@@ -25,9 +27,9 @@ from fastapi.openapi.models import Operation
|
|
25
27
|
from fastapi.openapi.models import Parameter
|
26
28
|
from fastapi.openapi.models import Schema
|
27
29
|
|
30
|
+
from ..._gemini_schema_util import _to_snake_case
|
28
31
|
from ..common.common import ApiParameter
|
29
32
|
from ..common.common import PydocHelper
|
30
|
-
from ..common.common import to_snake_case
|
31
33
|
|
32
34
|
|
33
35
|
class OperationParser:
|
@@ -189,7 +191,7 @@ class OperationParser:
|
|
189
191
|
operation_id = self._operation.operationId
|
190
192
|
if not operation_id:
|
191
193
|
raise ValueError('Operation ID is missing')
|
192
|
-
return
|
194
|
+
return _to_snake_case(operation_id)[:60]
|
193
195
|
|
194
196
|
def get_return_type_hint(self) -> str:
|
195
197
|
"""Returns the return type hint string (like 'str', 'int', etc.)."""
|
@@ -12,45 +12,36 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
from __future__ import annotations
|
16
|
+
|
15
17
|
from typing import Any
|
16
18
|
from typing import Dict
|
17
19
|
from typing import List
|
18
20
|
from typing import Literal
|
19
21
|
from typing import Optional
|
20
|
-
from typing import Sequence
|
21
22
|
from typing import Tuple
|
22
23
|
from typing import Union
|
23
24
|
|
24
25
|
from fastapi.openapi.models import Operation
|
25
26
|
from google.genai.types import FunctionDeclaration
|
26
|
-
from google.genai.types import Schema
|
27
27
|
import requests
|
28
28
|
from typing_extensions import override
|
29
29
|
|
30
30
|
from ....auth.auth_credential import AuthCredential
|
31
31
|
from ....auth.auth_schemes import AuthScheme
|
32
|
-
from
|
32
|
+
from ..._gemini_schema_util import _to_gemini_schema
|
33
|
+
from ..._gemini_schema_util import _to_snake_case
|
34
|
+
from ...base_tool import BaseTool
|
33
35
|
from ...tool_context import ToolContext
|
34
36
|
from ..auth.auth_helpers import credential_to_param
|
35
37
|
from ..auth.auth_helpers import dict_to_auth_scheme
|
36
38
|
from ..auth.credential_exchangers.auto_auth_credential_exchanger import AutoAuthCredentialExchanger
|
37
39
|
from ..common.common import ApiParameter
|
38
|
-
from ..common.common import to_snake_case
|
39
40
|
from .openapi_spec_parser import OperationEndpoint
|
40
41
|
from .openapi_spec_parser import ParsedOperation
|
41
42
|
from .operation_parser import OperationParser
|
42
43
|
from .tool_auth_handler import ToolAuthHandler
|
43
44
|
|
44
|
-
# Not supported by the Gemini API
|
45
|
-
_OPENAPI_SCHEMA_IGNORE_FIELDS = (
|
46
|
-
"title",
|
47
|
-
"default",
|
48
|
-
"format",
|
49
|
-
"additional_properties",
|
50
|
-
"ref",
|
51
|
-
"def",
|
52
|
-
)
|
53
|
-
|
54
45
|
|
55
46
|
def snake_to_lower_camel(snake_case_string: str):
|
56
47
|
"""Converts a snake_case string to a lower_camel_case string.
|
@@ -70,117 +61,6 @@ def snake_to_lower_camel(snake_case_string: str):
|
|
70
61
|
])
|
71
62
|
|
72
63
|
|
73
|
-
# TODO: Switch to Gemini `from_json_schema` util when it is released
|
74
|
-
# in Gemini SDK.
|
75
|
-
def normalize_json_schema_type(
|
76
|
-
json_schema_type: Optional[Union[str, Sequence[str]]],
|
77
|
-
) -> tuple[Optional[str], bool]:
|
78
|
-
"""Converts a JSON Schema Type into Gemini Schema type.
|
79
|
-
|
80
|
-
Adopted and modified from Gemini SDK. This gets the first available schema
|
81
|
-
type from JSON Schema, and use it to mark Gemini schema type. If JSON Schema
|
82
|
-
contains a list of types, the first non null type is used.
|
83
|
-
|
84
|
-
Remove this after switching to Gemini `from_json_schema`.
|
85
|
-
"""
|
86
|
-
if json_schema_type is None:
|
87
|
-
return None, False
|
88
|
-
if isinstance(json_schema_type, str):
|
89
|
-
if json_schema_type == "null":
|
90
|
-
return None, True
|
91
|
-
return json_schema_type, False
|
92
|
-
|
93
|
-
non_null_types = []
|
94
|
-
nullable = False
|
95
|
-
# If json schema type is an array, pick the first non null type.
|
96
|
-
for type_value in json_schema_type:
|
97
|
-
if type_value == "null":
|
98
|
-
nullable = True
|
99
|
-
else:
|
100
|
-
non_null_types.append(type_value)
|
101
|
-
non_null_type = non_null_types[0] if non_null_types else None
|
102
|
-
return non_null_type, nullable
|
103
|
-
|
104
|
-
|
105
|
-
# TODO: Switch to Gemini `from_json_schema` util when it is released
|
106
|
-
# in Gemini SDK.
|
107
|
-
def to_gemini_schema(openapi_schema: Optional[Dict[str, Any]] = None) -> Schema:
|
108
|
-
"""Converts an OpenAPI schema dictionary to a Gemini Schema object.
|
109
|
-
|
110
|
-
Args:
|
111
|
-
openapi_schema: The OpenAPI schema dictionary.
|
112
|
-
|
113
|
-
Returns:
|
114
|
-
A Pydantic Schema object. Returns None if input is None.
|
115
|
-
Raises TypeError if input is not a dict.
|
116
|
-
"""
|
117
|
-
if openapi_schema is None:
|
118
|
-
return None
|
119
|
-
|
120
|
-
if not isinstance(openapi_schema, dict):
|
121
|
-
raise TypeError("openapi_schema must be a dictionary")
|
122
|
-
|
123
|
-
pydantic_schema_data = {}
|
124
|
-
|
125
|
-
# Adding this to force adding a type to an empty dict
|
126
|
-
# This avoid "... one_of or any_of must specify a type" error
|
127
|
-
if not openapi_schema.get("type"):
|
128
|
-
openapi_schema["type"] = "object"
|
129
|
-
|
130
|
-
for key, value in openapi_schema.items():
|
131
|
-
snake_case_key = to_snake_case(key)
|
132
|
-
# Check if the snake_case_key exists in the Schema model's fields.
|
133
|
-
if snake_case_key in Schema.model_fields:
|
134
|
-
if snake_case_key in _OPENAPI_SCHEMA_IGNORE_FIELDS:
|
135
|
-
# Ignore these fields as Gemini backend doesn't recognize them, and will
|
136
|
-
# throw exception if they appear in the schema.
|
137
|
-
# Format: properties[expiration].format: only 'enum' and 'date-time' are
|
138
|
-
# supported for STRING type
|
139
|
-
continue
|
140
|
-
elif snake_case_key == "type":
|
141
|
-
schema_type, nullable = normalize_json_schema_type(
|
142
|
-
openapi_schema.get("type", None)
|
143
|
-
)
|
144
|
-
# Adding this to force adding a type to an empty dict
|
145
|
-
# This avoid "... one_of or any_of must specify a type" error
|
146
|
-
pydantic_schema_data["type"] = schema_type if schema_type else "object"
|
147
|
-
pydantic_schema_data["type"] = pydantic_schema_data["type"].upper()
|
148
|
-
if nullable:
|
149
|
-
pydantic_schema_data["nullable"] = True
|
150
|
-
elif snake_case_key == "properties" and isinstance(value, dict):
|
151
|
-
pydantic_schema_data[snake_case_key] = {
|
152
|
-
k: to_gemini_schema(v) for k, v in value.items()
|
153
|
-
}
|
154
|
-
elif snake_case_key == "items" and isinstance(value, dict):
|
155
|
-
pydantic_schema_data[snake_case_key] = to_gemini_schema(value)
|
156
|
-
elif snake_case_key == "any_of" and isinstance(value, list):
|
157
|
-
pydantic_schema_data[snake_case_key] = [
|
158
|
-
to_gemini_schema(item) for item in value
|
159
|
-
]
|
160
|
-
# Important: Handle cases where the OpenAPI schema might contain lists
|
161
|
-
# or other structures that need to be recursively processed.
|
162
|
-
elif isinstance(value, list) and snake_case_key not in (
|
163
|
-
"enum",
|
164
|
-
"required",
|
165
|
-
"property_ordering",
|
166
|
-
):
|
167
|
-
new_list = []
|
168
|
-
for item in value:
|
169
|
-
if isinstance(item, dict):
|
170
|
-
new_list.append(to_gemini_schema(item))
|
171
|
-
else:
|
172
|
-
new_list.append(item)
|
173
|
-
pydantic_schema_data[snake_case_key] = new_list
|
174
|
-
elif isinstance(value, dict) and snake_case_key not in ("properties"):
|
175
|
-
# Handle dictionary which is neither properties or items
|
176
|
-
pydantic_schema_data[snake_case_key] = to_gemini_schema(value)
|
177
|
-
else:
|
178
|
-
# Simple value assignment (int, str, bool, etc.)
|
179
|
-
pydantic_schema_data[snake_case_key] = value
|
180
|
-
|
181
|
-
return Schema(**pydantic_schema_data)
|
182
|
-
|
183
|
-
|
184
64
|
AuthPreparationState = Literal["pending", "done"]
|
185
65
|
|
186
66
|
|
@@ -273,7 +153,7 @@ class RestApiTool(BaseTool):
|
|
273
153
|
parsed.operation, parsed.parameters, parsed.return_value
|
274
154
|
)
|
275
155
|
|
276
|
-
tool_name =
|
156
|
+
tool_name = _to_snake_case(operation_parser.get_function_name())
|
277
157
|
generated = cls(
|
278
158
|
name=tool_name,
|
279
159
|
description=parsed.operation.description
|
@@ -306,7 +186,7 @@ class RestApiTool(BaseTool):
|
|
306
186
|
def _get_declaration(self) -> FunctionDeclaration:
|
307
187
|
"""Returns the function declaration in the Gemini Schema format."""
|
308
188
|
schema_dict = self._operation_parser.get_json_schema()
|
309
|
-
parameters =
|
189
|
+
parameters = _to_gemini_schema(schema_dict)
|
310
190
|
function_decl = FunctionDeclaration(
|
311
191
|
name=self.name, description=self.description, parameters=parameters
|
312
192
|
)
|
@@ -185,7 +185,7 @@ class ToolAuthHandler:
|
|
185
185
|
)
|
186
186
|
self.credential_store.store_credential(key, auth_credential)
|
187
187
|
|
188
|
-
def
|
188
|
+
def _request_credential(self) -> None:
|
189
189
|
"""Handles the case where an OpenID Connect or OAuth2 authentication request is needed."""
|
190
190
|
if self.auth_scheme.type_ in (
|
191
191
|
AuthSchemeType.openIdConnect,
|
@@ -223,11 +223,6 @@ class ToolAuthHandler:
|
|
223
223
|
)
|
224
224
|
)
|
225
225
|
|
226
|
-
def _request_credential(self, auth_config: AuthConfig):
|
227
|
-
if not self.tool_context:
|
228
|
-
return
|
229
|
-
self.tool_context.request_credential(auth_config)
|
230
|
-
|
231
226
|
def prepare_auth_credentials(
|
232
227
|
self,
|
233
228
|
) -> AuthPreparationResult:
|
@@ -260,7 +255,7 @@ class ToolAuthHandler:
|
|
260
255
|
auth_credential=exchanged_credential,
|
261
256
|
)
|
262
257
|
else:
|
263
|
-
self.
|
258
|
+
self._request_credential()
|
264
259
|
return AuthPreparationResult(
|
265
260
|
state="pending",
|
266
261
|
auth_scheme=self.auth_scheme,
|
@@ -16,5 +16,12 @@ from .tool_context import ToolContext
|
|
16
16
|
|
17
17
|
|
18
18
|
def transfer_to_agent(agent_name: str, tool_context: ToolContext):
|
19
|
-
"""Transfer the question to another agent.
|
19
|
+
"""Transfer the question to another agent.
|
20
|
+
|
21
|
+
This tool hands off control to another agent when it's more suitable to
|
22
|
+
answer the user's question according to the agent's description.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
agent_name: the agent name to transfer to.
|
26
|
+
"""
|
20
27
|
tool_context.actions.transfer_to_agent = agent_name
|
@@ -40,6 +40,8 @@ class VertexAiSearchTool(BaseTool):
|
|
40
40
|
*,
|
41
41
|
data_store_id: Optional[str] = None,
|
42
42
|
search_engine_id: Optional[str] = None,
|
43
|
+
filter: Optional[str] = None,
|
44
|
+
max_results: Optional[int] = None,
|
43
45
|
):
|
44
46
|
"""Initializes the Vertex AI Search tool.
|
45
47
|
|
@@ -64,6 +66,8 @@ class VertexAiSearchTool(BaseTool):
|
|
64
66
|
)
|
65
67
|
self.data_store_id = data_store_id
|
66
68
|
self.search_engine_id = search_engine_id
|
69
|
+
self.filter = filter
|
70
|
+
self.max_results = max_results
|
67
71
|
|
68
72
|
@override
|
69
73
|
async def process_llm_request(
|
@@ -84,7 +88,10 @@ class VertexAiSearchTool(BaseTool):
|
|
84
88
|
types.Tool(
|
85
89
|
retrieval=types.Retrieval(
|
86
90
|
vertex_ai_search=types.VertexAISearch(
|
87
|
-
datastore=self.data_store_id,
|
91
|
+
datastore=self.data_store_id,
|
92
|
+
engine=self.search_engine_id,
|
93
|
+
filter=self.filter,
|
94
|
+
max_results=self.max_results,
|
88
95
|
)
|
89
96
|
)
|
90
97
|
)
|
@@ -0,0 +1,51 @@
|
|
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
|
+
"""Utilities for Google LLM variants.
|
16
|
+
|
17
|
+
This module is for ADK internal use only.
|
18
|
+
Please do not rely on the implementation details.
|
19
|
+
"""
|
20
|
+
|
21
|
+
from __future__ import annotations
|
22
|
+
|
23
|
+
from enum import Enum
|
24
|
+
import os
|
25
|
+
|
26
|
+
_GOOGLE_LLM_VARIANT_VERTEX_AI = 'VERTEX_AI'
|
27
|
+
_GOOGLE_LLM_VARIANT_GEMINI_API = 'GEMINI_API'
|
28
|
+
|
29
|
+
|
30
|
+
class GoogleLLMVariant(Enum):
|
31
|
+
"""
|
32
|
+
The Google LLM variant to use.
|
33
|
+
see https://google.github.io/adk-docs/get-started/quickstart/#set-up-the-model
|
34
|
+
"""
|
35
|
+
|
36
|
+
VERTEX_AI = _GOOGLE_LLM_VARIANT_VERTEX_AI
|
37
|
+
"""For using credentials from Google Vertex AI"""
|
38
|
+
GEMINI_API = _GOOGLE_LLM_VARIANT_GEMINI_API
|
39
|
+
"""For using API Key from Google AI Studio"""
|
40
|
+
|
41
|
+
|
42
|
+
def get_google_llm_variant() -> str:
|
43
|
+
return (
|
44
|
+
GoogleLLMVariant.VERTEX_AI
|
45
|
+
if os.environ.get('GOOGLE_GENAI_USE_VERTEXAI', '0').lower()
|
46
|
+
in [
|
47
|
+
'true',
|
48
|
+
'1',
|
49
|
+
]
|
50
|
+
else GoogleLLMVariant.GEMINI_API
|
51
|
+
)
|
google/adk/version.py
CHANGED