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
Description
_safe_json_serializeinsrc/google/adk/telemetry/tracing.pysilently replaces PydanticBaseModelinstances 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:
When a tool returns a Pydantic model, ADK wraps it as
{'result': <BaseModel>}in__build_response_event(functions.py line 798-799).json.dumpscan't serializeBaseModelnatively, so thedefaultlambda fires and replaces it.Reproduction
Suggested Fix
BaseModelis already imported intracing.py(line 60) and pydantic is already a dependency. The fix is minimal:This is consistent with how the rest of the file already handles Pydantic models (e.g.
_serialize_contentat line 470 callscontent.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
AnyUrlnot serializable (MCP tools, same root cause)datenot serializable (BigQuery tools, same root cause)All of these are symptoms of the same gap:
_safe_json_serializeonly handles JSON-native types.Environment