fix: reject unsupported reasoning_effort with drop_params

This commit is contained in:
Oxygen56 2026-10-02 06:29:18 +08:00
parent 2027549c43
commit 7bf9ec40a8
5 changed files with 105 additions and 56 deletions

View file

@ -103,7 +103,7 @@ class AzureOpenAIConfig(BaseConfig):
return super().get_config()
def get_supported_openai_params(self, model: str) -> list[str]:
supported_params: Final = [
return [
"temperature",
"n",
"stream",
@ -135,9 +135,6 @@ class AzureOpenAIConfig(BaseConfig):
"prompt_cache_key",
"store",
]
if litellm.OpenAIGPTConfig.supports_reasoning_effort_passthrough(model):
supported_params.append("reasoning_effort")
return supported_params
@classmethod
def requires_max_completion_tokens(cls, model: str) -> bool:

View file

@ -182,8 +182,6 @@ class OpenAIGPTConfig(BaseLLMModelInfo, BaseConfig):
model_specific_params.append(
"user"
) # user is not a param supported by all openai-compatible endpoints - e.g. azure ai
if OpenAIGPTConfig.supports_reasoning_effort_passthrough(model):
model_specific_params.append("reasoning_effort")
return base_params + model_specific_params
@staticmethod
@ -194,12 +192,6 @@ class OpenAIGPTConfig(BaseLLMModelInfo, BaseConfig):
or model_for_check in litellm.open_ai_text_completion_models
)
@staticmethod
def supports_reasoning_effort_passthrough(model: str) -> bool:
raw_model: Final = model.split("responses/", 1)[1] if "responses/" in model else model
model_for_check: Final = raw_model.split("/", 1)[1] if "/" in raw_model else raw_model
return model_for_check.startswith(("gpt-4.1", "gpt-4o"))
def _map_openai_params(
self,
non_default_params: dict,

View file

@ -4503,6 +4503,15 @@ def get_optional_params(
unsupported_params[k] = non_default_params[k]
if unsupported_params:
if "reasoning_effort" in unsupported_params and custom_llm_provider in ("openai", "azure"):
raise UnsupportedParamsError(
status_code=500,
message=(
f"{custom_llm_provider} does not support reasoning_effort for model={model}. "
"reasoning_effort cannot be silently dropped. To forward it to the provider, "
"send allowed_openai_params=['reasoning_effort'] in your request."
),
)
if litellm.drop_params is True or (drop_params is not None and drop_params is True):
for k in unsupported_params:
non_default_params.pop(k, None)

View file

@ -45,18 +45,24 @@ def test_supports_system_message():
## confirm you can make a openai call with this param
response = litellm.completion(model="gpt-3.5-turbo", messages=new_messages, supports_system_message=False)
response = litellm.completion(
model="gpt-3.5-turbo", messages=new_messages, supports_system_message=False
)
assert isinstance(response, litellm.ModelResponse)
@pytest.mark.parametrize("stop_sequence, expected_count", [("\n", 0), (["\n"], 0), (["finish_reason"], 1)])
@pytest.mark.parametrize(
"stop_sequence, expected_count", [("\n", 0), (["\n"], 0), (["finish_reason"], 1)]
)
def test_anthropic_optional_params(stop_sequence, expected_count):
"""
Test if whitespace character optional param is dropped by anthropic
"""
litellm.drop_params = True
optional_params = get_optional_params(model="claude-3", custom_llm_provider="anthropic", stop=stop_sequence)
optional_params = get_optional_params(
model="claude-3", custom_llm_provider="anthropic", stop=stop_sequence
)
assert len(optional_params) == expected_count
@ -135,28 +141,6 @@ def test_get_optional_params_with_allowed_openai_params():
assert optional_params["reasoning_effort"] == reasoning_effort
@pytest.mark.parametrize("custom_llm_provider", ["openai", "azure"])
@pytest.mark.parametrize("model", ["gpt-4.1", "gpt-4.1-mini", "gpt-4o", "gpt-4o-mini"])
def test_gpt4_reasoning_effort_is_not_silently_dropped(custom_llm_provider, model):
optional_params = get_optional_params(
model=model,
custom_llm_provider=custom_llm_provider,
reasoning_effort="low",
drop_params=True,
)
assert optional_params["reasoning_effort"] == "low"
optional_params_without_drop = get_optional_params(
model=model,
custom_llm_provider=custom_llm_provider,
reasoning_effort="low",
drop_params=False,
)
assert optional_params_without_drop["reasoning_effort"] == "low"
def test_allowed_openai_params_does_not_forward_unset_params():
"""
Regression test for https://github.com/BerriAI/litellm/issues/25697
@ -279,7 +263,9 @@ def test_bedrock_optional_params_simple(model):
("bedrock/cohere.embed-multilingual-v3", True, None),
],
)
def test_bedrock_optional_params_embeddings_dimension(model, expected_dimensions, dimensions_kwarg):
def test_bedrock_optional_params_embeddings_dimension(
model, expected_dimensions, dimensions_kwarg
):
litellm.drop_params = True
optional_params = get_optional_params_embeddings(
model=model,
@ -513,10 +499,10 @@ def test_azure_tool_choice(api_version):
if api_version == "2024-07-01":
assert optional_params["tool_choice"] == "required"
else:
assert "tool_choice" not in optional_params, (
"tool choice should not be present. Got - tool_choice={} for api version={}".format(
optional_params["tool_choice"], api_version
)
assert (
"tool_choice" not in optional_params
), "tool choice should not be present. Got - tool_choice={} for api version={}".format(
optional_params["tool_choice"], api_version
)
@ -546,7 +532,9 @@ def test_dynamic_drop_params(drop_params):
def test_dynamic_drop_params_e2e():
with patch("litellm.llms.custom_httpx.http_handler.HTTPHandler.post", new=MagicMock()) as mock_response:
with patch(
"litellm.llms.custom_httpx.http_handler.HTTPHandler.post", new=MagicMock()
) as mock_response:
try:
response = litellm.completion(
model="command-r-08-2024",
@ -563,7 +551,9 @@ def test_dynamic_drop_params_e2e():
def test_dynamic_pass_additional_params():
with patch("litellm.llms.custom_httpx.http_handler.HTTPHandler.post", new=MagicMock()) as mock_response:
with patch(
"litellm.llms.custom_httpx.http_handler.HTTPHandler.post", new=MagicMock()
) as mock_response:
try:
response = litellm.completion(
model="command-r-08-2024",
@ -611,7 +601,9 @@ def test_dynamic_drop_params_parallel_tool_calls():
"""
https://github.com/BerriAI/litellm/issues/4584
"""
with patch("litellm.llms.custom_httpx.http_handler.HTTPHandler.post", new=MagicMock()) as mock_response:
with patch(
"litellm.llms.custom_httpx.http_handler.HTTPHandler.post", new=MagicMock()
) as mock_response:
try:
response = litellm.completion(
model="command-r-08-2024",
@ -666,7 +658,9 @@ def test_dynamic_drop_additional_params_stream_options():
def test_dynamic_drop_additional_params_e2e():
with patch("litellm.llms.custom_httpx.http_handler.HTTPHandler.post", new=MagicMock()) as mock_response:
with patch(
"litellm.llms.custom_httpx.http_handler.HTTPHandler.post", new=MagicMock()
) as mock_response:
try:
response = litellm.completion(
model="command-r-08-2024",
@ -685,12 +679,16 @@ def test_dynamic_drop_additional_params_e2e():
def test_get_optional_params_image_gen():
response = litellm.utils.get_optional_params_image_gen(aws_region_name="us-east-1", custom_llm_provider="openai")
response = litellm.utils.get_optional_params_image_gen(
aws_region_name="us-east-1", custom_llm_provider="openai"
)
print(response)
assert "aws_region_name" not in response
response = litellm.utils.get_optional_params_image_gen(aws_region_name="us-east-1", custom_llm_provider="bedrock")
response = litellm.utils.get_optional_params_image_gen(
aws_region_name="us-east-1", custom_llm_provider="bedrock"
)
print(response)
@ -753,7 +751,9 @@ def test_vertex_safety_settings(provider):
},
]
optional_params = get_optional_params(model="gemini-1.5-pro", custom_llm_provider=provider)
optional_params = get_optional_params(
model="gemini-1.5-pro", custom_llm_provider=provider
)
assert len(optional_params) == 1
@ -862,7 +862,9 @@ def _check_additional_properties(schema):
if isinstance(schema, dict):
# Remove the 'additionalProperties' key if it exists and is set to False
if "additionalProperties" in schema or "strict" in schema:
raise ValueError("additionalProperties and strict should not be in the schema")
raise ValueError(
"additionalProperties and strict should not be in the schema"
)
# Recursively process all dictionary values
for key, value in schema.items():
@ -1065,23 +1067,32 @@ def test_vertex_schema_field():
)
print(optional_params)
print(optional_params["tools"][0]["function_declarations"][0])
assert "$schema" not in optional_params["tools"][0]["function_declarations"][0]["parameters"]
assert (
"$schema"
not in optional_params["tools"][0]["function_declarations"][0]["parameters"]
)
def test_watsonx_tool_choice():
optional_params = get_optional_params(model="gemini-1.5-pro", custom_llm_provider="watsonx", tool_choice="auto")
optional_params = get_optional_params(
model="gemini-1.5-pro", custom_llm_provider="watsonx", tool_choice="auto"
)
print(optional_params)
assert optional_params["tool_choice_option"] == "auto"
def test_watsonx_text_top_k():
optional_params = get_optional_params(model="gemini-1.5-pro", custom_llm_provider="watsonx_text", top_k=10)
optional_params = get_optional_params(
model="gemini-1.5-pro", custom_llm_provider="watsonx_text", top_k=10
)
print(optional_params)
assert optional_params["top_k"] == 10
def test_together_ai_model_params():
optional_params = get_optional_params(model="together_ai", custom_llm_provider="together_ai", logprobs=1)
optional_params = get_optional_params(
model="together_ai", custom_llm_provider="together_ai", logprobs=1
)
print(optional_params)
assert optional_params["logprobs"] == 1
@ -1190,7 +1201,9 @@ def test_groq_response_format_json_schema():
def test_gemini_frequency_penalty():
optional_params = get_optional_params(model="gemini-1.5-flash", custom_llm_provider="gemini", frequency_penalty=0.5)
optional_params = get_optional_params(
model="gemini-1.5-flash", custom_llm_provider="gemini", frequency_penalty=0.5
)
assert optional_params["frequency_penalty"] == 0.5
@ -1994,7 +2007,9 @@ def test_validate_openai_optional_params_integration():
mock_response.usage.completion_tokens = 5
mock_response.usage.total_tokens = 15
mock_client.return_value.chat.completions.create.return_value = mock_response
mock_client.return_value.chat.completions.create.return_value = (
mock_response
)
# Call completion with more than 4 stop sequences
response = litellm.completion(

View file

@ -6100,6 +6100,42 @@ class TestFinalOptionalParamsLineRedaction:
assert "'temperature': 0.25" in printed
@pytest.mark.parametrize(
"custom_llm_provider, model",
[
("openai", "gpt-4.1"),
("openai", "gpt-4.1-mini"),
("openai", "gpt-4o"),
("openai", "gpt-4o-mini"),
("azure", "gpt-4.1"),
("azure", "gpt-4.1-mini"),
("azure", "gpt-4o"),
("azure", "gpt-4o-mini"),
("azure", "azure/eu/gpt-4o-2024-08-06"),
],
)
@pytest.mark.parametrize("request_drop, global_drop", [(True, False), (None, True)])
def test_unsupported_reasoning_effort_is_not_silently_dropped(
custom_llm_provider: str,
model: str,
request_drop: bool | None,
global_drop: bool,
monkeypatch: pytest.MonkeyPatch,
) -> None:
from litellm.utils import get_optional_params
# https://github.com/BerriAI/litellm/issues/40470, verified 2026-10-02.
# https://github.com/BerriAI/litellm/pull/44060#discussion_r4159472144, verified 2026-10-02.
monkeypatch.setattr(litellm, "drop_params", global_drop)
with pytest.raises(litellm.UnsupportedParamsError, match="reasoning_effort"):
get_optional_params(
model=model,
custom_llm_provider=custom_llm_provider,
reasoning_effort="low",
drop_params=request_drop,
)
class TestDropParamsStringCoercion:
@pytest.mark.parametrize("drop_params", ["true", "True", True])
def test_truthy_drop_params_drops_unsupported_temperature(self, drop_params, monkeypatch):