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.
Files changed (46) hide show
  1. google/adk/agents/base_agent.py +7 -7
  2. google/adk/agents/callback_context.py +0 -1
  3. google/adk/agents/llm_agent.py +3 -8
  4. google/adk/auth/auth_credential.py +2 -1
  5. google/adk/auth/auth_handler.py +7 -3
  6. google/adk/cli/browser/index.html +1 -1
  7. google/adk/cli/browser/{main-ZBO76GRM.js → main-HWIBUY2R.js} +69 -53
  8. google/adk/cli/cli.py +54 -47
  9. google/adk/cli/cli_deploy.py +6 -1
  10. google/adk/cli/cli_eval.py +1 -1
  11. google/adk/cli/cli_tools_click.py +78 -13
  12. google/adk/cli/fast_api.py +6 -0
  13. google/adk/evaluation/agent_evaluator.py +2 -2
  14. google/adk/evaluation/response_evaluator.py +2 -2
  15. google/adk/evaluation/trajectory_evaluator.py +4 -5
  16. google/adk/events/event_actions.py +9 -4
  17. google/adk/flows/llm_flows/agent_transfer.py +1 -1
  18. google/adk/flows/llm_flows/base_llm_flow.py +1 -1
  19. google/adk/flows/llm_flows/contents.py +10 -6
  20. google/adk/flows/llm_flows/functions.py +38 -18
  21. google/adk/flows/llm_flows/instructions.py +2 -2
  22. google/adk/models/gemini_llm_connection.py +2 -2
  23. google/adk/models/llm_response.py +10 -1
  24. google/adk/planners/built_in_planner.py +1 -0
  25. google/adk/sessions/_session_util.py +29 -0
  26. google/adk/sessions/database_session_service.py +60 -43
  27. google/adk/sessions/state.py +1 -1
  28. google/adk/sessions/vertex_ai_session_service.py +7 -5
  29. google/adk/tools/agent_tool.py +2 -3
  30. google/adk/tools/application_integration_tool/__init__.py +2 -0
  31. google/adk/tools/application_integration_tool/application_integration_toolset.py +48 -26
  32. google/adk/tools/application_integration_tool/clients/connections_client.py +26 -54
  33. google/adk/tools/application_integration_tool/integration_connector_tool.py +159 -0
  34. google/adk/tools/function_tool.py +42 -0
  35. google/adk/tools/google_api_tool/google_api_tool_set.py +12 -9
  36. google/adk/tools/load_artifacts_tool.py +1 -1
  37. google/adk/tools/openapi_tool/auth/credential_exchangers/oauth2_exchanger.py +4 -4
  38. google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +1 -1
  39. google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +5 -12
  40. google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +46 -8
  41. google/adk/version.py +1 -1
  42. {google_adk-0.2.0.dist-info → google_adk-0.4.0.dist-info}/METADATA +28 -9
  43. {google_adk-0.2.0.dist-info → google_adk-0.4.0.dist-info}/RECORD +46 -44
  44. {google_adk-0.2.0.dist-info → google_adk-0.4.0.dist-info}/WHEEL +0 -0
  45. {google_adk-0.2.0.dist-info → google_adk-0.4.0.dist-info}/entry_points.txt +0 -0
  46. {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: Dict[str, Any] = None,
50
- scopes: list[str] = None,
51
- ) -> Optional[OpenAPIToolset]:
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
- cl: Type['GoogleApiToolSet'],
96
+ cls: Type[GoogleApiToolSet],
94
97
  api_name: str,
95
98
  api_version: str,
96
- ) -> 'GoogleApiToolSet':
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 cl(
104
- cl._load_tool_set_with_oidc_auth(
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
- # Attache the content of the artifacts if the model requests them.
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
- HTTO bearer token cannot be generated, return the origianl credential
69
+ HTTP bearer token cannot be generated, return the original credential.
70
70
  """
71
71
 
72
- if "access_token" not in auth_credential.oauth2.token:
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.token["access_token"]
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.token:
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 adictionary."""
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
- for prop_name, prop_details in schema.properties.items():
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
- if snake_case_key == "properties" and isinstance(value, dict):
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
@@ -13,4 +13,4 @@
13
13
  # limitations under the License.
14
14
 
15
15
  # version: date+base_cl
16
- __version__ = "0.2.0"
16
+ __version__ = "0.4.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-adk
3
- Version: 0.2.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.9.0
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 seeking fine-grained
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
- You can install the ADK using `pip`:
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 [**Contributing Guidelines**](./CONTRIBUTING.md) to get started.
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