Skip to content

fix(adk): return str from serialize_metadata_value for pydantic values#2173

Open
anxkhn wants to merge 1 commit into
kagent-dev:mainfrom
anxkhn:fix/adk-serialize-metadata-value-str
Open

fix(adk): return str from serialize_metadata_value for pydantic values#2173
anxkhn wants to merge 1 commit into
kagent-dev:mainfrom
anxkhn:fix/adk-serialize-metadata-value-str

Conversation

@anxkhn

@anxkhn anxkhn commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

What

serialize_metadata_value in python/packages/kagent-adk/src/kagent/adk/converters/event_converter.py
is declared def serialize_metadata_value(value: Any) -> str: and its docstring
promises to return the "String representation of the value." For any value that
has a model_dump attribute (that is, any Pydantic model) it did:

return value.model_dump(exclude_none=True, by_alias=True)

Pydantic's model_dump() returns a dict, not a string (the string form is
model_dump_json()), so the function violated both its -> str annotation and
its docstring for Pydantic inputs.

This is not purely cosmetic. The result is written into A2A message/event
metadata by _get_context_metadata, next to sibling values that are plain
strings (for example error_code is stored via str(...)). Real Pydantic
values flow through this branch: event.usage_metadata, event.grounding_metadata,
and event.custom_metadata. So a dict was being injected where the surrounding
metadata values are strings.

Fix

Wrap the model_dump(...) call in str() so the return matches the annotation
and the docstring:

return str(value.model_dump(exclude_none=True, by_alias=True))

This also makes the helper consistent with its twin in the langgraph package,
python/packages/kagent-langgraph/src/kagent/langgraph/_metadata_utils.py, which
already wraps model_dump(...) in str(...). The adk copy had simply dropped
the str() wrapper. No signature or API change; the except fallback already
returned str(value).

Tests

Added TestSerializeMetadataValue in
python/packages/kagent-adk/tests/unittests/converters/test_event_converter.py:

  • a Pydantic model serializes to a str equal to
    str(model.model_dump(exclude_none=True, by_alias=True)) (fails on the old
    code, which returned a dict; passes after the wrap), and
  • a plain value (42) serializes to "42".

cd python && uv run pytest packages/kagent-adk/tests/unittests/ -> 340 passed,
1 skipped. ruff format and ruff check are clean on the changed files.

serialize_metadata_value is annotated -> str and its docstring promises a
string representation, but for values with a model_dump attribute it returned
value.model_dump(...), which pydantic produces as a dict. The result is stored
into A2A message/event metadata alongside sibling values that are strings
(for example error_code is stringified explicitly), so a dict was injected
where a string was expected for usage_metadata, grounding_metadata, and
custom_metadata.

Wrap the model_dump call in str() so the return matches the annotation and
docstring. This mirrors the equivalent helper in the langgraph package, which
already wraps model_dump in str(). Add a regression test asserting a pydantic
value serializes to a str.

Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 7, 2026 08:00
@github-actions github-actions Bot added the bug Something isn't working label Jul 7, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes serialize_metadata_value in the ADK event converter so it consistently returns a str for Pydantic model inputs, aligning behavior with its type annotation/docstring and avoiding injecting dict objects into A2A event metadata.

Changes:

  • Wrap value.model_dump(...) with str(...) when serializing Pydantic values.
  • Add unit tests covering Pydantic-model and plain-value serialization behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
python/packages/kagent-adk/src/kagent/adk/converters/event_converter.py Ensures Pydantic model_dump() output is coerced to str to match function contract and metadata expectations.
python/packages/kagent-adk/tests/unittests/converters/test_event_converter.py Adds tests validating string output for both Pydantic and non-Pydantic inputs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 4 to 8
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

Comment on lines +145 to +147
"""A pydantic value must serialize to a str, matching the -> str
annotation and docstring, so metadata values stay strings like their
siblings (e.g. error_code, which is stringified explicitly)."""

@supreme-gg-gg supreme-gg-gg left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's nothing wrong with returning dict here. A2A metadata allows them and they are needed in the frontend to properly show things like token usage:

function getMessageTokenStats(metadata: Record<string, unknown> | undefined): TokenStats | undefined {
const usage = getMetadataValue<ADKMetadata["kagent_usage_metadata"]>(metadata, "usage_metadata");
if (!usage) return undefined;
return {
total: usage.totalTokenCount ?? 0,
prompt: usage.promptTokenCount ?? 0,
completion: usage.candidatesTokenCount ?? 0,
};
}

A better fix would be to update the type annotation to str | dict[str, Any] and the docstring.

If you trace the langgraph example you cited, you will find that even though serialize_metadata_value does return string it is in fact never used in the converter with any metadata, so it's effectively dead code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants