diff --git a/src/google/adk/a2a/converters/event_converter.py b/src/google/adk/a2a/converters/event_converter.py index 43e5e1a0..5e941a78 100644 --- a/src/google/adk/a2a/converters/event_converter.py +++ b/src/google/adk/a2a/converters/event_converter.py @@ -42,8 +42,10 @@ 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 +from .part_converter import A2APartToGenAIPartConverter from .part_converter import convert_a2a_part_to_genai_part from .part_converter import convert_genai_part_to_a2a_part +from .part_converter import GenAIPartToA2APartConverter from .utils import _get_adk_metadata_key # Constants @@ -169,6 +171,7 @@ def convert_a2a_task_to_event( a2a_task: Task, author: Optional[str] = None, invocation_context: Optional[InvocationContext] = None, + part_converter: A2APartToGenAIPartConverter = convert_a2a_part_to_genai_part, ) -> Event: """Converts an A2A task to an ADK event. @@ -177,6 +180,7 @@ def convert_a2a_task_to_event( author: The author of the event. Defaults to "a2a agent" if not provided. invocation_context: The invocation context containing session information. If provided, the branch will be set from the context. + part_converter: The function to convert A2A part to GenAI part. Returns: An ADK Event object representing the converted task. @@ -203,7 +207,9 @@ def convert_a2a_task_to_event( # Convert message if available if message: try: - return convert_a2a_message_to_event(message, author, invocation_context) + return convert_a2a_message_to_event( + message, author, invocation_context, part_converter=part_converter + ) except Exception as e: logger.error("Failed to convert A2A task message to event: %s", e) raise RuntimeError(f"Failed to convert task message: {e}") from e @@ -229,6 +235,7 @@ def convert_a2a_message_to_event( a2a_message: Message, author: Optional[str] = None, invocation_context: Optional[InvocationContext] = None, + part_converter: A2APartToGenAIPartConverter = convert_a2a_part_to_genai_part, ) -> Event: """Converts an A2A message to an ADK event. @@ -237,6 +244,7 @@ def convert_a2a_message_to_event( author: The author of the event. Defaults to "a2a agent" if not provided. invocation_context: The invocation context containing session information. If provided, the branch will be set from the context. + part_converter: The function to convert A2A part to GenAI part. Returns: An ADK Event object with converted content and long-running tool metadata. @@ -269,7 +277,7 @@ def convert_a2a_message_to_event( for a2a_part in a2a_message.parts: try: - part = convert_a2a_part_to_genai_part(a2a_part) + part = part_converter(a2a_part) if part is None: logger.warning("Failed to convert A2A part, skipping: %s", a2a_part) continue @@ -322,13 +330,18 @@ def convert_a2a_message_to_event( @a2a_experimental def convert_event_to_a2a_message( - event: Event, invocation_context: InvocationContext, role: Role = Role.agent + event: Event, + invocation_context: InvocationContext, + role: Role = Role.agent, + part_converter: GenAIPartToA2APartConverter = convert_genai_part_to_a2a_part, ) -> Optional[Message]: """Converts an ADK event to an A2A message. Args: event: The ADK event to convert. invocation_context: The invocation context. + role: The role of the message. + part_converter: The function to convert GenAI part to A2A part. Returns: An A2A Message if the event has content, None otherwise. @@ -347,7 +360,7 @@ def convert_event_to_a2a_message( try: a2a_parts = [] for part in event.content.parts: - a2a_part = convert_genai_part_to_a2a_part(part) + a2a_part = part_converter(part) if a2a_part: a2a_parts.append(a2a_part) _process_long_running_tool(a2a_part, event) @@ -477,6 +490,7 @@ def convert_event_to_a2a_events( invocation_context: InvocationContext, task_id: Optional[str] = None, context_id: Optional[str] = None, + part_converter: GenAIPartToA2APartConverter = convert_genai_part_to_a2a_part, ) -> List[A2AEvent]: """Converts a GenAI event to a list of A2A events. @@ -485,6 +499,7 @@ def convert_event_to_a2a_events( invocation_context: The invocation context. task_id: Optional task ID to use for generated events. context_id: Optional Context ID to use for generated events. + part_converter: The function to convert GenAI part to A2A part. Returns: A list of A2A events representing the converted ADK event. @@ -509,7 +524,9 @@ def convert_event_to_a2a_events( a2a_events.append(error_event) # Handle regular message content - message = convert_event_to_a2a_message(event, invocation_context) + message = convert_event_to_a2a_message( + event, invocation_context, part_converter=part_converter + ) if message: running_event = _create_status_update_event( message, invocation_context, event, task_id, context_id diff --git a/src/google/adk/a2a/converters/part_converter.py b/src/google/adk/a2a/converters/part_converter.py index b4ad20fe..ee77fa0b 100644 --- a/src/google/adk/a2a/converters/part_converter.py +++ b/src/google/adk/a2a/converters/part_converter.py @@ -19,6 +19,7 @@ module containing utilities for conversion betwen A2A Part and Google GenAI Part from __future__ import annotations import base64 +from collections.abc import Callable import json import logging from typing import Optional @@ -51,6 +52,14 @@ A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT = 'code_execution_result' A2A_DATA_PART_METADATA_TYPE_EXECUTABLE_CODE = 'executable_code' +A2APartToGenAIPartConverter = Callable[ + [a2a_types.Part], Optional[genai_types.Part] +] +GenAIPartToA2APartConverter = Callable[ + [genai_types.Part], Optional[a2a_types.Part] +] + + @a2a_experimental def convert_a2a_part_to_genai_part( a2a_part: a2a_types.Part, diff --git a/src/google/adk/a2a/converters/request_converter.py b/src/google/adk/a2a/converters/request_converter.py index 5f9a58c4..78a6d78e 100644 --- a/src/google/adk/a2a/converters/request_converter.py +++ b/src/google/adk/a2a/converters/request_converter.py @@ -31,6 +31,7 @@ from google.genai import types as genai_types from ...runners import RunConfig from ..experimental import a2a_experimental +from .part_converter import A2APartToGenAIPartConverter from .part_converter import convert_a2a_part_to_genai_part @@ -50,6 +51,7 @@ def _get_user_id(request: RequestContext) -> str: @a2a_experimental def convert_a2a_request_to_adk_run_args( request: RequestContext, + part_converter: A2APartToGenAIPartConverter = convert_a2a_part_to_genai_part, ) -> dict[str, Any]: if not request.message: @@ -60,10 +62,7 @@ def convert_a2a_request_to_adk_run_args( 'session_id': request.context_id, 'new_message': genai_types.Content( role='user', - parts=[ - convert_a2a_part_to_genai_part(part) - for part in request.message.parts - ], + parts=[part_converter(part) for part in request.message.parts], ), 'run_config': RunConfig(), } diff --git a/src/google/adk/a2a/executor/a2a_agent_executor.py b/src/google/adk/a2a/executor/a2a_agent_executor.py index 29b681a8..4cb92843 100644 --- a/src/google/adk/a2a/executor/a2a_agent_executor.py +++ b/src/google/adk/a2a/executor/a2a_agent_executor.py @@ -53,6 +53,10 @@ from pydantic import BaseModel from typing_extensions import override from ..converters.event_converter import convert_event_to_a2a_events +from ..converters.part_converter import A2APartToGenAIPartConverter +from ..converters.part_converter import convert_a2a_part_to_genai_part +from ..converters.part_converter import convert_genai_part_to_a2a_part +from ..converters.part_converter import GenAIPartToA2APartConverter 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 @@ -65,12 +69,18 @@ logger = logging.getLogger('google_adk.' + __name__) class A2aAgentExecutorConfig(BaseModel): """Configuration for the A2aAgentExecutor.""" - pass + a2a_part_converter: A2APartToGenAIPartConverter = ( + convert_a2a_part_to_genai_part + ) + gen_ai_part_converter: GenAIPartToA2APartConverter = ( + convert_genai_part_to_a2a_part + ) @a2a_experimental class A2aAgentExecutor(AgentExecutor): """An AgentExecutor that runs an ADK Agent against an A2A request and + publishes updates to an event queue. """ @@ -82,7 +92,7 @@ class A2aAgentExecutor(AgentExecutor): ): super().__init__() self._runner = runner - self._config = config + self._config = config or A2aAgentExecutorConfig() async def _resolve_runner(self) -> Runner: """Resolve the runner, handling cases where it's a callable that returns a Runner.""" @@ -183,7 +193,9 @@ class A2aAgentExecutor(AgentExecutor): runner = await self._resolve_runner() # Convert the a2a request to ADK run args - run_args = convert_a2a_request_to_adk_run_args(context) + run_args = convert_a2a_request_to_adk_run_args( + context, self._config.a2a_part_converter + ) # ensure the session exists session = await self._prepare_session(context, run_args, runner) @@ -217,7 +229,11 @@ class A2aAgentExecutor(AgentExecutor): async with Aclosing(runner.run_async(**run_args)) as agen: async for adk_event in agen: for a2a_event in convert_event_to_a2a_events( - adk_event, invocation_context, context.task_id, context.context_id + adk_event, + invocation_context, + context.task_id, + context.context_id, + self._config.gen_ai_part_converter, ): task_result_aggregator.process_event(a2a_event) await event_queue.enqueue_event(a2a_event) diff --git a/src/google/adk/agents/remote_a2a_agent.py b/src/google/adk/agents/remote_a2a_agent.py index b2a0640e..9733bbdf 100644 --- a/src/google/adk/agents/remote_a2a_agent.py +++ b/src/google/adk/agents/remote_a2a_agent.py @@ -57,7 +57,10 @@ import httpx 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 A2APartToGenAIPartConverter +from ..a2a.converters.part_converter import convert_a2a_part_to_genai_part from ..a2a.converters.part_converter import convert_genai_part_to_a2a_part +from ..a2a.converters.part_converter import GenAIPartToA2APartConverter 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 @@ -120,6 +123,8 @@ class RemoteA2aAgent(BaseAgent): description: str = "", httpx_client: Optional[httpx.AsyncClient] = None, timeout: float = DEFAULT_TIMEOUT, + genai_part_converter: GenAIPartToA2APartConverter = convert_genai_part_to_a2a_part, + a2a_part_converter: A2APartToGenAIPartConverter = convert_a2a_part_to_genai_part, **kwargs: Any, ) -> None: """Initialize RemoteA2aAgent. @@ -149,6 +154,8 @@ class RemoteA2aAgent(BaseAgent): self._httpx_client_needs_cleanup = httpx_client is None self._timeout = timeout self._is_resolved = False + self._genai_part_converter = genai_part_converter + self._a2a_part_converter = a2a_part_converter # Validate and store agent card reference if isinstance(agent_card, AgentCard): @@ -298,7 +305,7 @@ class RemoteA2aAgent(BaseAgent): return None a2a_message = convert_event_to_a2a_message( - ctx.session.events[-1], ctx, Role.user + ctx.session.events[-1], ctx, Role.user, self._genai_part_converter ) if function_call_event.custom_metadata: a2a_message.task_id = ( @@ -355,7 +362,7 @@ class RemoteA2aAgent(BaseAgent): for part in event.content.parts: - converted_part = convert_genai_part_to_a2a_part(part) + converted_part = self._genai_part_converter(part) if converted_part: message_parts.append(converted_part) else: @@ -380,7 +387,10 @@ class RemoteA2aAgent(BaseAgent): if a2a_response.root.result: if isinstance(a2a_response.root.result, A2ATask): event = convert_a2a_task_to_event( - a2a_response.root.result, self.name, ctx + a2a_response.root.result, + self.name, + ctx, + self._a2a_part_converter, ) event.custom_metadata = event.custom_metadata or {} event.custom_metadata[A2A_METADATA_PREFIX + "task_id"] = ( @@ -389,7 +399,10 @@ class RemoteA2aAgent(BaseAgent): else: event = convert_a2a_message_to_event( - a2a_response.root.result, self.name, ctx + a2a_response.root.result, + self.name, + ctx, + self._a2a_part_converter, ) event.custom_metadata = event.custom_metadata or {} if a2a_response.root.result.task_id: diff --git a/tests/unittests/a2a/converters/test_event_converter.py b/tests/unittests/a2a/converters/test_event_converter.py index dae90c89..9f7cf613 100644 --- a/tests/unittests/a2a/converters/test_event_converter.py +++ b/tests/unittests/a2a/converters/test_event_converter.py @@ -652,6 +652,8 @@ class TestA2AToEventConverters: with patch( "google.adk.a2a.converters.event_converter.convert_a2a_message_to_event" ) as mock_convert_message: + from google.adk.a2a.converters.part_converter import convert_a2a_part_to_genai_part + mock_event = Mock(spec=Event) mock_convert_message.return_value = mock_event @@ -662,7 +664,10 @@ class TestA2AToEventConverters: assert result == mock_event # Should call convert_a2a_message_to_event with the status message mock_convert_message.assert_called_once_with( - mock_status.message, "test-author", self.mock_invocation_context + mock_status.message, + "test-author", + self.mock_invocation_context, + part_converter=convert_a2a_part_to_genai_part, ) def test_convert_a2a_task_to_event_with_history_message(self): @@ -680,6 +685,8 @@ class TestA2AToEventConverters: with patch( "google.adk.a2a.converters.event_converter.convert_a2a_message_to_event" ) as mock_convert_message: + from google.adk.a2a.converters.part_converter import convert_a2a_part_to_genai_part + mock_event = Mock(spec=Event) mock_event.invocation_id = "test-invocation-id" mock_convert_message.return_value = mock_event @@ -688,7 +695,10 @@ class TestA2AToEventConverters: # Verify the message converter was called with correct parameters mock_convert_message.assert_called_once_with( - mock_message, "test-author", None + mock_message, + "test-author", + None, + part_converter=convert_a2a_part_to_genai_part, ) assert result == mock_event @@ -761,10 +771,7 @@ class TestA2AToEventConverters: with pytest.raises(RuntimeError, match="Failed to convert task message"): convert_a2a_task_to_event(mock_task, "test-author") - @patch( - "google.adk.a2a.converters.event_converter.convert_a2a_part_to_genai_part" - ) - def test_convert_a2a_message_to_event_success(self, mock_convert_part): + def test_convert_a2a_message_to_event_success(self): """Test successful conversion of A2A message to event.""" from google.adk.a2a.converters.event_converter import convert_a2a_message_to_event from google.genai import types as genai_types @@ -772,13 +779,17 @@ class TestA2AToEventConverters: # Create mock parts and message with valid genai Part mock_a2a_part = Mock() mock_genai_part = genai_types.Part(text="test content") + mock_convert_part = Mock() mock_convert_part.return_value = mock_genai_part mock_message = Mock(spec=Message) mock_message.parts = [mock_a2a_part] result = convert_a2a_message_to_event( - mock_message, "test-author", self.mock_invocation_context + mock_message, + "test-author", + self.mock_invocation_context, + mock_convert_part, ) # Verify conversion was successful @@ -790,12 +801,7 @@ class TestA2AToEventConverters: assert result.content.parts[0].text == "test content" mock_convert_part.assert_called_once_with(mock_a2a_part) - @patch( - "google.adk.a2a.converters.event_converter.convert_a2a_part_to_genai_part" - ) - def test_convert_a2a_message_to_event_with_long_running_tools( - self, mock_convert_part - ): + def test_convert_a2a_message_to_event_with_long_running_tools(self): """Test conversion with long-running tools by mocking the entire flow.""" from google.adk.a2a.converters.event_converter import convert_a2a_message_to_event @@ -805,6 +811,7 @@ class TestA2AToEventConverters: mock_message.parts = [mock_a2a_part] # Mock the part conversion to return None to simulate long-running tool detection logic + mock_convert_part = Mock() mock_convert_part.return_value = None # Patch the long-running tool detection since the main logic is in the actual conversion @@ -812,7 +819,10 @@ class TestA2AToEventConverters: "google.adk.a2a.converters.event_converter.logger" ) as mock_logger: result = convert_a2a_message_to_event( - mock_message, "test-author", self.mock_invocation_context + mock_message, + "test-author", + self.mock_invocation_context, + mock_convert_part, ) # Verify basic conversion worked @@ -845,24 +855,23 @@ class TestA2AToEventConverters: with pytest.raises(ValueError, match="A2A message cannot be None"): convert_a2a_message_to_event(None) - @patch( - "google.adk.a2a.converters.event_converter.convert_a2a_part_to_genai_part" - ) - def test_convert_a2a_message_to_event_part_conversion_fails( - self, mock_convert_part - ): + def test_convert_a2a_message_to_event_part_conversion_fails(self): """Test handling when part conversion returns None.""" from google.adk.a2a.converters.event_converter import convert_a2a_message_to_event # Setup mock to return None (conversion failure) mock_a2a_part = Mock() + mock_convert_part = Mock() mock_convert_part.return_value = None mock_message = Mock(spec=Message) mock_message.parts = [mock_a2a_part] result = convert_a2a_message_to_event( - mock_message, "test-author", self.mock_invocation_context + mock_message, + "test-author", + self.mock_invocation_context, + mock_convert_part, ) # Verify event was created but with no parts @@ -871,12 +880,7 @@ class TestA2AToEventConverters: assert result.content.role == "model" assert len(result.content.parts) == 0 - @patch( - "google.adk.a2a.converters.event_converter.convert_a2a_part_to_genai_part" - ) - def test_convert_a2a_message_to_event_part_conversion_exception( - self, mock_convert_part - ): + def test_convert_a2a_message_to_event_part_conversion_exception(self): """Test handling when part conversion raises exception.""" from google.adk.a2a.converters.event_converter import convert_a2a_message_to_event from google.genai import types as genai_types @@ -886,6 +890,7 @@ class TestA2AToEventConverters: mock_a2a_part2 = Mock() mock_genai_part = genai_types.Part(text="successful conversion") + mock_convert_part = Mock() mock_convert_part.side_effect = [ Exception("Conversion failed"), # First part fails mock_genai_part, # Second part succeeds @@ -895,7 +900,10 @@ class TestA2AToEventConverters: mock_message.parts = [mock_a2a_part1, mock_a2a_part2] result = convert_a2a_message_to_event( - mock_message, "test-author", self.mock_invocation_context + mock_message, + "test-author", + self.mock_invocation_context, + mock_convert_part, ) # Verify event was created with only the successfully converted part @@ -905,12 +913,7 @@ class TestA2AToEventConverters: assert len(result.content.parts) == 1 assert result.content.parts[0].text == "successful conversion" - @patch( - "google.adk.a2a.converters.event_converter.convert_a2a_part_to_genai_part" - ) - def test_convert_a2a_message_to_event_missing_tool_id( - self, mock_convert_part - ): + def test_convert_a2a_message_to_event_missing_tool_id(self): """Test handling of message conversion when part conversion fails.""" from google.adk.a2a.converters.event_converter import convert_a2a_message_to_event @@ -920,10 +923,14 @@ class TestA2AToEventConverters: mock_message.parts = [mock_a2a_part] # Mock the part conversion to return None + mock_convert_part = Mock() mock_convert_part.return_value = None result = convert_a2a_message_to_event( - mock_message, "test-author", self.mock_invocation_context + mock_message, + "test-author", + self.mock_invocation_context, + mock_convert_part, ) # Verify basic conversion worked diff --git a/tests/unittests/a2a/converters/test_request_converter.py b/tests/unittests/a2a/converters/test_request_converter.py index 10a3ed03..115b2312 100644 --- a/tests/unittests/a2a/converters/test_request_converter.py +++ b/tests/unittests/a2a/converters/test_request_converter.py @@ -146,10 +146,7 @@ class TestGetUserId: class TestConvertA2aRequestToAdkRunArgs: """Test cases for convert_a2a_request_to_adk_run_args function.""" - @patch( - "google.adk.a2a.converters.request_converter.convert_a2a_part_to_genai_part" - ) - def test_convert_a2a_request_basic(self, mock_convert_part): + def test_convert_a2a_request_basic(self): """Test basic conversion of A2A request to ADK run args.""" # Arrange mock_part1 = Mock() @@ -172,10 +169,11 @@ class TestConvertA2aRequestToAdkRunArgs: # Create proper genai_types.Part objects instead of mocks mock_genai_part1 = genai_types.Part(text="test part 1") mock_genai_part2 = genai_types.Part(text="test part 2") + mock_convert_part = Mock() mock_convert_part.side_effect = [mock_genai_part1, mock_genai_part2] # Act - result = convert_a2a_request_to_adk_run_args(request) + result = convert_a2a_request_to_adk_run_args(request, mock_convert_part) # Assert assert result is not None @@ -201,14 +199,12 @@ class TestConvertA2aRequestToAdkRunArgs: with pytest.raises(ValueError, match="Request message cannot be None"): convert_a2a_request_to_adk_run_args(request) - @patch( - "google.adk.a2a.converters.request_converter.convert_a2a_part_to_genai_part" - ) - def test_convert_a2a_request_empty_parts(self, mock_convert_part): + def test_convert_a2a_request_empty_parts(self): """Test conversion with empty parts list.""" # Arrange mock_message = Mock() mock_message.parts = [] + mock_convert_part = Mock() request = Mock(spec=RequestContext) request.message = mock_message @@ -216,7 +212,7 @@ class TestConvertA2aRequestToAdkRunArgs: request.call_context = None # Act - result = convert_a2a_request_to_adk_run_args(request) + result = convert_a2a_request_to_adk_run_args(request, mock_convert_part) # Assert assert result is not None @@ -230,10 +226,7 @@ class TestConvertA2aRequestToAdkRunArgs: # Verify convert_part wasn't called mock_convert_part.assert_not_called() - @patch( - "google.adk.a2a.converters.request_converter.convert_a2a_part_to_genai_part" - ) - def test_convert_a2a_request_none_context_id(self, mock_convert_part): + def test_convert_a2a_request_none_context_id(self): """Test conversion when context_id is None.""" # Arrange mock_part = Mock() @@ -247,10 +240,11 @@ class TestConvertA2aRequestToAdkRunArgs: # Create proper genai_types.Part object instead of mock mock_genai_part = genai_types.Part(text="test part") + mock_convert_part = Mock() mock_convert_part.return_value = mock_genai_part # Act - result = convert_a2a_request_to_adk_run_args(request) + result = convert_a2a_request_to_adk_run_args(request, mock_convert_part) # Assert assert result is not None @@ -261,10 +255,7 @@ class TestConvertA2aRequestToAdkRunArgs: assert result["new_message"].parts == [mock_genai_part] assert isinstance(result["run_config"], RunConfig) - @patch( - "google.adk.a2a.converters.request_converter.convert_a2a_part_to_genai_part" - ) - def test_convert_a2a_request_no_auth(self, mock_convert_part): + def test_convert_a2a_request_no_auth(self): """Test conversion when no authentication is available.""" # Arrange mock_part = Mock() @@ -278,10 +269,11 @@ class TestConvertA2aRequestToAdkRunArgs: # Create proper genai_types.Part object instead of mock mock_genai_part = genai_types.Part(text="test part") + mock_convert_part = Mock() mock_convert_part.return_value = mock_genai_part # Act - result = convert_a2a_request_to_adk_run_args(request) + result = convert_a2a_request_to_adk_run_args(request, mock_convert_part) # Assert assert result is not None @@ -296,10 +288,7 @@ class TestConvertA2aRequestToAdkRunArgs: class TestIntegration: """Integration test cases combining both functions.""" - @patch( - "google.adk.a2a.converters.request_converter.convert_a2a_part_to_genai_part" - ) - def test_end_to_end_conversion_with_auth_user(self, mock_convert_part): + def test_end_to_end_conversion_with_auth_user(self): """Test end-to-end conversion with authenticated user.""" # Arrange mock_user = Mock() @@ -319,10 +308,11 @@ class TestIntegration: # Create proper genai_types.Part object instead of mock mock_genai_part = genai_types.Part(text="test part") + mock_convert_part = Mock() mock_convert_part.return_value = mock_genai_part # Act - result = convert_a2a_request_to_adk_run_args(request) + result = convert_a2a_request_to_adk_run_args(request, mock_convert_part) # Assert assert result is not None @@ -333,10 +323,7 @@ class TestIntegration: assert result["new_message"].parts == [mock_genai_part] assert isinstance(result["run_config"], RunConfig) - @patch( - "google.adk.a2a.converters.request_converter.convert_a2a_part_to_genai_part" - ) - def test_end_to_end_conversion_with_fallback_user(self, mock_convert_part): + def test_end_to_end_conversion_with_fallback_user(self): """Test end-to-end conversion with fallback user ID.""" # Arrange mock_part = Mock() @@ -350,10 +337,11 @@ class TestIntegration: # Create proper genai_types.Part object instead of mock mock_genai_part = genai_types.Part(text="test part") + mock_convert_part = Mock() mock_convert_part.return_value = mock_genai_part # Act - result = convert_a2a_request_to_adk_run_args(request) + result = convert_a2a_request_to_adk_run_args(request, mock_convert_part) # Assert assert result is not None diff --git a/tests/unittests/a2a/executor/test_a2a_agent_executor.py b/tests/unittests/a2a/executor/test_a2a_agent_executor.py index d1c9e288..11d6b3a7 100644 --- a/tests/unittests/a2a/executor/test_a2a_agent_executor.py +++ b/tests/unittests/a2a/executor/test_a2a_agent_executor.py @@ -56,7 +56,12 @@ class TestA2aAgentExecutor: self.mock_runner._new_invocation_context = Mock() self.mock_runner.run_async = AsyncMock() - self.mock_config = Mock(spec=A2aAgentExecutorConfig) + self.mock_a2a_part_converter = Mock() + self.mock_gen_ai_part_converter = Mock() + self.mock_config = A2aAgentExecutorConfig( + a2a_part_converter=self.mock_a2a_part_converter, + gen_ai_part_converter=self.mock_gen_ai_part_converter, + ) self.executor = A2aAgentExecutor( runner=self.mock_runner, config=self.mock_config ) diff --git a/tests/unittests/agents/test_remote_a2a_agent.py b/tests/unittests/agents/test_remote_a2a_agent.py index c6071bb4..dec8a09a 100644 --- a/tests/unittests/agents/test_remote_a2a_agent.py +++ b/tests/unittests/agents/test_remote_a2a_agent.py @@ -441,7 +441,14 @@ class TestRemoteA2aAgentMessageHandling: def setup_method(self): """Setup test fixtures.""" self.agent_card = create_test_agent_card() - self.agent = RemoteA2aAgent(name="test_agent", agent_card=self.agent_card) + self.mock_genai_part_converter = Mock() + self.mock_a2a_part_converter = Mock() + self.agent = RemoteA2aAgent( + name="test_agent", + agent_card=self.agent_card, + genai_part_converter=self.mock_genai_part_converter, + a2a_part_converter=self.mock_a2a_part_converter, + ) # Mock session and context self.mock_session = Mock(spec=Session) @@ -519,20 +526,17 @@ class TestRemoteA2aAgentMessageHandling: ) as mock_convert: mock_convert.return_value = mock_event - with patch( - "google.adk.agents.remote_a2a_agent.convert_genai_part_to_a2a_part" - ) as mock_convert_part: - mock_a2a_part = Mock() - mock_convert_part.return_value = mock_a2a_part + mock_a2a_part = Mock() + self.mock_genai_part_converter.return_value = mock_a2a_part - result = self.agent._construct_message_parts_from_session( - self.mock_context - ) + result = self.agent._construct_message_parts_from_session( + self.mock_context + ) - assert len(result) == 2 # Returns tuple of (parts, context_id) - assert len(result[0]) == 1 # parts list - assert result[0][0] == mock_a2a_part - assert result[1] is None # context_id + assert len(result) == 2 # Returns tuple of (parts, context_id) + assert len(result[0]) == 1 # parts list + assert result[0][0] == mock_a2a_part + assert result[1] is None # context_id def test_construct_message_parts_from_session_empty_events(self): """Test message parts construction with empty events.""" @@ -575,7 +579,10 @@ class TestRemoteA2aAgentMessageHandling: assert result == mock_event mock_convert.assert_called_once_with( - mock_a2a_message, self.agent.name, self.mock_context + mock_a2a_message, + self.agent.name, + self.mock_context, + self.mock_a2a_part_converter, ) # Check that metadata was added assert result.custom_metadata is not None @@ -613,7 +620,10 @@ class TestRemoteA2aAgentMessageHandling: assert result == mock_event mock_convert.assert_called_once_with( - mock_a2a_task, self.agent.name, self.mock_context + mock_a2a_task, + self.agent.name, + self.mock_context, + self.mock_a2a_part_converter, ) # Check that metadata was added assert result.custom_metadata is not None @@ -649,7 +659,14 @@ class TestRemoteA2aAgentExecution: def setup_method(self): """Setup test fixtures.""" self.agent_card = create_test_agent_card() - self.agent = RemoteA2aAgent(name="test_agent", agent_card=self.agent_card) + self.mock_genai_part_converter = Mock() + self.mock_a2a_part_converter = Mock() + self.agent = RemoteA2aAgent( + name="test_agent", + agent_card=self.agent_card, + genai_part_converter=self.mock_genai_part_converter, + a2a_part_converter=self.mock_a2a_part_converter, + ) # Mock session and context self.mock_session = Mock(spec=Session)