Skip to content

Commit 6c96399

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 <eranco@redhat.com>
1 parent f283027 commit 6c96399

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
@@ -385,7 +385,7 @@ def _construct_message_parts_from_session(
385385
if not event or not event.content or not event.content.parts:
386386
continue
387387

388-
for part in event.content.parts:
388+
for part in reversed(event.content.parts):
389389
converted_parts = self._genai_part_converter(part)
390390
if not isinstance(converted_parts, list):
391391
converted_parts = [converted_parts] if converted_parts else []

tests/unittests/agents/test_remote_a2a_agent.py

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

732+
733+
def test_construct_message_parts_from_session_preserves_order(self):
734+
"""Test that message parts are in correct order with multi-part messages.
735+
736+
This test verifies the fix for the bug where _present_other_agent_message
737+
creates multi-part messages with "For context:" prefix, and ensures the
738+
parts are in the correct chronological order (not reversed).
739+
"""
740+
# Create mock events with multiple parts
741+
# Event 1: User message
742+
user_part = Mock()
743+
user_part.text = "User question"
744+
user_content = Mock()
745+
user_content.parts = [user_part]
746+
user_event = Mock()
747+
user_event.content = user_content
748+
user_event.author = "user"
749+
750+
# Event 2: Other agent message (will be transformed by
751+
# _present_other_agent_message)
752+
other_agent_part1 = Mock()
753+
other_agent_part1.text = "For context:"
754+
other_agent_part2 = Mock()
755+
other_agent_part2.text = "[other_agent] said: Response text"
756+
other_agent_content = Mock()
757+
other_agent_content.parts = [other_agent_part1, other_agent_part2]
758+
other_agent_event = Mock()
759+
other_agent_event.content = other_agent_content
760+
other_agent_event.author = "other_agent"
761+
762+
self.mock_session.events = [user_event, other_agent_event]
763+
764+
with patch(
765+
"google.adk.agents.remote_a2a_agent._present_other_agent_message"
766+
) as mock_present:
767+
# Mock _present_other_agent_message to return the transformed event
768+
mock_present.return_value = other_agent_event
769+
770+
# Mock the converter to track the order of parts
771+
converted_parts = []
772+
773+
def mock_converter(part):
774+
mock_a2a_part = Mock()
775+
mock_a2a_part.original_text = part.text
776+
converted_parts.append(mock_a2a_part)
777+
return mock_a2a_part
778+
779+
self.mock_genai_part_converter.side_effect = mock_converter
780+
781+
result = self.agent._construct_message_parts_from_session(self.mock_context)
782+
783+
# Verify the parts are in correct order
784+
assert len(result) == 2 # Returns tuple of (parts, context_id)
785+
assert len(result[0]) == 3 # 1 user part + 2 other agent parts
786+
assert result[1] is None # context_id
787+
788+
# Verify order: user part, then "For context:", then agent message
789+
assert converted_parts[0].original_text == "User question"
790+
assert converted_parts[1].original_text == "For context:"
791+
assert (
792+
converted_parts[2].original_text == "[other_agent] said: Response text"
793+
)
794+
732795
@pytest.mark.asyncio
733796
async def test_handle_a2a_response_with_task_completed_and_no_update(self):
734797
"""Test successful A2A response handling with non-streaming task and no update."""

0 commit comments

Comments
 (0)