From cdebce941252c8817447108b395b90997d45ec40 Mon Sep 17 00:00:00 2001 From: Yashwant Bezawada Date: Sun, 9 Nov 2025 03:12:07 -0600 Subject: [PATCH 1/2] fix: Always set additionalProperties=false for strict schema compliance Fixes #2740 The `to_strict_json_schema` function now always sets `additionalProperties` to `false` for object types, as required by the OpenAI API for structured output. Previously, it only set this value when the key was missing, which caused issues with Pydantic models using `extra="allow"`. When Pydantic models use `ConfigDict(extra="allow")`, the generated schema includes `"additionalProperties": true`. However, the OpenAI API requires `"additionalProperties": false` for structured output to work correctly. Changes: - Modified `_ensure_strict_json_schema()` to unconditionally set `additionalProperties = false` for all object types - Added comprehensive test case `test_pydantic_extra_allow()` to verify the fix handles models with `extra="allow"` correctly This ensures API compliance while maintaining backward compatibility with existing code. --- src/openai/lib/_pydantic.py | 5 ++++- tests/lib/test_pydantic.py | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/openai/lib/_pydantic.py b/src/openai/lib/_pydantic.py index 3cfe224cb1..02bd2bcdf0 100644 --- a/src/openai/lib/_pydantic.py +++ b/src/openai/lib/_pydantic.py @@ -47,7 +47,10 @@ def _ensure_strict_json_schema( _ensure_strict_json_schema(definition_schema, path=(*path, "definitions", definition_name), root=root) typ = json_schema.get("type") - if typ == "object" and "additionalProperties" not in json_schema: + if typ == "object": + # Always set additionalProperties to False for strict schema compliance. + # The OpenAI API requires additionalProperties=false for structured output, + # even if Pydantic models use extra="allow" which sets it to True. json_schema["additionalProperties"] = False # object types diff --git a/tests/lib/test_pydantic.py b/tests/lib/test_pydantic.py index 754a15151c..43006ac37c 100644 --- a/tests/lib/test_pydantic.py +++ b/tests/lib/test_pydantic.py @@ -409,3 +409,28 @@ def test_nested_inline_ref_expansion() -> None: "additionalProperties": False, } ) + + +def test_pydantic_extra_allow() -> None: + """Test that models with extra='allow' correctly set additionalProperties to False. + + Regression test for issue #2740. + The OpenAI API requires additionalProperties=false for structured output, + even when Pydantic models use extra="allow" which generates True by default. + """ + from pydantic import ConfigDict + + class MyClassWithExtraAllow(BaseModel): + model_config = ConfigDict(extra="allow") + field: str = Field(description="A test field") + + schema = to_strict_json_schema(MyClassWithExtraAllow) + + # The schema must have additionalProperties set to False + assert schema.get("additionalProperties") == False, \ + "additionalProperties must be False for API compliance, even with extra='allow'" + + # Verify the rest of the schema is correct + assert schema["type"] == "object" + assert "field" in schema["properties"] + assert schema["required"] == ["field"] From bcf94fbe44a49e09df0a1624809bdb46d17e6156 Mon Sep 17 00:00:00 2001 From: Yashwant Bezawada Date: Sat, 15 Nov 2025 13:29:33 -0600 Subject: [PATCH 2/2] fix: preserve structured additionalProperties schemas for Dict types Addresses review feedback: - Only set additionalProperties=False when it's True (from extra="allow") or missing, preserving structured schemas for Dict[str, T] types - Convert test to use inline snapshots as requested This fix ensures that Dict[str, T] typed fields maintain their structured additionalProperties schemas (e.g., {"type": "number"}) instead of being forced to False, which would break validation for dynamic keys. --- src/openai/lib/_pydantic.py | 7 +++++-- tests/lib/test_pydantic.py | 17 +++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/openai/lib/_pydantic.py b/src/openai/lib/_pydantic.py index 02bd2bcdf0..d4c18d2325 100644 --- a/src/openai/lib/_pydantic.py +++ b/src/openai/lib/_pydantic.py @@ -48,10 +48,13 @@ def _ensure_strict_json_schema( typ = json_schema.get("type") if typ == "object": - # Always set additionalProperties to False for strict schema compliance. + # Set additionalProperties to False for strict schema compliance, but preserve + # structured schemas (e.g., for Dict[str, T] which needs {"additionalProperties": {schema}}). # The OpenAI API requires additionalProperties=false for structured output, # even if Pydantic models use extra="allow" which sets it to True. - json_schema["additionalProperties"] = False + additional_props = json_schema.get("additionalProperties") + if additional_props is True or "additionalProperties" not in json_schema: + json_schema["additionalProperties"] = False # object types # { 'type': 'object', 'properties': { 'a': {...} } } diff --git a/tests/lib/test_pydantic.py b/tests/lib/test_pydantic.py index 43006ac37c..f784921a7b 100644 --- a/tests/lib/test_pydantic.py +++ b/tests/lib/test_pydantic.py @@ -426,11 +426,12 @@ class MyClassWithExtraAllow(BaseModel): schema = to_strict_json_schema(MyClassWithExtraAllow) - # The schema must have additionalProperties set to False - assert schema.get("additionalProperties") == False, \ - "additionalProperties must be False for API compliance, even with extra='allow'" - - # Verify the rest of the schema is correct - assert schema["type"] == "object" - assert "field" in schema["properties"] - assert schema["required"] == ["field"] + assert schema == snapshot( + { + "properties": {"field": {"description": "A test field", "title": "Field", "type": "string"}}, + "required": ["field"], + "title": "MyClassWithExtraAllow", + "type": "object", + "additionalProperties": False, + } + )