mirror of
https://github.com/encounter/adk-python.git
synced 2026-07-09 18:19:28 -07:00
fix: Correct message part ordering in A2A history
Merges test case from https://github.com/google/adk-python/pull/3262 Fixes: #3260 Co-authored-by: Ayush Agrawal <ayushagra@google.com> PiperOrigin-RevId: 826119626
This commit is contained in:
committed by
Copybara-Service
parent
b0017aed44
commit
5eca72f9bf
@@ -356,29 +356,33 @@ class RemoteA2aAgent(BaseAgent):
|
||||
"""
|
||||
message_parts: list[A2APart] = []
|
||||
context_id = None
|
||||
|
||||
events_to_process = []
|
||||
for event in reversed(ctx.session.events):
|
||||
if _is_other_agent_reply(self.name, event):
|
||||
event = _present_other_agent_message(event)
|
||||
elif event.author == self.name:
|
||||
if event.author == self.name:
|
||||
# stop on content generated by current a2a agent given it should already
|
||||
# be in remote session
|
||||
if event.custom_metadata:
|
||||
metadata = event.custom_metadata
|
||||
context_id = metadata.get(A2A_METADATA_PREFIX + "context_id")
|
||||
break
|
||||
events_to_process.append(event)
|
||||
|
||||
for event in reversed(events_to_process):
|
||||
if _is_other_agent_reply(self.name, event):
|
||||
event = _present_other_agent_message(event)
|
||||
|
||||
if not event.content or not event.content.parts:
|
||||
continue
|
||||
|
||||
for part in event.content.parts:
|
||||
|
||||
converted_part = self._genai_part_converter(part)
|
||||
if converted_part:
|
||||
message_parts.append(converted_part)
|
||||
else:
|
||||
logger.warning("Failed to convert part to A2A format: %s", part)
|
||||
|
||||
return message_parts[::-1], context_id
|
||||
return message_parts, context_id
|
||||
|
||||
async def _handle_a2a_response(
|
||||
self, a2a_response: A2AClientEvent | A2AMessage, ctx: InvocationContext
|
||||
|
||||
@@ -735,6 +735,71 @@ class TestRemoteA2aAgentMessageHandling:
|
||||
assert A2A_METADATA_PREFIX + "task_id" in result.custom_metadata
|
||||
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_submitted_and_no_update(self):
|
||||
"""Test successful A2A response handling with streaming task and no update."""
|
||||
|
||||
Reference in New Issue
Block a user