From f93d80ea840e9b29f17601990a8bccec67f08f1f Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 16 Sep 2026 18:02:06 -0700 Subject: [PATCH] fix(mistral): forward reasoning_effort only on models that accept it --- litellm/llms/mistral/chat/transformation.py | 14 +++--- .../test_mistral_chat_transformation.py | 46 +++++++++++++++---- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/litellm/llms/mistral/chat/transformation.py b/litellm/llms/mistral/chat/transformation.py index 970da0582ae..807f201a94f 100644 --- a/litellm/llms/mistral/chat/transformation.py +++ b/litellm/llms/mistral/chat/transformation.py @@ -24,7 +24,7 @@ from litellm.secret_managers.main import get_secret_str from litellm.types.llms.mistral import MistralThinkingBlock, MistralToolCallMessage from litellm.types.llms.openai import AllMessageValues from litellm.types.utils import ModelResponse, ModelResponseStream -from litellm.utils import convert_to_model_response_object +from litellm.utils import convert_to_model_response_object, supports_reasoning if TYPE_CHECKING: import tiktoken @@ -87,7 +87,9 @@ class MistralConfig(OpenAIGPTConfig): return super().get_config() def get_supported_openai_params(self, model: str) -> list[str]: - supported_params: Final = [ + is_magistral: Final = "magistral" in model.lower() + accepts_reasoning_effort: Final = is_magistral or supports_reasoning(model=model, custom_llm_provider="mistral") + return [ "stream", "temperature", "top_p", @@ -99,14 +101,10 @@ class MistralConfig(OpenAIGPTConfig): "stop", "response_format", "parallel_tool_calls", - "reasoning_effort", + *(("thinking",) if is_magistral else ()), + *(("reasoning_effort",) if accepts_reasoning_effort else ()), ] - if "magistral" in model.lower(): - supported_params.append("thinking") - - return supported_params - def _map_tool_choice(self, tool_choice: str) -> str: if tool_choice == "auto" or tool_choice == "none": return tool_choice diff --git a/tests/test_litellm/llms/mistral/test_mistral_chat_transformation.py b/tests/test_litellm/llms/mistral/test_mistral_chat_transformation.py index edfaf352e1f..57a5f9ef2cd 100644 --- a/tests/test_litellm/llms/mistral/test_mistral_chat_transformation.py +++ b/tests/test_litellm/llms/mistral/test_mistral_chat_transformation.py @@ -51,11 +51,18 @@ class TestMistralReasoningSupport: assert "reasoning_effort" in supported_params assert "thinking" in supported_params - # Non-magistral models accept reasoning_effort (forwarded verbatim) but not thinking + # Non-magistral reasoning models accept reasoning_effort (forwarded verbatim) but not thinking + supported_params_reasoning = mistral_config.get_supported_openai_params( + "mistral/mistral-medium-latest" + ) + assert "reasoning_effort" in supported_params_reasoning + assert "thinking" not in supported_params_reasoning + + # Models Mistral rejects reasoning_effort on keep it unsupported, so drop_params still drops it supported_params_normal = mistral_config.get_supported_openai_params( "mistral/mistral-large-latest" ) - assert "reasoning_effort" in supported_params_normal + assert "reasoning_effort" not in supported_params_normal assert "thinking" not in supported_params_normal def test_map_openai_params_reasoning_effort(self): @@ -78,23 +85,46 @@ class TestMistralReasoningSupport: result_normal = mistral_config.map_openai_params( non_default_params={"reasoning_effort": "low"}, optional_params=optional_params_normal, - model="mistral/mistral-large-latest", + model="mistral/mistral-medium-latest", drop_params=False, ) assert "_add_reasoning_prompt" not in result_normal assert result_normal["reasoning_effort"] == "low" - def test_reasoning_effort_not_unsupported_for_non_magistral(self): - """Codex sends reasoning_effort to every model; Mistral must not raise UnsupportedParamsError.""" + @pytest.mark.parametrize( + ("model", "reasoning_effort"), + [("mistral-medium-latest", "high"), ("zai-glm-5-2", "xhigh")], + ) + def test_reasoning_effort_forwarded_verbatim_for_reasoning_models(self, model, reasoning_effort): + """Codex sends reasoning_effort to every model; Mistral reasoning models forward it as-is.""" import litellm optional_params = litellm.get_optional_params( - model="mistral-medium-latest", + model=model, custom_llm_provider="mistral", - reasoning_effort="medium", + reasoning_effort=reasoning_effort, ) - assert optional_params["reasoning_effort"] == "medium" + assert optional_params["reasoning_effort"] == reasoning_effort + + def test_reasoning_effort_stays_unsupported_for_non_reasoning_models(self): + """Mistral rejects reasoning_effort on codestral, so drop_params keeps dropping it there.""" + import litellm + + with pytest.raises(litellm.UnsupportedParamsError): + litellm.get_optional_params( + model="codestral-latest", + custom_llm_provider="mistral", + reasoning_effort="high", + ) + + dropped = litellm.get_optional_params( + model="codestral-latest", + custom_llm_provider="mistral", + reasoning_effort="high", + drop_params=True, + ) + assert "reasoning_effort" not in dropped def test_client_metadata_stripped_from_request(self): """client_metadata passed by Codex must not reach Mistral, whose schema rejects unknown fields."""