diff --git a/litellm/llms/openai/chat/gpt_5_transformation.py b/litellm/llms/openai/chat/gpt_5_transformation.py index 05c003c8b7..5e85c547aa 100644 --- a/litellm/llms/openai/chat/gpt_5_transformation.py +++ b/litellm/llms/openai/chat/gpt_5_transformation.py @@ -69,14 +69,20 @@ class OpenAIGPT5Config(OpenAIGPTConfig): base_gpt_series_params.remove("tool_choice") non_supported_params = [ - "logprobs", - "top_p", "presence_penalty", "frequency_penalty", - "top_logprobs", "stop", + "logit_bias", + "modalities", + "prediction", + "audio", + "web_search_options", ] + # gpt-5.1/5.2 support logprobs, top_p, top_logprobs when reasoning_effort="none" + if not self.is_model_gpt_5_1_model(model): + non_supported_params.extend(["logprobs", "top_p", "top_logprobs"]) + return [ param for param in base_gpt_series_params @@ -118,6 +124,24 @@ class OpenAIGPT5Config(OpenAIGPTConfig): "max_tokens" ) + # gpt-5.1/5.2 support logprobs, top_p, top_logprobs only when reasoning_effort="none" + if self.is_model_gpt_5_1_model(model): + sampling_params = ["logprobs", "top_logprobs", "top_p"] + has_sampling = any(p in non_default_params for p in sampling_params) + if has_sampling and reasoning_effort not in (None, "none"): + if litellm.drop_params or drop_params: + for p in sampling_params: + non_default_params.pop(p, None) + else: + raise litellm.utils.UnsupportedParamsError( + message=( + "gpt-5.1/5.2 only support logprobs, top_p, top_logprobs when " + "reasoning_effort='none'. Current reasoning_effort='{}'. " + "To drop unsupported params set `litellm.drop_params = True`" + ).format(reasoning_effort), + status_code=400, + ) + if "temperature" in non_default_params: temperature_value: Optional[float] = non_default_params.pop("temperature") if temperature_value is not None: diff --git a/tests/test_litellm/llms/openai/test_gpt5_transformation.py b/tests/test_litellm/llms/openai/test_gpt5_transformation.py index 386f264a4d..decc19c866 100644 --- a/tests/test_litellm/llms/openai/test_gpt5_transformation.py +++ b/tests/test_litellm/llms/openai/test_gpt5_transformation.py @@ -414,3 +414,98 @@ def test_gpt5_2_allows_reasoning_effort_xhigh(config: OpenAIConfig): drop_params=False, ) assert params["reasoning_effort"] == "xhigh" + + +# GPT-5 unsupported params audit (validated via direct API calls) +def test_gpt5_rejects_params_unsupported_by_openai(config: OpenAIConfig): + """Params that OpenAI rejects for all GPT-5 reasoning models.""" + rejected_params = [ + "logit_bias", + "modalities", + "prediction", + "audio", + "web_search_options", + ] + for model in ["gpt-5", "gpt-5-mini", "gpt-5-codex", "gpt-5.1", "gpt-5.2"]: + supported = config.get_supported_openai_params(model=model) + for param in rejected_params: + assert param not in supported, ( + f"{param} should not be supported for {model}" + ) + + +def test_gpt5_1_supports_logprobs_top_p(config: OpenAIConfig): + """gpt-5.1/5.2 support logprobs, top_p, top_logprobs when reasoning_effort='none'.""" + for model in ["gpt-5.1", "gpt-5.2"]: + supported = config.get_supported_openai_params(model=model) + assert "logprobs" in supported, f"logprobs should be supported for {model}" + assert "top_p" in supported, f"top_p should be supported for {model}" + assert "top_logprobs" in supported, f"top_logprobs should be supported for {model}" + + +def test_gpt5_base_does_not_support_logprobs_top_p(config: OpenAIConfig): + """Base gpt-5/gpt-5-mini do NOT support logprobs, top_p, top_logprobs.""" + for model in ["gpt-5", "gpt-5-mini", "gpt-5-codex"]: + supported = config.get_supported_openai_params(model=model) + assert "logprobs" not in supported, f"logprobs should not be supported for {model}" + assert "top_p" not in supported, f"top_p should not be supported for {model}" + assert "top_logprobs" not in supported, f"top_logprobs should not be supported for {model}" + + +def test_gpt5_1_logprobs_passthrough(config: OpenAIConfig): + """Test that logprobs passes through for gpt-5.1.""" + params = config.map_openai_params( + non_default_params={"logprobs": True, "top_logprobs": 3}, + optional_params={}, + model="gpt-5.1", + drop_params=False, + ) + assert params["logprobs"] is True + assert params["top_logprobs"] == 3 + + +def test_gpt5_1_top_p_passthrough(config: OpenAIConfig): + """Test that top_p passes through for gpt-5.1.""" + params = config.map_openai_params( + non_default_params={"top_p": 0.9}, + optional_params={}, + model="gpt-5.1", + drop_params=False, + ) + assert params["top_p"] == 0.9 + + +def test_gpt5_1_logprobs_rejected_with_reasoning_effort(config: OpenAIConfig): + """logprobs/top_p/top_logprobs are rejected when reasoning_effort != 'none'.""" + for effort in ["low", "medium", "high"]: + with pytest.raises(litellm.utils.UnsupportedParamsError): + config.map_openai_params( + non_default_params={"logprobs": True, "reasoning_effort": effort}, + optional_params={}, + model="gpt-5.1", + drop_params=False, + ) + + +def test_gpt5_1_top_p_rejected_with_reasoning_effort(config: OpenAIConfig): + """top_p is rejected when reasoning_effort != 'none'.""" + with pytest.raises(litellm.utils.UnsupportedParamsError): + config.map_openai_params( + non_default_params={"top_p": 0.9, "reasoning_effort": "high"}, + optional_params={}, + model="gpt-5.1", + drop_params=False, + ) + + +def test_gpt5_1_logprobs_dropped_with_reasoning_effort(config: OpenAIConfig): + """logprobs/top_p are dropped when reasoning_effort != 'none' and drop_params=True.""" + params = config.map_openai_params( + non_default_params={"logprobs": True, "top_p": 0.9, "reasoning_effort": "high"}, + optional_params={}, + model="gpt-5.1", + drop_params=True, + ) + assert "logprobs" not in params + assert "top_p" not in params + assert params["reasoning_effort"] == "high"