diff --git a/python/packages/kagent-adk/src/kagent/adk/converters/event_converter.py b/python/packages/kagent-adk/src/kagent/adk/converters/event_converter.py index fadd1e574..68341b926 100644 --- a/python/packages/kagent-adk/src/kagent/adk/converters/event_converter.py +++ b/python/packages/kagent-adk/src/kagent/adk/converters/event_converter.py @@ -32,18 +32,25 @@ logger = logging.getLogger("kagent_adk." + __name__) -def serialize_metadata_value(value: Any) -> str: - """Safely serializes metadata values to string format. +def serialize_metadata_value(value: Any) -> str | dict[str, Any]: + """Safely serializes a metadata value for A2A message/event metadata. + + Pydantic values (anything with ``model_dump``) are returned as their + JSON-compatible serialized ``dict`` so structured metadata such as + ``usage_metadata`` stays machine-readable for consumers (for example the UI + reads the token counts as object fields). Everything else is returned as its + string representation. Args: value: The value to serialize. Returns: - String representation of the value. + The value's JSON-compatible ``model_dump`` dict for Pydantic values, + otherwise its string representation. """ if hasattr(value, "model_dump"): try: - return value.model_dump(exclude_none=True, by_alias=True) + return value.model_dump(mode="json", exclude_none=True, by_alias=True) except Exception as e: logger.warning("Failed to serialize metadata value: %s", e) return str(value) diff --git a/python/packages/kagent-adk/tests/unittests/converters/test_event_converter.py b/python/packages/kagent-adk/tests/unittests/converters/test_event_converter.py index 62611648c..7292f2291 100644 --- a/python/packages/kagent-adk/tests/unittests/converters/test_event_converter.py +++ b/python/packages/kagent-adk/tests/unittests/converters/test_event_converter.py @@ -1,11 +1,13 @@ +from enum import Enum from unittest.mock import Mock import pytest from a2a.types import TaskState, TaskStatusUpdateEvent from google.genai import types as genai_types from kagent.core.a2a import get_kagent_metadata_key +from pydantic import BaseModel, Field -from kagent.adk.converters.event_converter import convert_event_to_a2a_events +from kagent.adk.converters.event_converter import convert_event_to_a2a_events, serialize_metadata_value def _create_mock_invocation_context(): @@ -135,3 +137,30 @@ def test_message_carries_task_and_context_ids(self): assert message is not None assert message.task_id == "task-xyz" assert message.context_id == "ctx-xyz" + + +class TestSerializeMetadataValue: + """Test cases for serialize_metadata_value.""" + + def test_pydantic_value_serializes_to_dump_dict(self): + """A Pydantic value serializes to its JSON-compatible model_dump dict so + structured metadata (e.g. usage_metadata token counts) stays + machine-readable for consumers such as the UI.""" + + class _Status(Enum): + READY = "ready" + + class _Model(BaseModel): + a: int + b: str | None = None + status: _Status + alias_value: str = Field(alias="aliasValue") + + value = _Model(a=1, status=_Status.READY, aliasValue="x") + + result = serialize_metadata_value(value) + + assert result == {"a": 1, "status": "ready", "aliasValue": "x"} + + def test_plain_value_serializes_to_str(self): + assert serialize_metadata_value(42) == "42"