From a7f01bcfeadb740f4a7f880aea43b5ee12d992b8 Mon Sep 17 00:00:00 2001 From: Eran Cohen Date: Thu, 23 Oct 2025 14:20:08 +0300 Subject: [PATCH] Fix: Correct message part ordering in A2A history The overall logic processes events in reverse chronological order and then reverses the entire list of collected parts at the end to restore the correct sequence of messages. However, the parts *within* a single message were being appended in their original forward order. This resulted in their sequence being incorrectly inverted by the final list reversal. This change fixes the issue by iterating over an event's parts in reverse. This pre-reversal ensures that after the final list-wide reversal, the internal order of parts within each message is correctly maintained. Signed-off-by: Eran Cohen --- src/google/adk/agents/remote_a2a_agent.py | 2 +- .../unittests/agents/test_remote_a2a_agent.py | 63 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/google/adk/agents/remote_a2a_agent.py b/src/google/adk/agents/remote_a2a_agent.py index bee8904c7e..e29e8a57ff 100644 --- a/src/google/adk/agents/remote_a2a_agent.py +++ b/src/google/adk/agents/remote_a2a_agent.py @@ -370,7 +370,7 @@ def _construct_message_parts_from_session( if not event.content or not event.content.parts: continue - for part in event.content.parts: + for part in reversed(event.content.parts): converted_part = self._genai_part_converter(part) if converted_part: diff --git a/tests/unittests/agents/test_remote_a2a_agent.py b/tests/unittests/agents/test_remote_a2a_agent.py index 597b5f1b51..3e75860edd 100644 --- a/tests/unittests/agents/test_remote_a2a_agent.py +++ b/tests/unittests/agents/test_remote_a2a_agent.py @@ -695,6 +695,69 @@ async def test_handle_a2a_response_success_with_message(self): assert result.custom_metadata is not None assert A2A_METADATA_PREFIX + "context_id" in result.custom_metadata + + def test_construct_message_parts_from_session_preserves_order(self): + """Test that message parts are in correct order with multi-part messages. + + This test verifies the fix for the bug where _present_other_agent_message + creates multi-part messages with "For context:" prefix, and ensures the + parts are in the correct chronological order (not reversed). + """ + # Create mock events with multiple parts + # Event 1: User message + user_part = Mock() + user_part.text = "User question" + user_content = Mock() + user_content.parts = [user_part] + user_event = Mock() + user_event.content = user_content + user_event.author = "user" + + # Event 2: Other agent message (will be transformed by + # _present_other_agent_message) + other_agent_part1 = Mock() + other_agent_part1.text = "For context:" + other_agent_part2 = Mock() + other_agent_part2.text = "[other_agent] said: Response text" + other_agent_content = Mock() + other_agent_content.parts = [other_agent_part1, other_agent_part2] + other_agent_event = Mock() + other_agent_event.content = other_agent_content + other_agent_event.author = "other_agent" + + self.mock_session.events = [user_event, other_agent_event] + + with patch( + "google.adk.agents.remote_a2a_agent._present_other_agent_message" + ) as mock_present: + # Mock _present_other_agent_message to return the transformed event + mock_present.return_value = other_agent_event + + # Mock the converter to track the order of parts + converted_parts = [] + + def mock_converter(part): + mock_a2a_part = Mock() + mock_a2a_part.original_text = part.text + converted_parts.append(mock_a2a_part) + return mock_a2a_part + + self.mock_genai_part_converter.side_effect = mock_converter + + result = self.agent._construct_message_parts_from_session(self.mock_context) + + # Verify the parts are in correct order + assert len(result) == 2 # Returns tuple of (parts, context_id) + assert len(result[0]) == 3 # 1 user part + 2 other agent parts + assert result[1] is None # context_id + + # Verify order: user part, then "For context:", then agent message + assert converted_parts[0].original_text == "User question" + assert converted_parts[1].original_text == "For context:" + assert ( + converted_parts[2].original_text == "[other_agent] said: Response text" + ) + @pytest.mark.asyncio async def test_handle_a2a_response_with_task_completed_and_no_update(self): """Test successful A2A response handling with non-streeaming task and no update."""