Skip to content

Commit 2f4f0b3

Browse files
fix(structured outputs): correct schema coercion for inline ref expansion (#2025)
1 parent 6d3513c commit 2f4f0b3

File tree

2 files changed

+177
-0
lines changed

2 files changed

+177
-0
lines changed

src/openai/lib/_pydantic.py

+3
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ def _ensure_strict_json_schema(
108108
# properties from the json schema take priority over the ones on the `$ref`
109109
json_schema.update({**resolved, **json_schema})
110110
json_schema.pop("$ref")
111+
# Since the schema expanded from `$ref` might not have `additionalProperties: false` applied,
112+
# we call `_ensure_strict_json_schema` again to fix the inlined schema and ensure it's valid.
113+
return _ensure_strict_json_schema(json_schema, path=path, root=root)
111114

112115
return json_schema
113116

tests/lib/test_pydantic.py

+174
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import openai
99
from openai._compat import PYDANTIC_V2
10+
from openai.lib._pydantic import to_strict_json_schema
1011

1112
from .schema_types.query import Query
1213

@@ -235,3 +236,176 @@ def test_enums() -> None:
235236
},
236237
}
237238
)
239+
240+
241+
class Star(BaseModel):
242+
name: str = Field(description="The name of the star.")
243+
244+
245+
class Galaxy(BaseModel):
246+
name: str = Field(description="The name of the galaxy.")
247+
largest_star: Star = Field(description="The largest star in the galaxy.")
248+
249+
250+
class Universe(BaseModel):
251+
name: str = Field(description="The name of the universe.")
252+
galaxy: Galaxy = Field(description="A galaxy in the universe.")
253+
254+
255+
def test_nested_inline_ref_expansion() -> None:
256+
if PYDANTIC_V2:
257+
assert to_strict_json_schema(Universe) == snapshot(
258+
{
259+
"title": "Universe",
260+
"type": "object",
261+
"$defs": {
262+
"Star": {
263+
"title": "Star",
264+
"type": "object",
265+
"properties": {
266+
"name": {
267+
"type": "string",
268+
"title": "Name",
269+
"description": "The name of the star.",
270+
}
271+
},
272+
"required": ["name"],
273+
"additionalProperties": False,
274+
},
275+
"Galaxy": {
276+
"title": "Galaxy",
277+
"type": "object",
278+
"properties": {
279+
"name": {
280+
"type": "string",
281+
"title": "Name",
282+
"description": "The name of the galaxy.",
283+
},
284+
"largest_star": {
285+
"title": "Star",
286+
"type": "object",
287+
"properties": {
288+
"name": {
289+
"type": "string",
290+
"title": "Name",
291+
"description": "The name of the star.",
292+
}
293+
},
294+
"required": ["name"],
295+
"description": "The largest star in the galaxy.",
296+
"additionalProperties": False,
297+
},
298+
},
299+
"required": ["name", "largest_star"],
300+
"additionalProperties": False,
301+
},
302+
},
303+
"properties": {
304+
"name": {
305+
"type": "string",
306+
"title": "Name",
307+
"description": "The name of the universe.",
308+
},
309+
"galaxy": {
310+
"title": "Galaxy",
311+
"type": "object",
312+
"properties": {
313+
"name": {
314+
"type": "string",
315+
"title": "Name",
316+
"description": "The name of the galaxy.",
317+
},
318+
"largest_star": {
319+
"title": "Star",
320+
"type": "object",
321+
"properties": {
322+
"name": {
323+
"type": "string",
324+
"title": "Name",
325+
"description": "The name of the star.",
326+
}
327+
},
328+
"required": ["name"],
329+
"description": "The largest star in the galaxy.",
330+
"additionalProperties": False,
331+
},
332+
},
333+
"required": ["name", "largest_star"],
334+
"description": "A galaxy in the universe.",
335+
"additionalProperties": False,
336+
},
337+
},
338+
"required": ["name", "galaxy"],
339+
"additionalProperties": False,
340+
}
341+
)
342+
else:
343+
assert to_strict_json_schema(Universe) == snapshot(
344+
{
345+
"title": "Universe",
346+
"type": "object",
347+
"definitions": {
348+
"Star": {
349+
"title": "Star",
350+
"type": "object",
351+
"properties": {
352+
"name": {"title": "Name", "description": "The name of the star.", "type": "string"}
353+
},
354+
"required": ["name"],
355+
"additionalProperties": False,
356+
},
357+
"Galaxy": {
358+
"title": "Galaxy",
359+
"type": "object",
360+
"properties": {
361+
"name": {"title": "Name", "description": "The name of the galaxy.", "type": "string"},
362+
"largest_star": {
363+
"title": "Largest Star",
364+
"description": "The largest star in the galaxy.",
365+
"type": "object",
366+
"properties": {
367+
"name": {"title": "Name", "description": "The name of the star.", "type": "string"}
368+
},
369+
"required": ["name"],
370+
"additionalProperties": False,
371+
},
372+
},
373+
"required": ["name", "largest_star"],
374+
"additionalProperties": False,
375+
},
376+
},
377+
"properties": {
378+
"name": {
379+
"title": "Name",
380+
"description": "The name of the universe.",
381+
"type": "string",
382+
},
383+
"galaxy": {
384+
"title": "Galaxy",
385+
"description": "A galaxy in the universe.",
386+
"type": "object",
387+
"properties": {
388+
"name": {
389+
"title": "Name",
390+
"description": "The name of the galaxy.",
391+
"type": "string",
392+
},
393+
"largest_star": {
394+
"title": "Largest Star",
395+
"description": "The largest star in the galaxy.",
396+
"type": "object",
397+
"properties": {
398+
"name": {"title": "Name", "description": "The name of the star.", "type": "string"}
399+
},
400+
"required": ["name"],
401+
"additionalProperties": False,
402+
},
403+
},
404+
"required": ["name", "largest_star"],
405+
"additionalProperties": False,
406+
},
407+
},
408+
"required": ["name", "galaxy"],
409+
"additionalProperties": False,
410+
}
411+
)

0 commit comments

Comments
 (0)