This commit is contained in:
devin-ai-integration[bot] 2026-08-27 19:37:32 -05:00 • committed by GitHub
commit 2eb52203b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 70 additions and 10 deletions

View file

@ -6,7 +6,8 @@ Why separate file? Make it easy to see how transformation works
Docs - https://docs.mistral.ai/api/
"""
from collections.abc import AsyncIterator, Coroutine, Iterator
from collections.abc import AsyncIterator, Coroutine, Iterator, Mapping
from types import MappingProxyType
from typing import Any, Final, Literal, cast, get_type_hints, overload
import httpx
@ -24,7 +25,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
class MistralConfig(OpenAIGPTConfig):
@ -98,12 +99,26 @@ class MistralConfig(OpenAIGPTConfig):
"parallel_tool_calls",
]
# Add reasoning support for magistral models
if "magistral" in model.lower():
if self._is_magistral_model(model):
supported_params.extend(["thinking", "reasoning_effort"])
elif self._supports_native_reasoning_effort(model):
supported_params.append("reasoning_effort")
return supported_params
@staticmethod
def _is_magistral_model(model: str) -> bool:
return "magistral" in model.lower()
@staticmethod
def _supports_native_reasoning_effort(model: str) -> bool:
"""
Mistral's hybrid reasoning models (mistral-medium-3-5 and newer) take ``reasoning_effort``
directly on /chat/completions, so it is forwarded as-is instead of being emulated with the
magistral system prompt: https://docs.mistral.ai/capabilities/reasoning
"""
return supports_reasoning(model=model, custom_llm_provider="mistral")
def _map_tool_choice(self, tool_choice: str) -> str:
if tool_choice == "auto" or tool_choice == "none":
return tool_choice
@ -168,16 +183,19 @@ class MistralConfig(OpenAIGPTConfig):
optional_params["extra_body"] = {"random_seed": value}
if param == "response_format":
optional_params["response_format"] = value
if param == "reasoning_effort" and "magistral" in model.lower():
# Flag that we need to add reasoning system prompt
optional_params["_add_reasoning_prompt"] = True
if param == "thinking" and "magistral" in model.lower():
# Flag that we need to add reasoning system prompt
optional_params["_add_reasoning_prompt"] = True
if param in ("reasoning_effort", "thinking"):
optional_params.update(self._map_reasoning_param(param=param, value=value, model=model))
if param == "parallel_tool_calls":
optional_params["parallel_tool_calls"] = value
return optional_params
def _map_reasoning_param(self, param: str, value: object, model: str) -> Mapping[str, object]:
if self._is_magistral_model(model):
return MappingProxyType({"_add_reasoning_prompt": True})
if param == "reasoning_effort" and value is not None and self._supports_native_reasoning_effort(model):
return MappingProxyType({"reasoning_effort": value})
return MappingProxyType({})
def _get_openai_compatible_provider_info(self, api_base: str | None, api_key: str | None) -> tuple[str, str | None]:
# mistral is openai compatible, we just need to set this to custom_openai and have the api_base be https://api.mistral.ai
api_base = (

View file

@ -307,6 +307,48 @@ class TestMistralReasoningSupport:
assert len(result["messages"]) == 1
assert result["messages"][0]["role"] == "user"
@pytest.mark.parametrize(
"model",
["mistral/mistral-medium-3-5", "mistral-medium-3-5", "mistral/mistral-medium-latest"],
)
def test_native_reasoning_model_supports_reasoning_effort(self, model):
"""Hybrid reasoning models take reasoning_effort natively, see https://github.com/BerriAI/litellm/issues/36407"""
supported_params = MistralConfig().get_supported_openai_params(model)
assert "reasoning_effort" in supported_params
assert "thinking" not in supported_params
def test_native_reasoning_effort_forwarded_to_request_body(self):
mistral_config = MistralConfig()
optional_params = mistral_config.map_openai_params(
non_default_params={"reasoning_effort": "high"},
optional_params={},
model="mistral/mistral-medium-3-5",
drop_params=False,
)
assert optional_params == {"reasoning_effort": "high"}
request = mistral_config.transform_request(
model="mistral/mistral-medium-3-5",
messages=[{"role": "user", "content": "What is 15 * 7?"}],
optional_params=optional_params,
litellm_params={},
headers={},
)
assert request["reasoning_effort"] == "high"
assert len(request["messages"]) == 1
def test_magistral_reasoning_effort_stays_prompt_based(self):
optional_params = MistralConfig().map_openai_params(
non_default_params={"reasoning_effort": "high"},
optional_params={},
model="mistral/magistral-medium-2506",
drop_params=False,
)
assert optional_params == {"_add_reasoning_prompt": True}
def test_case_insensitive_magistral_detection(self):
"""Test that magistral model detection is case-insensitive."""
mistral_config = MistralConfig()