FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix(workflow): dump validated schemas in JSON mode by a2105z · Pull Request #6750 · google/adk-python · GitHub

Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .py  (4) All 1 file type selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
12 changes: 9 additions & 3 deletions src/google/adk/utils/_schema_utils.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,16 @@ def validate_schema(schema: SchemaType, json_text: str) -> Any:

if is_basemodel_schema(schema):
# For regular BaseModel, use model_validate_json
return schema.model_validate_json(json_text).model_dump(exclude_none=True)
return schema.model_validate_json(json_text).model_dump(
mode='json', exclude_none=True
)
elif is_list_of_basemodel(schema):
# For list[BaseModel], use TypeAdapter to validate
type_adapter = TypeAdapter(schema)
validated: list[Any] = type_adapter.validate_json(json_text)
return [item.model_dump(exclude_none=True) for item in validated]
return [
item.model_dump(mode='json', exclude_none=True) for item in validated
]
else:
# For other schema types (list[str], dict, Schema, etc.),
return json.loads(json_text)
Expand All @@ -162,8 +166,10 @@ def validate_node_data(
return data

def _to_serializable(val: Any) -> Any:
# JSON mode applies when_used="json" serializers and converts values
# (Decimal, datetime, Enum, …) into JSON-serializable forms.
if isinstance(val, BaseModel):
return val.model_dump(exclude_none=True)
return val.model_dump(mode='json', exclude_none=True)
if isinstance(val, list):
return [_to_serializable(item) for item in val]
if isinstance(val, dict):
Expand Down
8 changes: 4 additions & 4 deletions src/google/adk/workflow/_base_node.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ def _validate_name(cls, v: str) -> str:
generic aliases like ``list[str]``, raw ``dict`` schemas, etc.).

When set to a ``BaseModel`` subclass, the node's output data is validated:
- dict → ``output_schema.model_validate(data).model_dump()``
- BaseModel instance → ``data.model_dump()`` (already converted)
- dict → ``output_schema.model_validate(data).model_dump(mode="json")``
- BaseModel instance → ``data.model_dump(mode="json")`` (already converted)

``None`` means no output validation (the default).
"""
Expand Down Expand Up @@ -133,9 +133,9 @@ def _validate_output_data(self, data: Any) -> Any:

@staticmethod
def _to_serializable(data: Any) -> Any:
"""Converts BaseModel instances to dicts recursively."""
"""Converts BaseModel instances to JSON-serializable dicts recursively."""
if isinstance(data, BaseModel):
return data.model_dump()
return data.model_dump(mode='json')
if isinstance(data, list):
return [BaseNode._to_serializable(item) for item in data]
if isinstance(data, dict):
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ def _validate_resume_response(response_data: object, schema: object) -> object:
model_instance = TypeAdapter(DynamicModel).validate_python(
response_data
)
return model_instance.model_dump()
return model_instance.model_dump(mode='json')
except ValidationError as e:
raise ValueError(f'Validation failed for object schema: {e}') from e

Expand Down
60 changes: 60 additions & 0 deletions tests/unittests/utils/test_schema_utils.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,22 @@

"""Tests for _schema_utils module."""

from datetime import datetime
from decimal import Decimal
from enum import Enum
import json
from typing import Annotated

from google.adk.utils._schema_utils import get_list_inner_type
from google.adk.utils._schema_utils import is_basemodel_schema
from google.adk.utils._schema_utils import is_list_of_basemodel
from google.adk.utils._schema_utils import schema_to_json_schema
from google.adk.utils._schema_utils import validate_node_data
from google.adk.utils._schema_utils import validate_schema
from google.adk.workflow._base_node import BaseNode
from google.genai import types
from pydantic import BaseModel
from pydantic import PlainSerializer
from pydantic import ValidationError
import pytest

Expand Down Expand Up @@ -258,6 +266,58 @@ def test_raw_string_not_parsed_with_str_schema(self):
result = validate_node_data(str, 'hello')
assert result == 'hello'

def test_json_mode_serializers_are_applied_for_decimal(self):
"""when_used='json' serializers run so validated node data is JSON-safe."""
JsonDecimal = Annotated[
Decimal, PlainSerializer(float, return_type=float, when_used='json')
]

class Price(BaseModel):
amount: JsonDecimal

class Payload(BaseModel):
price: Price

result = validate_node_data(Payload, {'price': {'amount': '29.99'}})
assert result == {'price': {'amount': 29.99}}
assert isinstance(result['price']['amount'], float)
assert json.dumps(result) == '{"price": {"amount": 29.99}}'

def test_datetime_and_enum_fields_are_json_serializable(self):
"""Python-mode types that json.dumps rejects become JSON-safe values."""

class Color(Enum):
RED = 1

class Payload(BaseModel):
stamped_at: datetime
color: Color

result = validate_node_data(
Payload,
{'stamped_at': '2026-01-02T03:04:05', 'color': 1},
)
assert result['color'] == 1
assert isinstance(result['stamped_at'], str)
json.dumps(result)

def test_base_node_output_validation_is_json_serializable(self):
"""BaseNode output_schema validation returns JSON-serializable dicts."""
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)
result = node._validate_output_data({'price': {'amount': '29.99'}})
assert result == {'price': {'amount': 29.99}}
json.dumps(result)


class TestSchemaToJsonSchema:
"""Tests for schema_to_json_schema function."""
Expand Down

Back | FazBrowse Home | New Git URL