google / google/adk-python

output_schema validation dumps in pydantic python mode, so the node result is not JSON-serializable

Abierto
#6,747 3 comentarios 0 reacciones 1 asignado Reclamado por @surajksharma07 Ver en GitHub
core request clarification
Lenguaje dominante
Python
Estrellas
21.5k
Forks
4k
Merge medio
1 d 14 h
PR fusionados (30 d)
37

Descripción

🔴 Required Information

Describe the Bug:

BaseNode._validate_schema documents that validated BaseModel instances are converted to dicts "to keep
Event.output JSON-serializable", but the conversion calls model_dump() — pydantic's python mode.
Python mode preserves Decimal, datetime, UUID and non-str Enum members, and skips any serializer
registered with when_used="json". The resulting dict is therefore not JSON-serializable, which is the
opposite of the documented intent.

The docstring and the implementation contradict each other:

# src/google/adk/workflow/_base_node.py
def _validate_schema(self, data: Any, schema: Any) -> Any:
"""... Any BaseModel instances in the validated
result are converted to dicts via ``model_dump()`` to keep
``Event.output`` JSON-serializable.
"""
...
validated = TypeAdapter(schema).validate_python(data)
return self._to_serializable(validated)

@staticmethod
def _to_serializable(data: Any) -> Any:
"""Converts BaseModel instances to dicts recursively."""
if isinstance(data, BaseModel):
return data.model_dump() # <-- python mode

Both validation paths reach it: _validate_input_data → _validate_schema → _to_serializable, and
_validate_output_data → _validate_schema → _to_serializable, plus a direct call on the
JSON-reparse fallback.

Steps to Reproduce:

pip install google-adk==2.3.0

Save the snippet below as repro.py

Run python repro.py

It prints a dict containing Decimal('29.99') and then raises

TypeError: Object of type Decimal is not JSON serializable

import json
from decimal import Decimal
from typing import Annotated

from google.adk.workflow._base_node import BaseNode
from pydantic import BaseModel, PlainSerializer

JsonDecimal = Annotated[Decimal, PlainSerializer(float, return_type=float, when_used="json")]

class Price(BaseModel):
amount: JsonDecimal

class Payload(BaseModel):
price: Price

node = BaseNode(name="pricing", output_schema=Payload)

validated = node._validate_output_data({"price": {"amount": "29.99"}})
print(validated) # {'price': {'amount': Decimal('29.99')}}
json.dumps(validated) # TypeError: Object of type Decimal is not JSON serializable

The node is constructed with the public output_schema field and handed the plain dict a model would
return; _validate_output_data is the method ADK's own node runner invokes on that result.

Expected Behavior:

The validated result is JSON-serializable, as _validate_schema documents — the when_used="json"
serializer the schema declares should run, so amount becomes the number 29.99.

Observed Behavior:

{'price': {'amount': Decimal('29.99')}}
Traceback (most recent call last):
...
TypeError: Object of type Decimal is not JSON serializable

Environment Details:

ADK Library Version (pip show google-adk): 2.3.0 (also present in 2.7.0 — see Regression)

Desktop OS: Linux

Python Version (python -V): 3.12.3

Model Information:

Are you using LiteLLM: No

Which model is being used: N/A — this is a schema-validation defect in _base_node.py and reproduces
with no model call at all.

🟡 Optional Information

Regression:

Not a regression; the code path looks the same in 2.7.0, the latest release at time of writing.

Additional Context:

Decimal is the case we hit in production, but it is not special — any type pydantic renders differently
in JSON mode fails the same way. Two more, both of which json.dumps also rejects:

Object of type MyEnum is not JSON serializable # a plain Enum field (not StrEnum)
Object of type datetime is not JSON serializable # a datetime field

Suggested fix — one keyword, covering both validation paths and every nesting depth, since the
list/dict branches recurse back into the same function:

@staticmethod
def _to_serializable(data: Any) -> Any:
"""Converts BaseModel instances to dicts recursively."""
if isinstance(data, BaseModel):
- return data.model_dump()
+ return data.model_dump(mode="json")

Happy to send this as a PR if that is useful.

Our workaround is to patch _to_serializable to dump in JSON mode and delegate every non-BaseModel
value back to the original implementation. It is guarded by a test asserting the unpatched function still
shows the defect, so it fails loudly and can be removed once this is fixed upstream.

Minimal Reproduction Code: see Steps to Reproduce above.

How often has this issue occurred?: Always (100%) — deterministic for any schema containing such a field.

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.