Unexpected additionalProperties requirement for nested Pydantic models in response_format

I’m encountering an error when using the following Pydantic model as response_schema in the API. The error only occurs with specific nested models, but not others that appear to have a similar structure.

Here’s the model:

from enum import Enum
from typing import List, Optional
from pydantic import BaseModel, Field

class ElementCategory(str, Enum):
    visual = "visual"
    audio = "audio"

class DetailLevel(str, Enum):
    minimal = "minimal"
    moderate = "moderate"

class ModelApproach(BaseModel):
    overall_style: str = Field(...)
    distinctive_features: List[str] = Field(...)

class SceneMapping(BaseModel):
    v1_scenes: List[str] = Field(...)
    v2_scenes: List[str] = Field(...)

class HighLevelComparison(BaseModel):
    v1_approach: ModelApproach = Field(...)
    v2_approach: ModelApproach = Field(...)
    scene_mapping: SceneMapping = Field(...)

class ElementComparison(BaseModel):
    element_name: str = Field(...)
    element_category: ElementCategory = Field(...)
    difference_notes: str = Field(...)

class SceneComparison(BaseModel):
    scene_id: str = Field(...)
    elements_comparison: List[ElementComparison] = Field(...)
    elements_unique_to_v1: List[str] = Field(...)

class ModelComparisonResponse(BaseModel):
    high_level_comparison: HighLevelComparison = Field(...)
    scene_comparisons: List[SceneComparison] = Field(...)
    conclusion: List[str] = Field(...)

And here is the call:

      PROMPT_MESSAGES = [
          {"role": "system", "content": self.system},
          {
              "role": "user",
              "content": [
                  {
                      "type": "text",
                      "text": prompt
                  }
              ]
          }
      ]

        params = {
            "model": 'o3-mini',
            "messages": PROMPT_MESSAGES,
            "max_completion_tokens": 10000,
            "seed": 33,
        }

        result = self.beta.chat.completions.parse(
            **params,
            response_format=ModelComparisonResponse
        )
        return result.choices[0].message.content

I am getting this error:

Error code: 400 - {'error': {'message': "Invalid schema for response_format 'ModelComparisonResponse': In context=('properties', 'v1_approach'), 'additionalProperties' is required to be supplied and to be false.", 'type': 'invalid_request_error', 'param': 'response_format', 'code': None}}

Interestingly:

  1. When I add model_config = {“json_schema_extra”: {“additionalProperties”: False}} to the ModelApproach class, I still get errors.
  2. When I add the same config to both ModelApproach and SceneMapping, it seems to work.
  3. When I comment out high_level_comparison from ModelComparisonResponse, everything works fine despite scene_comparisons containing equally nested models.

This seems inconsistent since the SceneComparison model also has nested models (ElementComparison), yet only the models under HighLevelComparison trigger the error.

Anyone able to understand why I got this error only for some of the nested models? Thanks!

You can do what the SDK schema maker seems not to be doing - putting in the expected additionalProperties at all nest levels that are required.

class ElementCategory(str, Enum):
    visual = "visual"
    audio = "audio"

class DetailLevel(str, Enum):
    minimal = "minimal"
    moderate = "moderate"

class ModelApproach(BaseModel):
    overall_style: str = Field(...)
    distinctive_features: List[str] = Field(...)
    
    model_config = ConfigDict(extra='forbid')

class SceneMapping(BaseModel):
    v1_scenes: List[str] = Field(...)
    v2_scenes: List[str] = Field(...)

    model_config = ConfigDict(extra='forbid')

class HighLevelComparison(BaseModel):
    v1_approach: ModelApproach = Field(...)
    v2_approach: ModelApproach = Field(...)
    scene_mapping: SceneMapping = Field(...)

    model_config = ConfigDict(extra='forbid')

class ElementComparison(BaseModel):
    element_name: str = Field(...)
    element_category: ElementCategory = Field(...)
    difference_notes: str = Field(...)

    model_config = ConfigDict(extra='forbid')

class SceneComparison(BaseModel):
    scene_id: str = Field(...)
    elements_comparison: List[ElementComparison] = Field(...)
    elements_unique_to_v1: List[str] = Field(...)

    model_config = ConfigDict(extra='forbid')

class ModelComparisonResponse(BaseModel):
    high_level_comparison: HighLevelComparison = Field(...)
    scene_comparisons: List[SceneComparison] = Field(...)
    conclusion: List[str] = Field(...)

    model_config = ConfigDict(extra='forbid')

Which was satisfied by responses.parse()

The curious person could see exactly which level is not having this requirement done for you, to then open up a github openai library issue.

ok this seems to be solved in v1.68.2 of the python SDK…
I wasn’t aligned to newest version. Sorry about that