From 62767efa060c10729167eaccf1b431a3b1bb470a Mon Sep 17 00:00:00 2001 From: fei <204683769+feiiiiii5@users.noreply.github.com> Date: Sun, 20 Sep 2026 14:01:32 +0800 Subject: [PATCH] fix(core): treat a JSON Schema array without items as untyped The array branch of _extract_field_type defaulted a missing items schema to str, so {"type": "array"} rejected integers with "Input should be a valid string". An absent items means no constraint on element type per JSON Schema, and Pydantic itself emits {"items": {}} for List[Any], which also hit the str default. Fall back to Any in both cases while keeping minItems/maxItems constraints. --- .../autogen_core/utils/_json_to_pydantic.py | 7 ++- .../tests/test_json_to_pydantic.py | 48 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/python/packages/autogen-core/src/autogen_core/utils/_json_to_pydantic.py b/python/packages/autogen-core/src/autogen_core/utils/_json_to_pydantic.py index e881d151a9fd..60350f09a72f 100644 --- a/python/packages/autogen-core/src/autogen_core/utils/_json_to_pydantic.py +++ b/python/packages/autogen-core/src/autogen_core/utils/_json_to_pydantic.py @@ -261,7 +261,10 @@ def _extract_field_type(self, key: str, value: Dict[str, Any], model_name: str, constraints["min_length"] = value["minItems"] if "maxItems" in value: constraints["max_length"] = value["maxItems"] - item_schema = value.get("items", {"type": "string"}) + # Per JSON Schema, an array without "items" (or with an empty items schema, + # which is what Pydantic emits for ``List[Any]``) puts no constraint on the + # type of its elements. Assuming strings here would reject valid data. + item_schema = value.get("items", {}) if "$ref" in item_schema: item_type = self.get_ref(item_schema["$ref"].split("/")[-1]) elif item_schema.get("type") == "object" and "properties" in item_schema: @@ -272,7 +275,7 @@ def _extract_field_type(self, key: str, value: Dict[str, Any], model_name: str, else: item_type_name = item_schema.get("type") if item_type_name is None: - item_type = str + item_type = Any elif item_type_name not in TYPE_MAPPING: raise UnsupportedKeywordError( f"Unsupported or missing item type `{item_type_name}` for array field `{key}` in `{model_name}`" diff --git a/python/packages/autogen-core/tests/test_json_to_pydantic.py b/python/packages/autogen-core/tests/test_json_to_pydantic.py index 0efad58b4ebc..988df6cb303f 100644 --- a/python/packages/autogen-core/tests/test_json_to_pydantic.py +++ b/python/packages/autogen-core/tests/test_json_to_pydantic.py @@ -3,6 +3,7 @@ from uuid import UUID, uuid4 import pytest + from autogen_core.utils._json_to_pydantic import ( FORMAT_MAPPING, TYPE_MAPPING, @@ -1042,3 +1043,50 @@ def test_nested_arrays_with_object_schemas() -> None: assert alice.name == "Alice" # type: ignore[attr-defined] assert alice.role == "Senior Developer" # type: ignore[attr-defined] assert alice.skills == ["Python", "JavaScript", "Docker"] # type: ignore[attr-defined] + + +def test_array_without_items_accepts_any_element_type() -> None: + """An array schema whose items are unconstrained accepts any element type. + + Pydantic describes ``List[Any]`` as an array with an empty items schema, and such a + schema must convert to a list of any type instead of a list of strings. + """ + + class Payload(BaseModel): + values: List[Any] + tags: List[Any] = [] + + schema = Payload.model_json_schema() + assert schema["properties"]["values"]["type"] == "array" + assert schema["properties"]["values"].get("items", {}) == {} + + Model = _JSONSchemaToPydantic().json_schema_to_pydantic(schema, "PayloadModel") + data = {"values": [1, True, "text", 4.5, {"a": 1}], "tags": []} + instance = Model(**data) + assert instance.values == data["values"] # type: ignore[attr-defined] + assert instance.tags == [] # type: ignore[attr-defined] + + # An array property that omits "items" entirely is equally unconstrained. + bare: Dict[str, Any] = { + "type": "object", + "properties": {"nums": {"type": "array"}}, + "required": ["nums"], + } + BareModel = _JSONSchemaToPydantic().json_schema_to_pydantic(bare, "BareArrayModel") + assert BareModel(**{"nums": [1, 2, 3]}).nums == [1, 2, 3] # type: ignore[attr-defined] + + +def test_constrained_array_without_items_keeps_constraints() -> None: + """``minItems``/``maxItems`` still apply when the element type is unconstrained.""" + schema: Dict[str, Any] = { + "type": "object", + "properties": {"xs": {"type": "array", "minItems": 1, "maxItems": 2}}, + "required": ["xs"], + } + Model = _JSONSchemaToPydantic().json_schema_to_pydantic(schema, "ConstrainedArrayModel") + + assert Model(**{"xs": [1, "two"]}).xs == [1, "two"] # type: ignore[attr-defined] + with pytest.raises(ValidationError): + Model(**{"xs": []}) + with pytest.raises(ValidationError): + Model(**{"xs": [1, 2, 3]})