From 60afd428e34039cfe892579ff7d80802e5115cef Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 18 Mar 2026 22:40:47 +0800 Subject: [PATCH 1/2] fix(pydantic): recurse into oneOf variants in strict JSON schema Pydantic v2 generates oneOf (not anyOf) for discriminated unions (fields using discriminator=...). The _ensure_strict_json_schema function already recurses into anyOf and allOf variants but skips oneOf, so inline object schemas inside oneOf never receive additionalProperties: false or the required array -- causing the OpenAI API to reject the schema. Add oneOf handling that mirrors the existing anyOf logic. --- src/openai/lib/_pydantic.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/openai/lib/_pydantic.py b/src/openai/lib/_pydantic.py index 3cfe224cb1..5c5f5b5b45 100644 --- a/src/openai/lib/_pydantic.py +++ b/src/openai/lib/_pydantic.py @@ -74,6 +74,14 @@ def _ensure_strict_json_schema( for i, variant in enumerate(any_of) ] + # discriminated unions (Pydantic v2 uses oneOf for fields with discriminator=...) + one_of = json_schema.get("oneOf") + if is_list(one_of): + json_schema["oneOf"] = [ + _ensure_strict_json_schema(variant, path=(*path, "oneOf", str(i)), root=root) + for i, variant in enumerate(one_of) + ] + # intersections all_of = json_schema.get("allOf") if is_list(all_of): From a497c36aedd330012d31525467a43b9d3e4a7b80 Mon Sep 17 00:00:00 2001 From: Alvin Tang Date: Wed, 18 Mar 2026 22:41:14 +0800 Subject: [PATCH 2/2] test: add regression test for oneOf in strict JSON schema --- tests/lib/test_pydantic.py | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/lib/test_pydantic.py b/tests/lib/test_pydantic.py index 754a15151c..acf165ab22 100644 --- a/tests/lib/test_pydantic.py +++ b/tests/lib/test_pydantic.py @@ -409,3 +409,50 @@ def test_nested_inline_ref_expansion() -> None: "additionalProperties": False, } ) + + +def test_discriminated_union_oneOf() -> None: + """Pydantic v2 generates oneOf for discriminated unions. + + _ensure_strict_json_schema must recurse into oneOf variants + so that inline object schemas receive additionalProperties: false + and required arrays, just like it already does for anyOf. + """ + if PYDANTIC_V1: + return + + from typing import Literal, Union + + class Cat(BaseModel): + pet_type: Literal["cat"] + meows: int + + class Dog(BaseModel): + pet_type: Literal["dog"] + barks: float + + class PetOwner(BaseModel): + name: str + pet: Union[Cat, Dog] = Field(discriminator="pet_type") + + schema = to_strict_json_schema(PetOwner) + + # Top-level must be strict + assert schema["additionalProperties"] is False + assert "name" in schema["required"] + assert "pet" in schema["required"] + + # $defs must be strict + for def_name in ("Cat", "Dog"): + assert schema["$defs"][def_name]["additionalProperties"] is False + assert "pet_type" in schema["$defs"][def_name]["required"] + + # The pet property should have oneOf (Pydantic v2 discriminated union) + pet_prop = schema["properties"]["pet"] + assert "oneOf" in pet_prop, f"Expected oneOf in pet property, got keys: {list(pet_prop.keys())}" + + # Each oneOf variant (which is a $ref) should have been processed + for variant in pet_prop["oneOf"]: + # Variants are $ref entries; the key point is that the recursion + # did not raise and the schema is well-formed + assert isinstance(variant, dict)