google-adk 0.2.0__py3-none-any.whl → 0.4.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 +7 -7
- google/adk/agents/callback_context.py +0 -1
- google/adk/agents/llm_agent.py +3 -8
- google/adk/auth/auth_credential.py +2 -1
- google/adk/auth/auth_handler.py +7 -3
- google/adk/cli/browser/index.html +1 -1
- google/adk/cli/browser/{main-ZBO76GRM.js → main-HWIBUY2R.js} +69 -53
- google/adk/cli/cli.py +54 -47
- google/adk/cli/cli_deploy.py +6 -1
- google/adk/cli/cli_eval.py +1 -1
- google/adk/cli/cli_tools_click.py +78 -13
- google/adk/cli/fast_api.py +6 -0
- google/adk/evaluation/agent_evaluator.py +2 -2
- google/adk/evaluation/response_evaluator.py +2 -2
- google/adk/evaluation/trajectory_evaluator.py +4 -5
- google/adk/events/event_actions.py +9 -4
- google/adk/flows/llm_flows/agent_transfer.py +1 -1
- google/adk/flows/llm_flows/base_llm_flow.py +1 -1
- google/adk/flows/llm_flows/contents.py +10 -6
- google/adk/flows/llm_flows/functions.py +38 -18
- google/adk/flows/llm_flows/instructions.py +2 -2
- google/adk/models/gemini_llm_connection.py +2 -2
- google/adk/models/llm_response.py +10 -1
- google/adk/planners/built_in_planner.py +1 -0
- google/adk/sessions/_session_util.py +29 -0
- google/adk/sessions/database_session_service.py +60 -43
- google/adk/sessions/state.py +1 -1
- google/adk/sessions/vertex_ai_session_service.py +7 -5
- google/adk/tools/agent_tool.py +2 -3
- google/adk/tools/application_integration_tool/__init__.py +2 -0
- google/adk/tools/application_integration_tool/application_integration_toolset.py +48 -26
- google/adk/tools/application_integration_tool/clients/connections_client.py +26 -54
- google/adk/tools/application_integration_tool/integration_connector_tool.py +159 -0
- google/adk/tools/function_tool.py +42 -0
- google/adk/tools/google_api_tool/google_api_tool_set.py +12 -9
- google/adk/tools/load_artifacts_tool.py +1 -1
- google/adk/tools/openapi_tool/auth/credential_exchangers/oauth2_exchanger.py +4 -4
- google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +1 -1
- google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +5 -12
- google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +46 -8
- google/adk/version.py +1 -1
- {google_adk-0.2.0.dist-info → google_adk-0.4.0.dist-info}/METADATA +28 -9
- {google_adk-0.2.0.dist-info → google_adk-0.4.0.dist-info}/RECORD +46 -44
- {google_adk-0.2.0.dist-info → google_adk-0.4.0.dist-info}/WHEEL +0 -0
- {google_adk-0.2.0.dist-info → google_adk-0.4.0.dist-info}/entry_points.txt +0 -0
- {google_adk-0.2.0.dist-info → google_adk-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,159 @@
|
|
1
|
+
# Copyright 2025 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
import logging
|
17
|
+
from typing import Any
|
18
|
+
from typing import Dict
|
19
|
+
from typing import Optional
|
20
|
+
|
21
|
+
from google.adk.tools.openapi_tool.openapi_spec_parser.rest_api_tool import RestApiTool
|
22
|
+
from google.adk.tools.openapi_tool.openapi_spec_parser.rest_api_tool import to_gemini_schema
|
23
|
+
from google.genai.types import FunctionDeclaration
|
24
|
+
from typing_extensions import override
|
25
|
+
|
26
|
+
from .. import BaseTool
|
27
|
+
from ..tool_context import ToolContext
|
28
|
+
|
29
|
+
logger = logging.getLogger(__name__)
|
30
|
+
|
31
|
+
|
32
|
+
class IntegrationConnectorTool(BaseTool):
|
33
|
+
"""A tool that wraps a RestApiTool to interact with a specific Application Integration endpoint.
|
34
|
+
|
35
|
+
This tool adds Application Integration specific context like connection
|
36
|
+
details, entity, operation, and action to the underlying REST API call
|
37
|
+
handled by RestApiTool. It prepares the arguments and then delegates the
|
38
|
+
actual API call execution to the contained RestApiTool instance.
|
39
|
+
|
40
|
+
* Generates request params and body
|
41
|
+
* Attaches auth credentials to API call.
|
42
|
+
|
43
|
+
Example:
|
44
|
+
```
|
45
|
+
# Each API operation in the spec will be turned into its own tool
|
46
|
+
# Name of the tool is the operationId of that operation, in snake case
|
47
|
+
operations = OperationGenerator().parse(openapi_spec_dict)
|
48
|
+
tool = [RestApiTool.from_parsed_operation(o) for o in operations]
|
49
|
+
```
|
50
|
+
"""
|
51
|
+
|
52
|
+
EXCLUDE_FIELDS = [
|
53
|
+
'connection_name',
|
54
|
+
'service_name',
|
55
|
+
'host',
|
56
|
+
'entity',
|
57
|
+
'operation',
|
58
|
+
'action',
|
59
|
+
]
|
60
|
+
|
61
|
+
OPTIONAL_FIELDS = [
|
62
|
+
'page_size',
|
63
|
+
'page_token',
|
64
|
+
'filter',
|
65
|
+
]
|
66
|
+
|
67
|
+
def __init__(
|
68
|
+
self,
|
69
|
+
name: str,
|
70
|
+
description: str,
|
71
|
+
connection_name: str,
|
72
|
+
connection_host: str,
|
73
|
+
connection_service_name: str,
|
74
|
+
entity: str,
|
75
|
+
operation: str,
|
76
|
+
action: str,
|
77
|
+
rest_api_tool: RestApiTool,
|
78
|
+
):
|
79
|
+
"""Initializes the ApplicationIntegrationTool.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
name: The name of the tool, typically derived from the API operation.
|
83
|
+
Should be unique and adhere to Gemini function naming conventions
|
84
|
+
(e.g., less than 64 characters).
|
85
|
+
description: A description of what the tool does, usually based on the
|
86
|
+
API operation's summary or description.
|
87
|
+
connection_name: The name of the Integration Connector connection.
|
88
|
+
connection_host: The hostname or IP address for the connection.
|
89
|
+
connection_service_name: The specific service name within the host.
|
90
|
+
entity: The Integration Connector entity being targeted.
|
91
|
+
operation: The specific operation being performed on the entity.
|
92
|
+
action: The action associated with the operation (e.g., 'execute').
|
93
|
+
rest_api_tool: An initialized RestApiTool instance that handles the
|
94
|
+
underlying REST API communication based on an OpenAPI specification
|
95
|
+
operation. This tool will be called by ApplicationIntegrationTool with
|
96
|
+
added connection and context arguments. tool =
|
97
|
+
[RestApiTool.from_parsed_operation(o) for o in operations]
|
98
|
+
"""
|
99
|
+
# Gemini restrict the length of function name to be less than 64 characters
|
100
|
+
super().__init__(
|
101
|
+
name=name,
|
102
|
+
description=description,
|
103
|
+
)
|
104
|
+
self.connection_name = connection_name
|
105
|
+
self.connection_host = connection_host
|
106
|
+
self.connection_service_name = connection_service_name
|
107
|
+
self.entity = entity
|
108
|
+
self.operation = operation
|
109
|
+
self.action = action
|
110
|
+
self.rest_api_tool = rest_api_tool
|
111
|
+
|
112
|
+
@override
|
113
|
+
def _get_declaration(self) -> FunctionDeclaration:
|
114
|
+
"""Returns the function declaration in the Gemini Schema format."""
|
115
|
+
schema_dict = self.rest_api_tool._operation_parser.get_json_schema()
|
116
|
+
for field in self.EXCLUDE_FIELDS:
|
117
|
+
if field in schema_dict['properties']:
|
118
|
+
del schema_dict['properties'][field]
|
119
|
+
for field in self.OPTIONAL_FIELDS + self.EXCLUDE_FIELDS:
|
120
|
+
if field in schema_dict['required']:
|
121
|
+
schema_dict['required'].remove(field)
|
122
|
+
|
123
|
+
parameters = to_gemini_schema(schema_dict)
|
124
|
+
function_decl = FunctionDeclaration(
|
125
|
+
name=self.name, description=self.description, parameters=parameters
|
126
|
+
)
|
127
|
+
return function_decl
|
128
|
+
|
129
|
+
@override
|
130
|
+
async def run_async(
|
131
|
+
self, *, args: dict[str, Any], tool_context: Optional[ToolContext]
|
132
|
+
) -> Dict[str, Any]:
|
133
|
+
args['connection_name'] = self.connection_name
|
134
|
+
args['service_name'] = self.connection_service_name
|
135
|
+
args['host'] = self.connection_host
|
136
|
+
args['entity'] = self.entity
|
137
|
+
args['operation'] = self.operation
|
138
|
+
args['action'] = self.action
|
139
|
+
logger.info('Running tool: %s with args: %s', self.name, args)
|
140
|
+
return self.rest_api_tool.call(args=args, tool_context=tool_context)
|
141
|
+
|
142
|
+
def __str__(self):
|
143
|
+
return (
|
144
|
+
f'ApplicationIntegrationTool(name="{self.name}",'
|
145
|
+
f' description="{self.description}",'
|
146
|
+
f' connection_name="{self.connection_name}", entity="{self.entity}",'
|
147
|
+
f' operation="{self.operation}", action="{self.action}")'
|
148
|
+
)
|
149
|
+
|
150
|
+
def __repr__(self):
|
151
|
+
return (
|
152
|
+
f'ApplicationIntegrationTool(name="{self.name}",'
|
153
|
+
f' description="{self.description}",'
|
154
|
+
f' connection_name="{self.connection_name}",'
|
155
|
+
f' connection_host="{self.connection_host}",'
|
156
|
+
f' connection_service_name="{self.connection_service_name}",'
|
157
|
+
f' entity="{self.entity}", operation="{self.operation}",'
|
158
|
+
f' action="{self.action}", rest_api_tool={repr(self.rest_api_tool)})'
|
159
|
+
)
|
@@ -59,6 +59,23 @@ class FunctionTool(BaseTool):
|
|
59
59
|
if 'tool_context' in signature.parameters:
|
60
60
|
args_to_call['tool_context'] = tool_context
|
61
61
|
|
62
|
+
# Before invoking the function, we check for if the list of args passed in
|
63
|
+
# has all the mandatory arguments or not.
|
64
|
+
# If the check fails, then we don't invoke the tool and let the Agent know
|
65
|
+
# that there was a missing a input parameter. This will basically help
|
66
|
+
# the underlying model fix the issue and retry.
|
67
|
+
mandatory_args = self._get_mandatory_args()
|
68
|
+
missing_mandatory_args = [
|
69
|
+
arg for arg in mandatory_args if arg not in args_to_call
|
70
|
+
]
|
71
|
+
|
72
|
+
if missing_mandatory_args:
|
73
|
+
missing_mandatory_args_str = '\n'.join(missing_mandatory_args)
|
74
|
+
error_str = f"""Invoking `{self.name}()` failed as the following mandatory input parameters are not present:
|
75
|
+
{missing_mandatory_args_str}
|
76
|
+
You could retry calling this tool, but it is IMPORTANT for you to provide all the mandatory parameters."""
|
77
|
+
return {'error': error_str}
|
78
|
+
|
62
79
|
if inspect.iscoroutinefunction(self.func):
|
63
80
|
return await self.func(**args_to_call) or {}
|
64
81
|
else:
|
@@ -85,3 +102,28 @@ class FunctionTool(BaseTool):
|
|
85
102
|
args_to_call['tool_context'] = tool_context
|
86
103
|
async for item in self.func(**args_to_call):
|
87
104
|
yield item
|
105
|
+
|
106
|
+
def _get_mandatory_args(
|
107
|
+
self,
|
108
|
+
) -> list[str]:
|
109
|
+
"""Identifies mandatory parameters (those without default values) for a function.
|
110
|
+
|
111
|
+
Returns:
|
112
|
+
A list of strings, where each string is the name of a mandatory parameter.
|
113
|
+
"""
|
114
|
+
signature = inspect.signature(self.func)
|
115
|
+
mandatory_params = []
|
116
|
+
|
117
|
+
for name, param in signature.parameters.items():
|
118
|
+
# A parameter is mandatory if:
|
119
|
+
# 1. It has no default value (param.default is inspect.Parameter.empty)
|
120
|
+
# 2. It's not a variable positional (*args) or variable keyword (**kwargs) parameter
|
121
|
+
#
|
122
|
+
# For more refer to: https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind
|
123
|
+
if param.default == inspect.Parameter.empty and param.kind not in (
|
124
|
+
inspect.Parameter.VAR_POSITIONAL,
|
125
|
+
inspect.Parameter.VAR_KEYWORD,
|
126
|
+
):
|
127
|
+
mandatory_params.append(name)
|
128
|
+
|
129
|
+
return mandatory_params
|
@@ -11,10 +11,12 @@
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
|
+
|
15
|
+
from __future__ import annotations
|
16
|
+
|
14
17
|
import inspect
|
15
18
|
import os
|
16
19
|
from typing import Any
|
17
|
-
from typing import Dict
|
18
20
|
from typing import Final
|
19
21
|
from typing import List
|
20
22
|
from typing import Optional
|
@@ -28,6 +30,7 @@ from .googleapi_to_openapi_converter import GoogleApiToOpenApiConverter
|
|
28
30
|
|
29
31
|
|
30
32
|
class GoogleApiToolSet:
|
33
|
+
"""Google API Tool Set."""
|
31
34
|
|
32
35
|
def __init__(self, tools: List[RestApiTool]):
|
33
36
|
self.tools: Final[List[GoogleApiTool]] = [
|
@@ -45,10 +48,10 @@ class GoogleApiToolSet:
|
|
45
48
|
|
46
49
|
@staticmethod
|
47
50
|
def _load_tool_set_with_oidc_auth(
|
48
|
-
spec_file: str = None,
|
49
|
-
spec_dict:
|
50
|
-
scopes: list[str] = None,
|
51
|
-
) ->
|
51
|
+
spec_file: Optional[str] = None,
|
52
|
+
spec_dict: Optional[dict[str, Any]] = None,
|
53
|
+
scopes: Optional[list[str]] = None,
|
54
|
+
) -> OpenAPIToolset:
|
52
55
|
spec_str = None
|
53
56
|
if spec_file:
|
54
57
|
# Get the frame of the caller
|
@@ -90,18 +93,18 @@ class GoogleApiToolSet:
|
|
90
93
|
|
91
94
|
@classmethod
|
92
95
|
def load_tool_set(
|
93
|
-
|
96
|
+
cls: Type[GoogleApiToolSet],
|
94
97
|
api_name: str,
|
95
98
|
api_version: str,
|
96
|
-
) ->
|
99
|
+
) -> GoogleApiToolSet:
|
97
100
|
spec_dict = GoogleApiToOpenApiConverter(api_name, api_version).convert()
|
98
101
|
scope = list(
|
99
102
|
spec_dict['components']['securitySchemes']['oauth2']['flows'][
|
100
103
|
'authorizationCode'
|
101
104
|
]['scopes'].keys()
|
102
105
|
)[0]
|
103
|
-
return
|
104
|
-
|
106
|
+
return cls(
|
107
|
+
cls._load_tool_set_with_oidc_auth(
|
105
108
|
spec_dict=spec_dict, scopes=[scope]
|
106
109
|
).get_tools()
|
107
110
|
)
|
@@ -89,7 +89,7 @@ class LoadArtifactsTool(BaseTool):
|
|
89
89
|
than the function call.
|
90
90
|
"""])
|
91
91
|
|
92
|
-
#
|
92
|
+
# Attach the content of the artifacts if the model requests them.
|
93
93
|
# This only adds the content to the model request, instead of the session.
|
94
94
|
if llm_request.contents and llm_request.contents[-1].parts:
|
95
95
|
function_response = llm_request.contents[-1].parts[0].function_response
|
@@ -66,10 +66,10 @@ class OAuth2CredentialExchanger(BaseAuthCredentialExchanger):
|
|
66
66
|
|
67
67
|
Returns:
|
68
68
|
An AuthCredential object containing the HTTP bearer access token. If the
|
69
|
-
|
69
|
+
HTTP bearer token cannot be generated, return the original credential.
|
70
70
|
"""
|
71
71
|
|
72
|
-
if
|
72
|
+
if not auth_credential.oauth2.access_token:
|
73
73
|
return auth_credential
|
74
74
|
|
75
75
|
# Return the access token as a bearer token.
|
@@ -78,7 +78,7 @@ class OAuth2CredentialExchanger(BaseAuthCredentialExchanger):
|
|
78
78
|
http=HttpAuth(
|
79
79
|
scheme="bearer",
|
80
80
|
credentials=HttpCredentials(
|
81
|
-
token=auth_credential.oauth2.
|
81
|
+
token=auth_credential.oauth2.access_token
|
82
82
|
),
|
83
83
|
),
|
84
84
|
)
|
@@ -111,7 +111,7 @@ class OAuth2CredentialExchanger(BaseAuthCredentialExchanger):
|
|
111
111
|
return auth_credential
|
112
112
|
|
113
113
|
# If access token is exchanged, exchange a HTTPBearer token.
|
114
|
-
if auth_credential.oauth2.
|
114
|
+
if auth_credential.oauth2.access_token:
|
115
115
|
return self.generate_auth_token(auth_credential)
|
116
116
|
|
117
117
|
return None
|
@@ -124,7 +124,7 @@ class OpenAPIToolset:
|
|
124
124
|
def _load_spec(
|
125
125
|
self, spec_str: str, spec_type: Literal["json", "yaml"]
|
126
126
|
) -> Dict[str, Any]:
|
127
|
-
"""Loads the OpenAPI spec string into
|
127
|
+
"""Loads the OpenAPI spec string into a dictionary."""
|
128
128
|
if spec_type == "json":
|
129
129
|
return json.loads(spec_str)
|
130
130
|
elif spec_type == "yaml":
|
@@ -14,20 +14,12 @@
|
|
14
14
|
|
15
15
|
import inspect
|
16
16
|
from textwrap import dedent
|
17
|
-
from typing import Any
|
18
|
-
from typing import Dict
|
19
|
-
from typing import List
|
20
|
-
from typing import Optional
|
21
|
-
from typing import Union
|
17
|
+
from typing import Any, Dict, List, Optional, Union
|
22
18
|
|
23
19
|
from fastapi.encoders import jsonable_encoder
|
24
|
-
from fastapi.openapi.models import Operation
|
25
|
-
from fastapi.openapi.models import Parameter
|
26
|
-
from fastapi.openapi.models import Schema
|
20
|
+
from fastapi.openapi.models import Operation, Parameter, Schema
|
27
21
|
|
28
|
-
from ..common.common import ApiParameter
|
29
|
-
from ..common.common import PydocHelper
|
30
|
-
from ..common.common import to_snake_case
|
22
|
+
from ..common.common import ApiParameter, PydocHelper, to_snake_case
|
31
23
|
|
32
24
|
|
33
25
|
class OperationParser:
|
@@ -110,7 +102,8 @@ class OperationParser:
|
|
110
102
|
description = request_body.description or ''
|
111
103
|
|
112
104
|
if schema and schema.type == 'object':
|
113
|
-
|
105
|
+
properties = schema.properties or {}
|
106
|
+
for prop_name, prop_details in properties.items():
|
114
107
|
self.params.append(
|
115
108
|
ApiParameter(
|
116
109
|
original_name=prop_name,
|
@@ -17,6 +17,7 @@ from typing import Dict
|
|
17
17
|
from typing import List
|
18
18
|
from typing import Literal
|
19
19
|
from typing import Optional
|
20
|
+
from typing import Sequence
|
20
21
|
from typing import Tuple
|
21
22
|
from typing import Union
|
22
23
|
|
@@ -59,6 +60,40 @@ def snake_to_lower_camel(snake_case_string: str):
|
|
59
60
|
])
|
60
61
|
|
61
62
|
|
63
|
+
# TODO: Switch to Gemini `from_json_schema` util when it is released
|
64
|
+
# in Gemini SDK.
|
65
|
+
def normalize_json_schema_type(
|
66
|
+
json_schema_type: Optional[Union[str, Sequence[str]]],
|
67
|
+
) -> tuple[Optional[str], bool]:
|
68
|
+
"""Converts a JSON Schema Type into Gemini Schema type.
|
69
|
+
|
70
|
+
Adopted and modified from Gemini SDK. This gets the first available schema
|
71
|
+
type from JSON Schema, and use it to mark Gemini schema type. If JSON Schema
|
72
|
+
contains a list of types, the first non null type is used.
|
73
|
+
|
74
|
+
Remove this after switching to Gemini `from_json_schema`.
|
75
|
+
"""
|
76
|
+
if json_schema_type is None:
|
77
|
+
return None, False
|
78
|
+
if isinstance(json_schema_type, str):
|
79
|
+
if json_schema_type == "null":
|
80
|
+
return None, True
|
81
|
+
return json_schema_type, False
|
82
|
+
|
83
|
+
non_null_types = []
|
84
|
+
nullable = False
|
85
|
+
# If json schema type is an array, pick the first non null type.
|
86
|
+
for type_value in json_schema_type:
|
87
|
+
if type_value == "null":
|
88
|
+
nullable = True
|
89
|
+
else:
|
90
|
+
non_null_types.append(type_value)
|
91
|
+
non_null_type = non_null_types[0] if non_null_types else None
|
92
|
+
return non_null_type, nullable
|
93
|
+
|
94
|
+
|
95
|
+
# TODO: Switch to Gemini `from_json_schema` util when it is released
|
96
|
+
# in Gemini SDK.
|
62
97
|
def to_gemini_schema(openapi_schema: Optional[Dict[str, Any]] = None) -> Schema:
|
63
98
|
"""Converts an OpenAPI schema dictionary to a Gemini Schema object.
|
64
99
|
|
@@ -82,13 +117,6 @@ def to_gemini_schema(openapi_schema: Optional[Dict[str, Any]] = None) -> Schema:
|
|
82
117
|
if not openapi_schema.get("type"):
|
83
118
|
openapi_schema["type"] = "object"
|
84
119
|
|
85
|
-
# Adding this to avoid "properties: should be non-empty for OBJECT type" error
|
86
|
-
# See b/385165182
|
87
|
-
if openapi_schema.get("type", "") == "object" and not openapi_schema.get(
|
88
|
-
"properties"
|
89
|
-
):
|
90
|
-
openapi_schema["properties"] = {"dummy_DO_NOT_GENERATE": {"type": "string"}}
|
91
|
-
|
92
120
|
for key, value in openapi_schema.items():
|
93
121
|
snake_case_key = to_snake_case(key)
|
94
122
|
# Check if the snake_case_key exists in the Schema model's fields.
|
@@ -99,7 +127,17 @@ def to_gemini_schema(openapi_schema: Optional[Dict[str, Any]] = None) -> Schema:
|
|
99
127
|
# Format: properties[expiration].format: only 'enum' and 'date-time' are
|
100
128
|
# supported for STRING type
|
101
129
|
continue
|
102
|
-
|
130
|
+
elif snake_case_key == "type":
|
131
|
+
schema_type, nullable = normalize_json_schema_type(
|
132
|
+
openapi_schema.get("type", None)
|
133
|
+
)
|
134
|
+
# Adding this to force adding a type to an empty dict
|
135
|
+
# This avoid "... one_of or any_of must specify a type" error
|
136
|
+
pydantic_schema_data["type"] = schema_type if schema_type else "object"
|
137
|
+
pydantic_schema_data["type"] = pydantic_schema_data["type"].upper()
|
138
|
+
if nullable:
|
139
|
+
pydantic_schema_data["nullable"] = True
|
140
|
+
elif snake_case_key == "properties" and isinstance(value, dict):
|
103
141
|
pydantic_schema_data[snake_case_key] = {
|
104
142
|
k: to_gemini_schema(v) for k, v in value.items()
|
105
143
|
}
|
google/adk/version.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: google-adk
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.0
|
4
4
|
Summary: Agent Development Kit
|
5
5
|
Author-email: Google LLC <googleapis-packages@google.com>
|
6
6
|
Requires-Python: >=3.9
|
@@ -27,7 +27,7 @@ Requires-Dist: google-cloud-aiplatform>=1.87.0
|
|
27
27
|
Requires-Dist: google-cloud-secret-manager>=2.22.0
|
28
28
|
Requires-Dist: google-cloud-speech>=2.30.0
|
29
29
|
Requires-Dist: google-cloud-storage>=2.18.0, <3.0.0
|
30
|
-
Requires-Dist: google-genai>=1.
|
30
|
+
Requires-Dist: google-genai>=1.11.0
|
31
31
|
Requires-Dist: graphviz>=0.20.2
|
32
32
|
Requires-Dist: mcp>=1.5.0;python_version>='3.10'
|
33
33
|
Requires-Dist: opentelemetry-api>=1.31.0
|
@@ -99,11 +99,7 @@ Provides-Extra: test
|
|
99
99
|
</h3>
|
100
100
|
</html>
|
101
101
|
|
102
|
-
Agent Development Kit (ADK) is designed for developers
|
103
|
-
control and flexibility when building advanced AI agents that are tightly
|
104
|
-
integrated with services in Google Cloud. It allows you to define agent
|
105
|
-
behavior, orchestration, and tool use directly in code, enabling robust
|
106
|
-
debugging, versioning, and deployment anywhere – from your laptop to the cloud.
|
102
|
+
Agent Development Kit (ADK) is a flexible and modular framework for developing and deploying AI agents. While optimized for Gemini and the Google ecosystem, ADK is model-agnostic, deployment-agnostic, and is built for compatibility with other frameworks. ADK was designed to make agent development feel more like software development, to make it easier for developers to create, deploy, and orchestrate agentic architectures that range from simple tasks to complex workflows.
|
107
103
|
|
108
104
|
|
109
105
|
---
|
@@ -126,12 +122,27 @@ debugging, versioning, and deployment anywhere – from your laptop to the cloud
|
|
126
122
|
|
127
123
|
## 🚀 Installation
|
128
124
|
|
129
|
-
|
125
|
+
### Stable Release (Recommended)
|
126
|
+
|
127
|
+
You can install the latest stable version of ADK using `pip`:
|
130
128
|
|
131
129
|
```bash
|
132
130
|
pip install google-adk
|
133
131
|
```
|
134
132
|
|
133
|
+
The release cadence is weekly.
|
134
|
+
|
135
|
+
This version is recommended for most users as it represents the most recent official release.
|
136
|
+
|
137
|
+
### Development Version
|
138
|
+
Bug fixes and new features are merged into the main branch on GitHub first. If you need access to changes that haven't been included in an official PyPI release yet, you can install directly from the main branch:
|
139
|
+
|
140
|
+
```bash
|
141
|
+
pip install git+https://github.com/google/adk-python.git@main
|
142
|
+
```
|
143
|
+
|
144
|
+
Note: The development version is built directly from the latest code commits. While it includes the newest fixes and features, it may also contain experimental changes or bugs not present in the stable release. Use it primarily for testing upcoming changes or accessing critical fixes before they are officially released.
|
145
|
+
|
135
146
|
## 📚 Documentation
|
136
147
|
|
137
148
|
Explore the full documentation for detailed guides on building, evaluating, and
|
@@ -193,10 +204,18 @@ adk eval \
|
|
193
204
|
samples_for_testing/hello_world/hello_world_eval_set_001.evalset.json
|
194
205
|
```
|
195
206
|
|
207
|
+
## 🤖 A2A and ADK integration
|
208
|
+
|
209
|
+
For remote agent-to-agent communication, ADK integrates with the
|
210
|
+
[A2A protocol](https://github.com/google/A2A/).
|
211
|
+
See this [example](https://github.com/google/A2A/tree/main/samples/python/agents/google_adk)
|
212
|
+
for how they can work together.
|
196
213
|
|
197
214
|
## 🤝 Contributing
|
198
215
|
|
199
|
-
We welcome contributions from the community! Whether it's bug reports, feature requests, documentation improvements, or code contributions, please see our
|
216
|
+
We welcome contributions from the community! Whether it's bug reports, feature requests, documentation improvements, or code contributions, please see our
|
217
|
+
- [General contribution guideline and flow](https://google.github.io/adk-docs/contributing-guide/#questions).
|
218
|
+
- Then if you want to contribute code, please read [Code Contributing Guidelines](./CONTRIBUTING.md) to get started.
|
200
219
|
|
201
220
|
## 📄 License
|
202
221
|
|