From 6c9639969d735ae3f2b5e4320e536cea56cf01b5 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 5d42730937..374b435457 100644 --- a/src/google/adk/agents/remote_a2a_agent.py +++ b/src/google/adk/agents/remote_a2a_agent.py @@ -385,7 +385,7 @@ def _construct_message_parts_from_session( if not event or not event.content or not event.content.parts: continue - for part in event.content.parts: + for part in reversed(event.content.parts): converted_parts = self._genai_part_converter(part) if not isinstance(converted_parts, list): converted_parts = [converted_parts] if converted_parts else [] diff --git a/tests/unittests/agents/test_remote_a2a_agent.py b/tests/unittests/agents/test_remote_a2a_agent.py index fd722abf3f..65370554f0 100644 --- a/tests/unittests/agents/test_remote_a2a_agent.py +++ b/tests/unittests/agents/test_remote_a2a_agent.py @@ -729,6 +729,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-streaming task and no update."""