fix(mistral): forward reasoning_effort only on models that accept it

This commit is contained in:
mateo-berri 2026-09-16 18:02:06 -07:00
parent 65f4d31bb8
commit f93d80ea84
2 changed files with 44 additions and 16 deletions

View file

@ -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

View file

@ -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."""