Skip to content

Commit a7f01bc

Browse files
committed
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 <[email protected]>
1 parent ba63176 commit a7f01bc

File tree

2 files changed

+64
-1
lines changed

2 files changed

+64
-1
lines changed

src/google/adk/agents/remote_a2a_agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ def _construct_message_parts_from_session(
370370
if not event.content or not event.content.parts:
371371
continue
372372

373-
for part in event.content.parts:
373+
for part in reversed(event.content.parts):
374374

375375
converted_part = self._genai_part_converter(part)
376376
if converted_part:

tests/unittests/agents/test_remote_a2a_agent.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,69 @@ async def test_handle_a2a_response_success_with_message(self):
695695
assert result.custom_metadata is not None
696696
assert A2A_METADATA_PREFIX + "context_id" in result.custom_metadata
697697

698+
699+
def test_construct_message_parts_from_session_preserves_order(self):
700+
"""Test that message parts are in correct order with multi-part messages.
701+
702+
This test verifies the fix for the bug where _present_other_agent_message
703+
creates multi-part messages with "For context:" prefix, and ensures the
704+
parts are in the correct chronological order (not reversed).
705+
"""
706+
# Create mock events with multiple parts
707+
# Event 1: User message
708+
user_part = Mock()
709+
user_part.text = "User question"
710+
user_content = Mock()
711+
user_content.parts = [user_part]
712+
user_event = Mock()
713+
user_event.content = user_content
714+
user_event.author = "user"
715+
716+
# Event 2: Other agent message (will be transformed by
717+
# _present_other_agent_message)
718+
other_agent_part1 = Mock()
719+
other_agent_part1.text = "For context:"
720+
other_agent_part2 = Mock()
721+
other_agent_part2.text = "[other_agent] said: Response text"
722+
other_agent_content = Mock()
723+
other_agent_content.parts = [other_agent_part1, other_agent_part2]
724+
other_agent_event = Mock()
725+
other_agent_event.content = other_agent_content
726+
other_agent_event.author = "other_agent"
727+
728+
self.mock_session.events = [user_event, other_agent_event]
729+
730+
with patch(
731+
"google.adk.agents.remote_a2a_agent._present_other_agent_message"
732+
) as mock_present:
733+
# Mock _present_other_agent_message to return the transformed event
734+
mock_present.return_value = other_agent_event
735+
736+
# Mock the converter to track the order of parts
737+
converted_parts = []
738+
739+
def mock_converter(part):
740+
mock_a2a_part = Mock()
741+
mock_a2a_part.original_text = part.text
742+
converted_parts.append(mock_a2a_part)
743+
return mock_a2a_part
744+
745+
self.mock_genai_part_converter.side_effect = mock_converter
746+
747+
result = self.agent._construct_message_parts_from_session(self.mock_context)
748+
749+
# Verify the parts are in correct order
750+
assert len(result) == 2 # Returns tuple of (parts, context_id)
751+
assert len(result[0]) == 3 # 1 user part + 2 other agent parts
752+
assert result[1] is None # context_id
753+
754+
# Verify order: user part, then "For context:", then agent message
755+
assert converted_parts[0].original_text == "User question"
756+
assert converted_parts[1].original_text == "For context:"
757+
assert (
758+
converted_parts[2].original_text == "[other_agent] said: Response text"
759+
)
760+
698761
@pytest.mark.asyncio
699762
async def test_handle_a2a_response_with_task_completed_and_no_update(self):
700763
"""Test successful A2A response handling with non-streeaming task and no update."""

0 commit comments

Comments
 (0)