Skip to content

_safe_json_serialize drops Pydantic model tool results as '<not serializable>' in traces #4629

@aperepel

Description

@aperepel

Description

_safe_json_serialize in src/google/adk/telemetry/tracing.py silently replaces Pydantic BaseModel instances with the string '<not serializable>' when serializing tool responses for tracing spans.

This affects any tool that returns a Pydantic model rather than a plain dict — the tool executes correctly, but the traced output is lost.

Root Cause

The serializer uses:

json.dumps(obj, ensure_ascii=False, default=lambda o: '<not serializable>')

When a tool returns a Pydantic model, ADK wraps it as {'result': <BaseModel>} in __build_response_event (functions.py line 798-799). json.dumps can't serialize BaseModel natively, so the default lambda fires and replaces it.

Reproduction

import json
import asyncio
from pydantic import BaseModel
from google.adk.telemetry.tracing import _safe_json_serialize


class SearchResults(BaseModel):
    query: str
    total: int
    items: list[str]


async def search_tool(query: str) -> SearchResults:
    """A tool that returns a Pydantic model."""
    return SearchResults(query=query, total=2, items=["doc1", "doc2"])


result = asyncio.run(search_tool(query="test"))

# ADK wraps non-dict results (functions.py __build_response_event)
tool_response = {"result": result}

# ADK traces the response (tracing.py trace_tool_call)
serialized = _safe_json_serialize(tool_response)
print(serialized)
# Output: {"result": "<not serializable>"}

# Expected: {"result": {"query": "test", "total": 2, "items": ["doc1", "doc2"]}}

Suggested Fix

BaseModel is already imported in tracing.py (line 60) and pydantic is already a dependency. The fix is minimal:

def _safe_json_serialize(obj) -> str:
  def _default(o):
    if isinstance(o, BaseModel):
      return o.model_dump(mode="json")
    return '<not serializable>'

  try:
    return json.dumps(obj, ensure_ascii=False, default=_default)
  except (TypeError, OverflowError):
    return '<not serializable>'

This is consistent with how the rest of the file already handles Pydantic models (e.g. _serialize_content at line 470 calls content.model_dump()).

Impact

Every tool returning a Pydantic model has its trace output silently dropped. This affects observability integrations (Langfuse, Phoenix, etc.) that consume these spans. The tool itself works fine — only the trace is broken.

Related Issues

All of these are symptoms of the same gap: _safe_json_serialize only handles JSON-native types.

Environment

  • ADK version: 1.25.1 (also confirmed on main branch)
  • Python: 3.12
  • Pydantic: 2.x

Metadata

Metadata

Labels

tracing[Component] This issue is related to OpenTelemetry tracing

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions