fix: also populate required for all properties in strict mode

OpenAI strict mode requires both additionalProperties:false AND all
property keys in required. Without required, OpenAI rejects the schema
even with additionalProperties:false set.
This commit is contained in:
Chesars
2026-03-18 23:03:20 -03:00
parent 4bd7bdcf43
commit 286b8d1460
2 changed files with 31 additions and 2 deletions
@@ -843,15 +843,18 @@ class LiteLLMAnthropicMessagesAdapter:
@staticmethod
def _add_additional_properties_false(schema: dict) -> None:
"""
Recursively add 'additionalProperties': false to all object schemas.
Recursively ensure object schemas comply with OpenAI strict mode.
OpenAI's strict mode requires this at every object nesting level.
OpenAI's strict mode requires:
1. 'additionalProperties': false at every object nesting level
2. All property keys listed in 'required'
"""
if not isinstance(schema, dict):
return
if schema.get("type") == "object" and "properties" in schema:
schema["additionalProperties"] = False
schema["required"] = list(schema["properties"].keys())
for prop in schema["properties"].values():
LiteLLMAnthropicMessagesAdapter._add_additional_properties_false(prop)
@@ -2004,6 +2004,7 @@ class TestTranslateAnthropicOutputFormatToOpenAI:
assert result is not None
schema = result["json_schema"]["schema"]
assert schema["additionalProperties"] is False
assert schema["required"] == ["name"]
def test_nested_objects_adds_additional_properties_false(self):
output_format = {
@@ -2028,8 +2029,11 @@ class TestTranslateAnthropicOutputFormatToOpenAI:
assert result is not None
schema = result["json_schema"]["schema"]
assert schema["additionalProperties"] is False
assert schema["required"] == ["user"]
assert schema["properties"]["user"]["additionalProperties"] is False
assert schema["properties"]["user"]["required"] == ["name", "address"]
assert schema["properties"]["user"]["properties"]["address"]["additionalProperties"] is False
assert schema["properties"]["user"]["properties"]["address"]["required"] == ["city"]
def test_array_items_object_adds_additional_properties_false(self):
output_format = {
@@ -2061,6 +2065,7 @@ class TestTranslateAnthropicOutputFormatToOpenAI:
output_format = {"type": "json_schema", "schema": original_schema}
self.adapter.translate_anthropic_output_format_to_openai(output_format)
assert "additionalProperties" not in original_schema
assert "required" not in original_schema
def test_defs_adds_additional_properties_false(self):
output_format = {
@@ -2080,6 +2085,27 @@ class TestTranslateAnthropicOutputFormatToOpenAI:
assert result is not None
schema = result["json_schema"]["schema"]
assert schema["$defs"]["Item"]["additionalProperties"] is False
assert schema["$defs"]["Item"]["required"] == ["value"]
def test_incomplete_required_gets_completed(self):
"""OpenAI strict mode requires ALL properties in required."""
output_format = {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
"email": {"type": "string"},
},
"required": ["name"], # only 1 of 3
},
}
result = self.adapter.translate_anthropic_output_format_to_openai(output_format)
assert result is not None
schema = result["json_schema"]["schema"]
assert schema["additionalProperties"] is False
assert sorted(schema["required"]) == ["age", "email", "name"]
def test_invalid_output_format_returns_none(self):
assert self.adapter.translate_anthropic_output_format_to_openai("invalid") is None