Merge pull request #26456 from BerriAI/litellm_hotfix_gpt-5.5-minimal-flag

This commit is contained in:
Mateo Wang 2026-05-02 01:23:14 -07:00 committed by GitHub
commit 4953b9e296
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 150 additions and 11 deletions

View file

@ -244,9 +244,11 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
),
status_code=400,
)
elif effective_effort == "minimal":
# minimal is opt-out: unknown models pass through; only block when
# the model map explicitly sets supports_minimal_reasoning_effort=false.
elif effective_effort in ("minimal", "low"):
# minimal/low are opt-out: unknown models pass through; only block when
# the model map explicitly sets supports_{level}_reasoning_effort=false.
# Example: gpt-5.5-pro only accepts {medium, high, xhigh}, so it sets
# supports_low_reasoning_effort=false (and supports_minimal=false).
if self._is_reasoning_effort_level_explicitly_disabled(
model, effective_effort
):

View file

@ -656,6 +656,8 @@ def process_items(schema, depth=0):
and ("items" not in schema or schema.get("items") == {})
):
schema["items"] = {"type": "object"}
elif schema.get("type") == "array" and "items" not in schema:
schema["items"] = {"type": "object"}
for key, value in schema.items():
if isinstance(value, dict):
process_items(value, depth + 1)

View file

@ -19928,7 +19928,7 @@
"supports_web_search": true,
"supports_none_reasoning_effort": true,
"supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true
"supports_minimal_reasoning_effort": false
},
"gpt-5.5-2026-04-23": {
"cache_read_input_token_cost": 5e-07,
@ -19976,7 +19976,7 @@
"supports_web_search": true,
"supports_none_reasoning_effort": true,
"supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true
"supports_minimal_reasoning_effort": false
},
"gpt-5.5-pro": {
"cache_read_input_token_cost": 3e-06,
@ -20019,7 +20019,8 @@
"supports_web_search": true,
"supports_none_reasoning_effort": false,
"supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true
"supports_minimal_reasoning_effort": false,
"supports_low_reasoning_effort": false
},
"gpt-5.5-pro-2026-04-23": {
"cache_read_input_token_cost": 3e-06,
@ -20062,7 +20063,8 @@
"supports_web_search": true,
"supports_none_reasoning_effort": false,
"supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true
"supports_minimal_reasoning_effort": false,
"supports_low_reasoning_effort": false
},
"gpt-5.4": {
"cache_read_input_token_cost": 2.5e-07,

View file

@ -140,6 +140,7 @@ class ProviderSpecificModelInfo(TypedDict, total=False):
supports_url_context: Optional[bool]
supports_none_reasoning_effort: Optional[bool]
supports_minimal_reasoning_effort: Optional[bool]
supports_low_reasoning_effort: Optional[bool]
supports_xhigh_reasoning_effort: Optional[bool]
supports_max_reasoning_effort: Optional[bool]

View file

@ -5896,6 +5896,9 @@ def _get_model_info_helper( # noqa: PLR0915
supports_minimal_reasoning_effort=_model_info.get(
"supports_minimal_reasoning_effort", None
),
supports_low_reasoning_effort=_model_info.get(
"supports_low_reasoning_effort", None
),
supports_xhigh_reasoning_effort=_model_info.get(
"supports_xhigh_reasoning_effort", None
),

View file

@ -19942,7 +19942,7 @@
"supports_web_search": true,
"supports_none_reasoning_effort": true,
"supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true
"supports_minimal_reasoning_effort": false
},
"gpt-5.5-2026-04-23": {
"cache_read_input_token_cost": 5e-07,
@ -19990,7 +19990,7 @@
"supports_web_search": true,
"supports_none_reasoning_effort": true,
"supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true
"supports_minimal_reasoning_effort": false
},
"gpt-5.5-pro": {
"cache_read_input_token_cost": 3e-06,
@ -20033,7 +20033,8 @@
"supports_web_search": true,
"supports_none_reasoning_effort": false,
"supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true
"supports_minimal_reasoning_effort": false,
"supports_low_reasoning_effort": false
},
"gpt-5.5-pro-2026-04-23": {
"cache_read_input_token_cost": 3e-06,
@ -20076,7 +20077,8 @@
"supports_web_search": true,
"supports_none_reasoning_effort": false,
"supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true
"supports_minimal_reasoning_effort": false,
"supports_low_reasoning_effort": false
},
"gpt-5.4": {
"cache_read_input_token_cost": 2.5e-07,

View file

@ -411,6 +411,46 @@ def test_generic_cost_per_token_gpt55_pro():
)
@pytest.mark.parametrize(
"model,expected_none,expected_xhigh,expected_minimal",
[
# Verified against OpenAI's live API on 2026-04-24:
# gpt-5.5 -> supports: none, low, medium, high, xhigh
# gpt-5.5-pro -> supports: medium, high, xhigh
# Neither supports "minimal"; gpt-5.5-pro additionally does not support "none".
# The JSON must reflect this so LiteLLM rejects unsupported values locally
# (or drops them with drop_params=True) instead of round-tripping to OpenAI
# for a 400.
("gpt-5.5", True, True, False),
("gpt-5.5-2026-04-23", True, True, False),
("gpt-5.5-pro", False, True, False),
("gpt-5.5-pro-2026-04-23", False, True, False),
],
)
def test_gpt55_reasoning_effort_flags_match_live_openai_api(
model, expected_none, expected_xhigh, expected_minimal
):
"""Pin reasoning_effort capability flags to OpenAI's actual API contract.
Observed via `POST /v1/chat/completions` with reasoning_effort=minimal:
``Unsupported value: 'reasoning_effort' does not support 'minimal' with
this model``. gpt-5.5-pro additionally rejects 'none' and 'low'.
"""
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
litellm.model_cost = litellm.get_model_cost_map(url="")
m = litellm.model_cost[model]
assert (
m.get("supports_none_reasoning_effort") is expected_none
), f"{model}: supports_none_reasoning_effort expected {expected_none}"
assert (
m.get("supports_xhigh_reasoning_effort") is expected_xhigh
), f"{model}: supports_xhigh_reasoning_effort expected {expected_xhigh}"
assert (
m.get("supports_minimal_reasoning_effort") is expected_minimal
), f"{model}: supports_minimal_reasoning_effort expected {expected_minimal}"
@pytest.mark.parametrize(
"base_model,dated_model",
[

View file

@ -536,6 +536,81 @@ def test_gpt5_unknown_model_passes_through_minimal(config: OpenAIConfig):
assert params["reasoning_effort"] == "minimal"
def test_gpt5_5_pro_rejects_reasoning_effort_low(config: OpenAIConfig):
"""gpt-5.5-pro only accepts {medium, high, xhigh} — 'low' must raise.
Verified against OpenAI's live API: /v1/chat/completions with
reasoning_effort='low' on gpt-5.5-pro returns HTTP 400.
"""
with pytest.raises(litellm.utils.UnsupportedParamsError):
config.map_openai_params(
non_default_params={"reasoning_effort": "low"},
optional_params={},
model="gpt-5.5-pro",
drop_params=False,
)
def test_gpt5_5_pro_dated_rejects_reasoning_effort_low(config: OpenAIConfig):
"""Dated snapshot must inherit the base alias's low-rejection behavior."""
with pytest.raises(litellm.utils.UnsupportedParamsError):
config.map_openai_params(
non_default_params={"reasoning_effort": "low"},
optional_params={},
model="gpt-5.5-pro-2026-04-23",
drop_params=False,
)
def test_gpt5_5_pro_drops_reasoning_effort_low_when_requested(config: OpenAIConfig):
"""drop_params=True silently strips 'low' instead of round-tripping a 400."""
params = config.map_openai_params(
non_default_params={"reasoning_effort": "low"},
optional_params={},
model="gpt-5.5-pro",
drop_params=True,
)
assert "reasoning_effort" not in params
def test_gpt5_5_chat_allows_reasoning_effort_low(config: OpenAIConfig):
"""gpt-5.5 (chat) supports 'low'; flag absent → opt-out check passes."""
params = config.map_openai_params(
non_default_params={"reasoning_effort": "low"},
optional_params={},
model="gpt-5.5",
drop_params=False,
)
assert params["reasoning_effort"] == "low"
def test_gpt5_unknown_model_passes_through_low(config: OpenAIConfig):
"""Unknown gpt-5 models pass 'low' through (opt-out, not opt-in)."""
params = config.map_openai_params(
non_default_params={"reasoning_effort": "low"},
optional_params={},
model="gpt-5.4-turbo-preview",
drop_params=False,
)
assert params["reasoning_effort"] == "low"
def test_gpt5_low_explicitly_disabled_check(gpt5_config: OpenAIGPT5Config):
"""supports_low_reasoning_effort=false → disabled; missing/true → not disabled."""
assert gpt5_config._is_reasoning_effort_level_explicitly_disabled(
"gpt-5.5-pro", "low"
)
assert gpt5_config._is_reasoning_effort_level_explicitly_disabled(
"gpt-5.5-pro-2026-04-23", "low"
)
assert not gpt5_config._is_reasoning_effort_level_explicitly_disabled(
"gpt-5.5", "low"
)
assert not gpt5_config._is_reasoning_effort_level_explicitly_disabled(
"gpt-5.4", "low"
)
def test_gpt5_normalizes_reasoning_effort_dict_with_summary(config: OpenAIConfig):
"""Dict with summary/generate_summary is normalized for chat completions."""
params = config.map_openai_params(

View file

@ -300,6 +300,18 @@ def test_process_items_basic():
process_items(schema)
assert schema["items"] == {"type": "object"}
# Test array missing items inside anyOf branch
schema = {
"type": "object",
"properties": {
"callbacks": {
"anyOf": [{"type": "array"}, {"type": "object"}],
}
},
}
process_items(schema)
assert schema["properties"]["callbacks"]["anyOf"][0]["items"] == {"type": "object"}
def test_build_vertex_schema_array_branch_missing_items_in_anyof():
"""