From d0b3b5d857d8105c689bd64204e367102a67eded Mon Sep 17 00:00:00 2001 From: "Xiang (Sean) Zhou" Date: Mon, 11 Aug 2025 15:08:29 -0700 Subject: [PATCH] chore: add experimental messages for a2a related API PiperOrigin-RevId: 793812544 --- .../adk/a2a/converters/event_converter.py | 8 +-- .../adk/a2a/converters/part_converter.py | 6 +-- .../adk/a2a/converters/request_converter.py | 4 +- .../adk/a2a/executor/a2a_agent_executor.py | 6 +-- .../a2a/executor/task_result_aggregator.py | 4 +- src/google/adk/a2a/experimental.py | 54 +++++++++++++++++++ .../adk/a2a/utils/agent_card_builder.py | 4 +- src/google/adk/agents/remote_a2a_agent.py | 8 +-- 8 files changed, 74 insertions(+), 20 deletions(-) create mode 100644 src/google/adk/a2a/experimental.py diff --git a/src/google/adk/a2a/converters/event_converter.py b/src/google/adk/a2a/converters/event_converter.py index e83a4e99..43e5e1a0 100644 --- a/src/google/adk/a2a/converters/event_converter.py +++ b/src/google/adk/a2a/converters/event_converter.py @@ -38,7 +38,7 @@ from google.genai import types as genai_types from ...agents.invocation_context import InvocationContext from ...events.event import Event from ...flows.llm_flows.functions import REQUEST_EUC_FUNCTION_CALL_NAME -from ...utils.feature_decorator import experimental +from ..experimental import a2a_experimental from .part_converter import A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY from .part_converter import A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL from .part_converter import A2A_DATA_PART_METADATA_TYPE_KEY @@ -224,7 +224,7 @@ def convert_a2a_task_to_event( raise -@experimental +@a2a_experimental def convert_a2a_message_to_event( a2a_message: Message, author: Optional[str] = None, @@ -320,7 +320,7 @@ def convert_a2a_message_to_event( raise RuntimeError(f"Failed to convert message: {e}") from e -@experimental +@a2a_experimental def convert_event_to_a2a_message( event: Event, invocation_context: InvocationContext, role: Role = Role.agent ) -> Optional[Message]: @@ -471,7 +471,7 @@ def _create_status_update_event( ) -@experimental +@a2a_experimental def convert_event_to_a2a_events( event: Event, invocation_context: InvocationContext, diff --git a/src/google/adk/a2a/converters/part_converter.py b/src/google/adk/a2a/converters/part_converter.py index dc353209..b4ad20fe 100644 --- a/src/google/adk/a2a/converters/part_converter.py +++ b/src/google/adk/a2a/converters/part_converter.py @@ -39,7 +39,7 @@ except ImportError as e: from google.genai import types as genai_types -from ...utils.feature_decorator import experimental +from ..experimental import a2a_experimental logger = logging.getLogger('google_adk.' + __name__) @@ -51,7 +51,7 @@ A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT = 'code_execution_result' A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE = 'executable_code' -@experimental +@a2a_experimental def convert_a2a_part_to_genai_part( a2a_part: a2a_types.Part, ) -> Optional[genai_types.Part]: @@ -140,7 +140,7 @@ def convert_a2a_part_to_genai_part( return None -@experimental +@a2a_experimental def convert_genai_part_to_a2a_part( part: genai_types.Part, ) -> Optional[a2a_types.Part]: diff --git a/src/google/adk/a2a/converters/request_converter.py b/src/google/adk/a2a/converters/request_converter.py index 001b5c56..5f9a58c4 100644 --- a/src/google/adk/a2a/converters/request_converter.py +++ b/src/google/adk/a2a/converters/request_converter.py @@ -30,7 +30,7 @@ except ImportError as e: from google.genai import types as genai_types from ...runners import RunConfig -from ...utils.feature_decorator import experimental +from ..experimental import a2a_experimental from .part_converter import convert_a2a_part_to_genai_part @@ -47,7 +47,7 @@ def _get_user_id(request: RequestContext) -> str: return f'A2A_USER_{request.context_id}' -@experimental +@a2a_experimental def convert_a2a_request_to_adk_run_args( request: RequestContext, ) -> dict[str, Any]: diff --git a/src/google/adk/a2a/executor/a2a_agent_executor.py b/src/google/adk/a2a/executor/a2a_agent_executor.py index 831f21af..6cd34a98 100644 --- a/src/google/adk/a2a/executor/a2a_agent_executor.py +++ b/src/google/adk/a2a/executor/a2a_agent_executor.py @@ -50,23 +50,23 @@ from google.adk.runners import Runner from pydantic import BaseModel from typing_extensions import override -from ...utils.feature_decorator import experimental from ..converters.event_converter import convert_event_to_a2a_events from ..converters.request_converter import convert_a2a_request_to_adk_run_args from ..converters.utils import _get_adk_metadata_key +from ..experimental import a2a_experimental from .task_result_aggregator import TaskResultAggregator logger = logging.getLogger('google_adk.' + __name__) -@experimental +@a2a_experimental class A2aAgentExecutorConfig(BaseModel): """Configuration for the A2aAgentExecutor.""" pass -@experimental +@a2a_experimental class A2aAgentExecutor(AgentExecutor): """An AgentExecutor that runs an ADK Agent against an A2A request and publishes updates to an event queue. diff --git a/src/google/adk/a2a/executor/task_result_aggregator.py b/src/google/adk/a2a/executor/task_result_aggregator.py index 202e8092..632d1d45 100644 --- a/src/google/adk/a2a/executor/task_result_aggregator.py +++ b/src/google/adk/a2a/executor/task_result_aggregator.py @@ -19,10 +19,10 @@ from a2a.types import Message from a2a.types import TaskState from a2a.types import TaskStatusUpdateEvent -from ...utils.feature_decorator import experimental +from ..experimental import a2a_experimental -@experimental +@a2a_experimental class TaskResultAggregator: """Aggregates the task status updates and provides the final task state.""" diff --git a/src/google/adk/a2a/experimental.py b/src/google/adk/a2a/experimental.py new file mode 100644 index 00000000..ef89fd89 --- /dev/null +++ b/src/google/adk/a2a/experimental.py @@ -0,0 +1,54 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""A2A specific experimental decorator with custom warning message.""" + +from __future__ import annotations + +from google.adk.utils.feature_decorator import _make_feature_decorator + +a2a_experimental = _make_feature_decorator( + label="EXPERIMENTAL", + default_message=( + "ADK Implementation for A2A support (A2aAgentExecutor, RemoteA2aAgent " + "and corresponding supporting components etc.) is in experimental mode " + "and is subjected to breaking changes. A2A protocol and SDK are" + "themselves not experimental. Once it's stable enough the experimental " + "mode will be removed. Your feedback is welcome." + ), +) +"""Mark a class or function as experimental A2A feature. + +This decorator shows a specific warning message for A2A functionality, +indicating that the API is experimental and subject to breaking changes. + +Sample usage: + +``` +# Use with default A2A experimental message +@a2a_experimental +class A2AExperimentalClass: + pass + +# Use with custom message (overrides default A2A message) +@a2a_experimental("Custom A2A experimental message.") +def a2a_experimental_function(): + pass + +# Use with empty parentheses (same as default A2A message) +@a2a_experimental() +class AnotherA2AClass: + pass +``` +""" diff --git a/src/google/adk/a2a/utils/agent_card_builder.py b/src/google/adk/a2a/utils/agent_card_builder.py index 06e0d55e..bde56201 100644 --- a/src/google/adk/a2a/utils/agent_card_builder.py +++ b/src/google/adk/a2a/utils/agent_card_builder.py @@ -41,10 +41,10 @@ from ...agents.loop_agent import LoopAgent from ...agents.parallel_agent import ParallelAgent from ...agents.sequential_agent import SequentialAgent from ...tools.example_tool import ExampleTool -from ...utils.feature_decorator import experimental +from ..experimental import a2a_experimental -@experimental +@a2a_experimental class AgentCardBuilder: """Builder class for creating agent cards from ADK agents. diff --git a/src/google/adk/agents/remote_a2a_agent.py b/src/google/adk/agents/remote_a2a_agent.py index cc9fb75a..9ffd3c9a 100644 --- a/src/google/adk/agents/remote_a2a_agent.py +++ b/src/google/adk/agents/remote_a2a_agent.py @@ -58,6 +58,7 @@ from ..a2a.converters.event_converter import convert_a2a_message_to_event from ..a2a.converters.event_converter import convert_a2a_task_to_event from ..a2a.converters.event_converter import convert_event_to_a2a_message from ..a2a.converters.part_converter import convert_genai_part_to_a2a_part +from ..a2a.experimental import a2a_experimental from ..a2a.logs.log_utils import build_a2a_request_log from ..a2a.logs.log_utils import build_a2a_response_log from ..agents.invocation_context import InvocationContext @@ -65,7 +66,6 @@ from ..events.event import Event from ..flows.llm_flows.contents import _convert_foreign_event from ..flows.llm_flows.contents import _is_other_agent_reply from ..flows.llm_flows.functions import find_matching_function_call -from ..utils.feature_decorator import experimental from .base_agent import BaseAgent __all__ = [ @@ -83,21 +83,21 @@ DEFAULT_TIMEOUT = 600.0 logger = logging.getLogger("google_adk." + __name__) -@experimental +@a2a_experimental class AgentCardResolutionError(Exception): """Raised when agent card resolution fails.""" pass -@experimental +@a2a_experimental class A2AClientError(Exception): """Raised when A2A client operations fail.""" pass -@experimental +@a2a_experimental class RemoteA2aAgent(BaseAgent): """Agent that communicates with a remote A2A agent via A2A client.