mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
Merge pull request #26456 from BerriAI/litellm_hotfix_gpt-5.5-minimal-flag
This commit is contained in:
commit
4953b9e296
9 changed files with 150 additions and 11 deletions
|
|
@ -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
|
||||
):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
[
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue