Skip to content

Commit fe08a9f

Browse files
enjoykumawatharanrk
authored andcommitted
fix: map A2A Message.role to correct GenAI content role
Merge #5198 ## Summary Fixes #5186 — `convert_a2a_message_to_event()` hard-codes `role="model"` for all resulting ADK events, regardless of the original A2A `Message.role`. User messages (`Role.user`) are incorrectly restored as model events. **Root cause:** `_create_event()` always creates `genai_types.Content(role="model", ...)` without considering the source message's role. **Fix:** - Add `_a2a_role_to_content_role()` helper that maps `A2ARole.user` → `"user"` and `A2ARole.agent` → `"model"` - Add `content_role` parameter to `_create_event()` (defaults to `"model"` for backward compatibility) - Pass the mapped role from `convert_a2a_message_to_event()` through to `_create_event()` ## Changes - `src/google/adk/a2a/converters/to_adk_event.py`: Import `Role`, add role mapping helper, thread `content_role` through `_create_event()` - `tests/unittests/a2a/converters/test_to_adk.py`: Add 2 tests verifying user→"user" and agent→"model" role mapping ## Test plan - [x] All 16 `test_to_adk.py` tests pass (14 existing + 2 new) - [x] New tests verify: `Role.user` → `content.role == "user"`, `Role.agent` → `content.role == "model"` - [x] No regressions in existing converter tests Co-authored-by: Haran Rajkumar <haranrk@google.com> COPYBARA_INTEGRATE_REVIEW=#5198 from enjoykumawat:fix/a2a-message-role-mapping da6826d PiperOrigin-RevId: 933926812
1 parent 796964a commit fe08a9f

2 files changed

Lines changed: 51 additions & 1 deletion

File tree

src/google/adk/a2a/converters/to_adk_event.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
from a2a.types import Message
2626
from a2a.types import Part as A2APart
27+
from a2a.types import Role as A2ARole
2728
from a2a.types import Task
2829
from a2a.types import TaskArtifactUpdateEvent
2930
from a2a.types import TaskState
@@ -187,6 +188,7 @@ def _create_event(
187188
actions: Optional[EventActions] = None,
188189
long_running_function_ids: Optional[set[str]] = None,
189190
partial: bool = False,
191+
content_role: str = "model",
190192
) -> Optional[Event]:
191193
"""Creates an ADK event from parts and metadata."""
192194
event_actions = actions or EventActions()
@@ -209,7 +211,7 @@ def _create_event(
209211
),
210212
content=(
211213
genai_types.Content(
212-
role="model",
214+
role=content_role,
213215
parts=output_parts,
214216
)
215217
if output_parts
@@ -221,6 +223,13 @@ def _create_event(
221223
return event
222224

223225

226+
def _a2a_role_to_content_role(role: Optional[A2ARole]) -> str:
227+
"""Maps an A2A Role to the corresponding GenAI content role."""
228+
if role == A2ARole.user:
229+
return "user"
230+
return "model"
231+
232+
224233
def _parse_adk_metadata_value(value: Any) -> Any:
225234
"""Parses ADK metadata values serialized through A2A."""
226235
if not isinstance(value, str):
@@ -468,11 +477,13 @@ def convert_a2a_message_to_event(
468477
output_parts, _ = _convert_a2a_parts_to_adk_parts(
469478
a2a_message.parts, part_converter
470479
)
480+
content_role = _a2a_role_to_content_role(getattr(a2a_message, "role", None))
471481
return _create_event(
472482
output_parts,
473483
invocation_context,
474484
author,
475485
_extract_event_actions(a2a_message.metadata),
486+
content_role=content_role,
476487
)
477488

478489
except Exception as e:

tests/unittests/a2a/converters/test_to_adk.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from a2a.types import Artifact
2121
from a2a.types import Message
2222
from a2a.types import Part as A2APart
23+
from a2a.types import Role as A2ARole
2324
from a2a.types import Task
2425
from a2a.types import TaskArtifactUpdateEvent
2526
from a2a.types import TaskState
@@ -643,3 +644,41 @@ def test_convert_a2a_artifact_update_to_event_none(self):
643644
"""Test convert_a2a_artifact_update_to_event with None."""
644645
with pytest.raises(ValueError, match="A2A artifact update cannot be None"):
645646
convert_a2a_artifact_update_to_event(None)
647+
648+
def test_convert_a2a_message_to_event_user_role(self) -> None:
649+
"""Test that A2A user role maps to GenAI content role 'user'."""
650+
a2a_part = Mock(spec=A2APart)
651+
a2a_part.root = Mock(spec=TextPart)
652+
a2a_part.root.metadata = {}
653+
message = Message(message_id="msg-1", role=A2ARole.user, parts=[a2a_part])
654+
655+
mock_genai_part = genai_types.Part.from_text(text="hello from user")
656+
mock_part_converter = Mock(return_value=[mock_genai_part])
657+
658+
event = convert_a2a_message_to_event(
659+
message,
660+
author="user",
661+
invocation_context=self.mock_context,
662+
part_converter=mock_part_converter,
663+
)
664+
665+
assert event.content.role == "user"
666+
667+
def test_convert_a2a_message_to_event_agent_role(self) -> None:
668+
"""Test that A2A agent role maps to GenAI content role 'model'."""
669+
a2a_part = Mock(spec=A2APart)
670+
a2a_part.root = Mock(spec=TextPart)
671+
a2a_part.root.metadata = {}
672+
message = Message(message_id="msg-1", role=A2ARole.agent, parts=[a2a_part])
673+
674+
mock_genai_part = genai_types.Part.from_text(text="hello from agent")
675+
mock_part_converter = Mock(return_value=[mock_genai_part])
676+
677+
event = convert_a2a_message_to_event(
678+
message,
679+
author="test-agent",
680+
invocation_context=self.mock_context,
681+
part_converter=mock_part_converter,
682+
)
683+
684+
assert event.content.role == "model"

0 commit comments

Comments
 (0)