mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
fix(mistral): send Magistral reasoning as supported native levels (#51329)
* fix(mistral): send reasoning_effort and thinking to magistral models as a level Mistral accepts magistral-small-latest and magistral-medium-latest now take reasoning_effort (none or high) as their reasoning switch, but LiteLLM dropped reasoning_effort and thinking for any magistral model and injected Magistral's deprecated reasoning system prompt instead, so streamed calls returned no reasoning and "none" still reasoned. Forward reasoning_effort rounded to the cost map's declared levels, map thinking onto it (disabled becomes none), drop the system prompt, and declare none and high on both magistral rows. * fix(mistral): send adaptive thinking to magistral as a reasoning level * fix(mistral): reject reasoning_effort strings that are not a level, and leave "default" to Mistral A string such as "HIGH" or "auto" went to Mistral as sent and Mistral answered 422, even with drop_params. It now takes the same path as a number: dropped with a warning under drop_params, a 400 without it. "default", which litellm.completion lists as a value, sends no reasoning_effort so Mistral applies its own default. * fix(mistral): preserve thinking when unsupported effort is dropped Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * refactor(mistral): raise reasoning failures at the public boundary Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(mistral): share fixed capabilities with warning regressions Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
8decbe4801
commit
ccb7f753c9
4 changed files with 294 additions and 422 deletions
|
|
@ -13,8 +13,8 @@ from typing import TYPE_CHECKING, Final, Literal, cast, get_type_hints, overload
|
|||
from urllib.parse import urlparse
|
||||
|
||||
import httpx
|
||||
from pydantic import TypeAdapter
|
||||
from typing_extensions import TypeIs # noqa: TID251 # TypeIs reaches typing only on 3.13
|
||||
from pydantic import JsonValue, TypeAdapter
|
||||
from typing_extensions import TypeIs, assert_never # noqa: TID251 # these are absent from typing on Python 3.10
|
||||
|
||||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
|
|
@ -28,10 +28,11 @@ from litellm.litellm_core_utils.prompt_templates.common_utils import (
|
|||
strip_none_values_from_message,
|
||||
)
|
||||
from litellm.litellm_core_utils.reasoning_effort_capability import (
|
||||
REASONING_EFFORT_ADVERTISEMENT_ORDER,
|
||||
declared_reasoning_efforts_for_model,
|
||||
nearest_declared_reasoning_effort,
|
||||
)
|
||||
from litellm.litellm_core_utils.reasoning_effort_utils import OPAQUE_VALUE, warn_dropped_reasoning
|
||||
from litellm.litellm_core_utils.reasoning_effort_utils import reasoning_effort_from_thinking, warn_dropped_reasoning
|
||||
from litellm.litellm_core_utils.url_utils import ensure_api_route
|
||||
from litellm.llms.openai.chat.gpt_transformation import (
|
||||
OpenAIChatCompletionStreamingHandler,
|
||||
|
|
@ -78,22 +79,29 @@ def _accepted_reasoning_effort(model: str, requested: str, custom_llm_provider:
|
|||
return accepted
|
||||
|
||||
|
||||
def _thinking_as_reasoning_effort(thinking: object, *, model: str, drop_params: bool) -> str | None:
|
||||
match thinking:
|
||||
case {"type": "disabled"}:
|
||||
return "none"
|
||||
case {"type": "adaptive"}:
|
||||
return "medium"
|
||||
case _:
|
||||
return reasoning_effort_from_thinking(thinking, model=model, drop_params=drop_params)
|
||||
|
||||
|
||||
_NO_TOOL_CALL_TURN: Final[tuple[int, frozenset[str]]] = (-1, frozenset())
|
||||
_MISTRAL_REQUEST_ROUTING: Final = TypeAdapter(MistralRequestRouting)
|
||||
_MISTRAL_CACHE_KEY_PARAMS: Final = TypeAdapter(MistralCacheKeyParams)
|
||||
_MISTRAL_REASONING_EFFORT_PARAMS: Final = TypeAdapter(MistralReasoningEffortParams)
|
||||
_MISTRAL_FILE_PART: Final = TypeAdapter(MistralFilePart)
|
||||
_DECODED_JSON: Final = TypeAdapter(object)
|
||||
_REASONING_PROMPT_REASON: Final = "litellm sends Magistral a reasoning system prompt instead of the requested level."
|
||||
_NON_LEVEL_EFFORT_REASON: Final = (
|
||||
"the Mistral API takes it only as a level such as 'high', so Mistral applies its default effort."
|
||||
)
|
||||
_REASONING_LEVELS: Final = frozenset(REASONING_EFFORT_ADVERTISEMENT_ORDER)
|
||||
_PROVIDER_DEFAULT_EFFORT: Final = "default"
|
||||
_NON_LEVEL_EFFORT_REASON: Final = "the Mistral API takes it only as a level such as 'high'."
|
||||
_UNUSABLE_TOOL_CHOICE_REASON: Final = (
|
||||
"the Mistral API takes tool_choice only as 'auto', 'none', 'any', 'required', "
|
||||
"or {'type': 'function', 'function': {'name': <tool name>}}."
|
||||
)
|
||||
_DOCUMENTED_MAGISTRAL_EFFORTS: Final = frozenset({"low", "medium", "high"})
|
||||
_REASONING_ON_THINKING_TYPES: Final = frozenset({"enabled", "adaptive"})
|
||||
|
||||
|
||||
def _reasoning_level_and_summary(reasoning_effort: object) -> tuple[object, object]:
|
||||
|
|
@ -103,20 +111,20 @@ def _reasoning_level_and_summary(reasoning_effort: object) -> tuple[object, obje
|
|||
return requested.get("effort"), requested.get("summary")
|
||||
|
||||
|
||||
def _warn_unless_reasoning_prompt_honors(param: str, value: object, model: str) -> None:
|
||||
if param == "reasoning_effort" and isinstance(value, str) and value in _DOCUMENTED_MAGISTRAL_EFFORTS:
|
||||
return
|
||||
thinking: Final = as_str_mapping(value)
|
||||
thinking_type: Final = thinking.get("type") if thinking is not None else None
|
||||
if param == "thinking" and isinstance(thinking_type, str) and thinking_type in _REASONING_ON_THINKING_TYPES:
|
||||
return
|
||||
warn_dropped_reasoning(param, value, model, _REASONING_PROMPT_REASON, drop_params_gated=False)
|
||||
|
||||
|
||||
def _is_json_array(value: object) -> TypeIs[Sequence[object]]: # guard-ok: a decoded JSON array is a list
|
||||
return isinstance(value, list)
|
||||
|
||||
|
||||
def _public_reasoning_level(result: str | UnsupportedParamsError | None) -> str | None:
|
||||
match result:
|
||||
case UnsupportedParamsError():
|
||||
raise result
|
||||
case str() | None:
|
||||
return result
|
||||
case _:
|
||||
assert_never(result)
|
||||
|
||||
|
||||
class MistralConfig(OpenAIGPTConfig):
|
||||
"""
|
||||
Reference: https://docs.mistral.ai/api/
|
||||
|
|
@ -238,33 +246,6 @@ class MistralConfig(OpenAIGPTConfig):
|
|||
model=model,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _get_mistral_reasoning_system_prompt() -> str:
|
||||
"""
|
||||
Returns the system prompt for Mistral reasoning models.
|
||||
Based on Mistral's documentation: https://huggingface.co/mistralai/Magistral-Small-2506
|
||||
|
||||
Mistral recommends the following system prompt for reasoning:
|
||||
"""
|
||||
return """
|
||||
<s>[SYSTEM_PROMPT]system_prompt
|
||||
A user will ask you to solve a task. You should first draft your thinking process (inner monologue) until you have derived the final answer. Afterwards, write a self-contained summary of your thoughts (i.e. your summary should be succinct but contain all the critical steps you needed to reach the conclusion). You should use Markdown to format your response. Write both your thoughts and summary in the same language as the task posed by the user. NEVER use \boxed{} in your response.
|
||||
|
||||
Your thinking process must follow the template below:
|
||||
<think>
|
||||
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct answer.
|
||||
</think>
|
||||
|
||||
Here, provide a concise summary that reflects your reasoning and presents a clear final answer to the user. Don't mention that this is a summary.
|
||||
|
||||
Problem:
|
||||
|
||||
[/SYSTEM_PROMPT][INST]user_message[/INST]<think>
|
||||
reasoning_traces
|
||||
</think>
|
||||
assistant_response</s>[INST]user_message[/INST]
|
||||
"""
|
||||
|
||||
def map_openai_params(
|
||||
self,
|
||||
non_default_params: dict,
|
||||
|
|
@ -272,6 +253,15 @@ class MistralConfig(OpenAIGPTConfig):
|
|||
model: str,
|
||||
drop_params: bool,
|
||||
) -> dict:
|
||||
requested: Final = _MISTRAL_REASONING_EFFORT_PARAMS.validate_python(non_default_params)
|
||||
reasoning_level: Final = (
|
||||
_public_reasoning_level(self._reasoning_level_to_send(model, requested, drop_params))
|
||||
if "reasoning_effort" in requested
|
||||
else None
|
||||
)
|
||||
leaves_level_to_mistral: Final = (
|
||||
_reasoning_level_and_summary(requested.get("reasoning_effort"))[0] == _PROVIDER_DEFAULT_EFFORT
|
||||
)
|
||||
for param, value in non_default_params.items():
|
||||
if param == "max_tokens":
|
||||
optional_params["max_tokens"] = value
|
||||
|
|
@ -296,42 +286,42 @@ class MistralConfig(OpenAIGPTConfig):
|
|||
optional_params["tool_choice"] = tool_choice
|
||||
if param == "seed":
|
||||
optional_params["extra_body"] = {"random_seed": value}
|
||||
if param == "reasoning_effort" and reasoning_level is not None:
|
||||
optional_params["reasoning_effort"] = reasoning_level
|
||||
if (
|
||||
param == "reasoning_effort"
|
||||
and "magistral" not in model.lower()
|
||||
and (
|
||||
reasoning_level := self._reasoning_level_to_send(
|
||||
model, _MISTRAL_REASONING_EFFORT_PARAMS.validate_python(non_default_params), drop_params
|
||||
)
|
||||
)
|
||||
param == "thinking"
|
||||
and reasoning_level is None
|
||||
and not leaves_level_to_mistral
|
||||
and (thinking_effort := _thinking_as_reasoning_effort(value, model=model, drop_params=drop_params))
|
||||
is not None
|
||||
):
|
||||
optional_params["reasoning_effort"] = reasoning_level
|
||||
if param in ("reasoning_effort", "thinking") and "magistral" in model.lower():
|
||||
# Flag that we need to add reasoning system prompt
|
||||
optional_params["_add_reasoning_prompt"] = True
|
||||
_warn_unless_reasoning_prompt_honors(param, OPAQUE_VALUE.validate_python(value), model)
|
||||
optional_params["reasoning_effort"] = _accepted_reasoning_effort(
|
||||
model, thinking_effort, self.custom_llm_provider
|
||||
)
|
||||
if param in ("temperature", "top_p", "stop", "response_format", "parallel_tool_calls", "prompt_cache_key"):
|
||||
optional_params[param] = value
|
||||
return optional_params
|
||||
|
||||
def _reasoning_level_to_send(
|
||||
self, model: str, requested: MistralReasoningEffortParams, drop_params: bool
|
||||
) -> str | None:
|
||||
) -> str | UnsupportedParamsError | None:
|
||||
requested_effort: Final = requested.get("reasoning_effort")
|
||||
level, summary = _reasoning_level_and_summary(requested_effort)
|
||||
leaves_level_to_mistral: Final = level == _PROVIDER_DEFAULT_EFFORT
|
||||
sendable: Final = (
|
||||
_accepted_reasoning_effort(model, level, self.custom_llm_provider) if isinstance(level, str) else None
|
||||
_accepted_reasoning_effort(model, level, self.custom_llm_provider)
|
||||
if isinstance(level, str) and level in _REASONING_LEVELS
|
||||
else None
|
||||
)
|
||||
if sendable is not None and summary is None:
|
||||
if summary is None and (sendable is not None or leaves_level_to_mistral):
|
||||
return sendable
|
||||
if drop_params or litellm.drop_params is True:
|
||||
if sendable is None and level is not None:
|
||||
if sendable is None and level is not None and not leaves_level_to_mistral:
|
||||
warn_dropped_reasoning(
|
||||
"reasoning_effort", requested_effort, model, _NON_LEVEL_EFFORT_REASON, drop_params_gated=True
|
||||
)
|
||||
return sendable
|
||||
raise UnsupportedParamsError(
|
||||
return UnsupportedParamsError(
|
||||
message=(
|
||||
f"{self.custom_llm_provider} does not support parameters: ['reasoning_effort'], for model={model}, "
|
||||
"since the Mistral API takes it only as a level such as 'high', with no reasoning summary. "
|
||||
|
|
@ -545,55 +535,8 @@ class MistralConfig(OpenAIGPTConfig):
|
|||
"""
|
||||
return [self._with_mistral_file_ids(m) for m in messages]
|
||||
|
||||
def _add_reasoning_system_prompt_if_needed(
|
||||
self, messages: list[AllMessageValues], optional_params: dict
|
||||
) -> list[AllMessageValues]:
|
||||
"""
|
||||
Add reasoning system prompt for Mistral magistral models when reasoning_effort is specified.
|
||||
"""
|
||||
if not optional_params.get("_add_reasoning_prompt", False):
|
||||
return messages
|
||||
|
||||
# Check if there's already a system message
|
||||
has_system_message: Final = any(msg.get("role") == "system" for msg in messages)
|
||||
|
||||
if has_system_message:
|
||||
# Prepend reasoning instructions to existing system message
|
||||
for i, msg in enumerate(messages):
|
||||
if msg.get("role") == "system":
|
||||
existing_content = msg.get("content", "")
|
||||
reasoning_prompt = self._get_mistral_reasoning_system_prompt()
|
||||
|
||||
# Handle both string and list content, preserving original format
|
||||
if isinstance(existing_content, str):
|
||||
# String content - prepend reasoning prompt
|
||||
new_content: str | list = f"{reasoning_prompt}\n\n{existing_content}"
|
||||
elif isinstance(existing_content, list):
|
||||
# List content - prepend reasoning prompt as text block
|
||||
new_content = [{"type": "text", "text": reasoning_prompt + "\n\n"}] + existing_content
|
||||
else:
|
||||
# Fallback for any other type - convert to string
|
||||
new_content = f"{reasoning_prompt}\n\n{existing_content}"
|
||||
|
||||
messages[i] = cast(AllMessageValues, {**msg, "content": new_content})
|
||||
break
|
||||
else:
|
||||
# Add new system message with reasoning instructions
|
||||
reasoning_message: Final[AllMessageValues] = cast(
|
||||
AllMessageValues,
|
||||
{
|
||||
"role": "system",
|
||||
"content": self._get_mistral_reasoning_system_prompt(),
|
||||
},
|
||||
)
|
||||
messages = [reasoning_message] + messages
|
||||
|
||||
# Remove the internal flag
|
||||
optional_params.pop("_add_reasoning_prompt", None)
|
||||
return messages
|
||||
|
||||
@classmethod
|
||||
def _clean_tool_schema_for_mistral(cls, tools: list) -> list:
|
||||
def _clean_tool_schema_for_mistral(cls, tools: list[JsonValue]) -> list[JsonValue]:
|
||||
"""
|
||||
Clean tool schemas to remove fields that cause issues with Mistral API.
|
||||
|
||||
|
|
@ -953,15 +896,10 @@ class MistralConfig(OpenAIGPTConfig):
|
|||
) -> dict:
|
||||
"""
|
||||
Transform the overall request to be sent to the API.
|
||||
For magistral models, adds reasoning system prompt when reasoning_effort is specified.
|
||||
|
||||
Returns:
|
||||
dict: The transformed request. Sent as the body of the API call.
|
||||
"""
|
||||
# Add reasoning system prompt if needed (for magistral models)
|
||||
if "magistral" in model.lower() and optional_params.get("_add_reasoning_prompt", False):
|
||||
messages = self._add_reasoning_system_prompt_if_needed(messages, optional_params)
|
||||
|
||||
cache_key_params: Final = _MISTRAL_CACHE_KEY_PARAMS.validate_python(optional_params)
|
||||
withheld_params: Final = (
|
||||
("client_metadata", "prompt_cache_key")
|
||||
|
|
|
|||
|
|
@ -44279,6 +44279,10 @@
|
|||
"max_output_tokens": 262144,
|
||||
"mode": "chat",
|
||||
"output_cost_per_token": 7.5e-06,
|
||||
"reasoning_effort_levels": [
|
||||
"none",
|
||||
"high"
|
||||
],
|
||||
"source": "https://docs.mistral.ai/models/model-cards/mistral-medium-3-5-26-04",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_function_calling": true,
|
||||
|
|
@ -44295,6 +44299,10 @@
|
|||
"max_output_tokens": 262144,
|
||||
"mode": "chat",
|
||||
"output_cost_per_token": 6e-07,
|
||||
"reasoning_effort_levels": [
|
||||
"none",
|
||||
"high"
|
||||
],
|
||||
"source": "https://docs.mistral.ai/models/model-cards/mistral-small-4-0-26-03",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_function_calling": true,
|
||||
|
|
|
|||
|
|
@ -44279,6 +44279,10 @@
|
|||
"max_output_tokens": 262144,
|
||||
"mode": "chat",
|
||||
"output_cost_per_token": 7.5e-06,
|
||||
"reasoning_effort_levels": [
|
||||
"none",
|
||||
"high"
|
||||
],
|
||||
"source": "https://docs.mistral.ai/models/model-cards/mistral-medium-3-5-26-04",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_function_calling": true,
|
||||
|
|
@ -44295,6 +44299,10 @@
|
|||
"max_output_tokens": 262144,
|
||||
"mode": "chat",
|
||||
"output_cost_per_token": 6e-07,
|
||||
"reasoning_effort_levels": [
|
||||
"none",
|
||||
"high"
|
||||
],
|
||||
"source": "https://docs.mistral.ai/models/model-cards/mistral-small-4-0-26-03",
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_function_calling": true,
|
||||
|
|
|
|||
|
|
@ -46,6 +46,39 @@ async def test_mistral_chat_transformation():
|
|||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fixed_reasoning_capabilities(local_model_cost_map: None, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
import litellm
|
||||
|
||||
declarations: Final = (
|
||||
("mistral-medium-latest", ("none", "high")),
|
||||
("mistral-small-latest", ("none", "high")),
|
||||
("mistral-vibe-cli-latest", ("none", "high")),
|
||||
("magistral-small-latest", ("none", "high")),
|
||||
("magistral-medium-latest", ("none", "high")),
|
||||
("zai-glm-5", ("none", "low", "high", "max")),
|
||||
("zai-glm-5-2", ("none", "low", "medium", "high", "xhigh", "max")),
|
||||
)
|
||||
for model, levels in declarations:
|
||||
monkeypatch.setitem(
|
||||
litellm.model_cost,
|
||||
f"mistral/{model}",
|
||||
{
|
||||
"litellm_provider": "mistral",
|
||||
"mode": "chat",
|
||||
"supports_reasoning": True,
|
||||
"reasoning_effort_levels": levels,
|
||||
},
|
||||
)
|
||||
for model in ("codestral-latest", "mistral-large-latest"):
|
||||
monkeypatch.setitem(
|
||||
litellm.model_cost,
|
||||
f"mistral/{model}",
|
||||
{"litellm_provider": "mistral", "mode": "chat", "supports_reasoning": False},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("fixed_reasoning_capabilities")
|
||||
class TestMistralReasoningSupport:
|
||||
"""Test suite for Mistral Magistral reasoning functionality."""
|
||||
|
||||
|
|
@ -66,32 +99,6 @@ class TestMistralReasoningSupport:
|
|||
assert "reasoning_effort" not in supported_params_normal
|
||||
assert "thinking" not in supported_params_normal
|
||||
|
||||
def test_map_openai_params_reasoning_effort(self, local_model_cost_map):
|
||||
"""Test that reasoning_effort parameter is properly mapped for magistral models."""
|
||||
mistral_config = MistralConfig()
|
||||
|
||||
# Test reasoning_effort mapping for magistral model
|
||||
optional_params = {}
|
||||
result = mistral_config.map_openai_params(
|
||||
non_default_params={"reasoning_effort": "low"},
|
||||
optional_params=optional_params,
|
||||
model="mistral/magistral-medium-2506",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert result.get("_add_reasoning_prompt") is True
|
||||
|
||||
optional_params_normal = {}
|
||||
result_normal = mistral_config.map_openai_params(
|
||||
non_default_params={"reasoning_effort": "low"},
|
||||
optional_params=optional_params_normal,
|
||||
model="mistral/mistral-medium-latest",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert "_add_reasoning_prompt" not in result_normal
|
||||
assert result_normal["reasoning_effort"] == "high"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("model", "requested", "sent"),
|
||||
[
|
||||
|
|
@ -102,18 +109,28 @@ class TestMistralReasoningSupport:
|
|||
("mistral-medium-latest", "xhigh", "high"),
|
||||
("mistral-small-latest", "medium", "high"),
|
||||
("mistral-vibe-cli-latest", "medium", "high"),
|
||||
("magistral-small-latest", "high", "high"),
|
||||
("magistral-small-latest", "none", "none"),
|
||||
("magistral-small-latest", "low", "high"),
|
||||
("magistral-small-latest", "medium", "high"),
|
||||
("magistral-medium-latest", "minimal", "high"),
|
||||
("magistral-medium-latest", "none", "none"),
|
||||
("zai-glm-5", "none", "none"),
|
||||
("zai-glm-5", "minimal", "low"),
|
||||
("zai-glm-5", "medium", "high"),
|
||||
("zai-glm-5", "xhigh", "max"),
|
||||
("zai-glm-5-2", "medium", "medium"),
|
||||
("zai-glm-5-2", "xhigh", "xhigh"),
|
||||
("zai-glm-5-2", "max", "max"),
|
||||
("zai-glm-5-2", "minimal", "low"),
|
||||
],
|
||||
)
|
||||
def test_reasoning_effort_is_sent_as_a_level_the_model_accepts(self, local_model_cost_map, model, requested, sent):
|
||||
def test_reasoning_effort_is_sent_as_a_level_the_model_accepts(
|
||||
self, local_model_cost_map: None, model: str, requested: str, sent: str
|
||||
) -> None:
|
||||
import litellm
|
||||
|
||||
optional_params = litellm.get_optional_params(
|
||||
optional_params: Final = litellm.get_optional_params(
|
||||
model=model,
|
||||
custom_llm_provider="mistral",
|
||||
reasoning_effort=requested,
|
||||
|
|
@ -128,10 +145,12 @@ class TestMistralReasoningSupport:
|
|||
({"summary": "auto"}, True, {}),
|
||||
],
|
||||
)
|
||||
def test_reasoning_effort_object_is_sent_as_its_level(self, local_model_cost_map, requested, drop_params, sent):
|
||||
def test_reasoning_effort_object_is_sent_as_its_level(
|
||||
self, local_model_cost_map: None, requested: object, drop_params: bool, sent: Mapping[str, str]
|
||||
) -> None:
|
||||
import litellm
|
||||
|
||||
optional_params = litellm.get_optional_params(
|
||||
optional_params: Final = litellm.get_optional_params(
|
||||
model="mistral-small-latest",
|
||||
custom_llm_provider="mistral",
|
||||
reasoning_effort=requested,
|
||||
|
|
@ -140,7 +159,9 @@ class TestMistralReasoningSupport:
|
|||
assert {key: optional_params[key] for key in ("reasoning_effort",) if key in optional_params} == sent
|
||||
|
||||
@pytest.mark.parametrize("requested", [{"effort": "high", "summary": "auto"}, {"summary": "auto"}])
|
||||
def test_reasoning_effort_object_mistral_cannot_take_is_rejected(self, local_model_cost_map, requested):
|
||||
def test_reasoning_effort_object_mistral_cannot_take_is_rejected(
|
||||
self, local_model_cost_map: None, requested: object
|
||||
) -> None:
|
||||
import litellm
|
||||
|
||||
with pytest.raises(litellm.UnsupportedParamsError, match=r"\['reasoning_effort'\]"):
|
||||
|
|
@ -151,8 +172,8 @@ class TestMistralReasoningSupport:
|
|||
)
|
||||
|
||||
def test_reasoning_effort_is_forwarded_verbatim_when_the_map_declares_no_levels(
|
||||
self, local_model_cost_map, monkeypatch
|
||||
):
|
||||
self, local_model_cost_map: None, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
import litellm
|
||||
|
||||
monkeypatch.setitem(
|
||||
|
|
@ -160,14 +181,14 @@ class TestMistralReasoningSupport:
|
|||
"mistral/undeclared-reasoner",
|
||||
{"litellm_provider": "mistral", "mode": "chat", "supports_reasoning": True},
|
||||
)
|
||||
optional_params = litellm.get_optional_params(
|
||||
optional_params: Final = litellm.get_optional_params(
|
||||
model="undeclared-reasoner",
|
||||
custom_llm_provider="mistral",
|
||||
reasoning_effort="medium",
|
||||
)
|
||||
assert optional_params["reasoning_effort"] == "medium"
|
||||
|
||||
def test_reasoning_effort_stays_unsupported_for_non_reasoning_models(self):
|
||||
def test_reasoning_effort_stays_unsupported_for_non_reasoning_models(self) -> None:
|
||||
import litellm
|
||||
|
||||
with pytest.raises(litellm.UnsupportedParamsError):
|
||||
|
|
@ -177,7 +198,7 @@ class TestMistralReasoningSupport:
|
|||
reasoning_effort="high",
|
||||
)
|
||||
|
||||
dropped = litellm.get_optional_params(
|
||||
dropped: Final = litellm.get_optional_params(
|
||||
model="codestral-latest",
|
||||
custom_llm_provider="mistral",
|
||||
reasoning_effort="high",
|
||||
|
|
@ -199,213 +220,111 @@ class TestMistralReasoningSupport:
|
|||
assert "client_metadata" not in request
|
||||
assert request["temperature"] == 0.2
|
||||
|
||||
def test_map_openai_params_thinking(self):
|
||||
"""Test that thinking parameter is properly mapped for magistral models."""
|
||||
mistral_config = MistralConfig()
|
||||
@pytest.mark.parametrize(
|
||||
("thinking", "sent"),
|
||||
[
|
||||
({"type": "enabled", "budget_tokens": 1024}, "high"),
|
||||
({"type": "enabled", "budget_tokens": 100}, "high"),
|
||||
({"type": "disabled"}, "none"),
|
||||
({"type": "adaptive"}, "high"),
|
||||
({"type": "adaptive", "display": "summarized"}, "high"),
|
||||
],
|
||||
)
|
||||
def test_magistral_sends_thinking_as_a_reasoning_effort_the_model_accepts(
|
||||
self, local_model_cost_map: None, thinking: object, sent: str
|
||||
) -> None:
|
||||
import litellm
|
||||
|
||||
# Test thinking mapping for magistral model
|
||||
optional_params = {}
|
||||
result = mistral_config.map_openai_params(
|
||||
non_default_params={"thinking": {"budget": 1000}},
|
||||
optional_params=optional_params,
|
||||
model="mistral/magistral-small-2506",
|
||||
drop_params=False,
|
||||
optional_params: Final = litellm.get_optional_params(
|
||||
model="magistral-small-latest",
|
||||
custom_llm_provider="mistral",
|
||||
thinking=thinking,
|
||||
)
|
||||
assert optional_params["reasoning_effort"] == sent
|
||||
assert "thinking" not in optional_params
|
||||
|
||||
@pytest.mark.parametrize("thinking_first", (False, True))
|
||||
@pytest.mark.parametrize(
|
||||
("requested", "thinking", "sent"),
|
||||
(
|
||||
("HIGH", {"type": "disabled"}, "none"),
|
||||
(5, {"type": "disabled"}, "none"),
|
||||
({"effort": None}, {"type": "disabled"}, "none"),
|
||||
("HIGH", {"type": "enabled", "budget_tokens": 1024}, "high"),
|
||||
("HIGH", {"type": "adaptive"}, "high"),
|
||||
("high", {"type": "disabled"}, "high"),
|
||||
({"effort": "high", "summary": "auto"}, {"type": "disabled"}, "high"),
|
||||
("default", {"type": "disabled"}, None),
|
||||
({"effort": "default", "summary": "auto"}, {"type": "disabled"}, None),
|
||||
),
|
||||
)
|
||||
def test_magistral_uses_thinking_only_when_effort_was_dropped(
|
||||
self,
|
||||
local_model_cost_map: None,
|
||||
requested: object,
|
||||
thinking: Mapping[str, object],
|
||||
sent: str | None,
|
||||
thinking_first: bool,
|
||||
) -> None:
|
||||
params: Final = (
|
||||
{"thinking": thinking, "reasoning_effort": requested}
|
||||
if thinking_first
|
||||
else {"reasoning_effort": requested, "thinking": thinking}
|
||||
)
|
||||
result: Final = MistralConfig().map_openai_params(
|
||||
non_default_params=params,
|
||||
optional_params={},
|
||||
model="magistral-small-latest",
|
||||
drop_params=True,
|
||||
)
|
||||
expected: Final = {} if sent is None else {"reasoning_effort": sent}
|
||||
assert result == expected
|
||||
|
||||
def test_magistral_prefers_reasoning_effort_over_thinking(self, local_model_cost_map: None) -> None:
|
||||
import litellm
|
||||
|
||||
optional_params: Final = litellm.get_optional_params(
|
||||
model="magistral-small-latest",
|
||||
custom_llm_provider="mistral",
|
||||
reasoning_effort="none",
|
||||
thinking={"type": "enabled", "budget_tokens": 4096},
|
||||
)
|
||||
assert optional_params["reasoning_effort"] == "none"
|
||||
|
||||
def test_magistral_thinking_without_a_type_sends_no_reasoning_effort(self, local_model_cost_map: None) -> None:
|
||||
import litellm
|
||||
|
||||
optional_params: Final = litellm.get_optional_params(
|
||||
model="magistral-small-latest",
|
||||
custom_llm_provider="mistral",
|
||||
thinking={"budget_tokens": 4096},
|
||||
)
|
||||
assert "reasoning_effort" not in optional_params
|
||||
assert "thinking" not in optional_params
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"reasoning", [{"reasoning_effort": "medium"}, {"thinking": {"type": "enabled", "budget_tokens": 1024}}]
|
||||
)
|
||||
def test_magistral_request_carries_reasoning_effort_and_the_callers_messages_only(
|
||||
self, local_model_cost_map: None, reasoning: Mapping[str, object]
|
||||
) -> None:
|
||||
import litellm
|
||||
|
||||
messages: Final[list[AllMessageValues]] = [{"role": "user", "content": "What is 17*23?"}]
|
||||
optional_params: Final = litellm.get_optional_params(
|
||||
model="magistral-small-latest", custom_llm_provider="mistral", **reasoning
|
||||
)
|
||||
|
||||
assert result.get("_add_reasoning_prompt") is True
|
||||
|
||||
def test_get_mistral_reasoning_system_prompt(self):
|
||||
"""Test that the reasoning system prompt is properly formatted."""
|
||||
prompt = MistralConfig._get_mistral_reasoning_system_prompt()
|
||||
assert isinstance(prompt, str)
|
||||
assert len(prompt) > 50 # Ensure it's not empty
|
||||
|
||||
def test_add_reasoning_system_prompt_no_existing_system_message(self):
|
||||
"""Test adding reasoning system prompt when no system message exists."""
|
||||
mistral_config = MistralConfig()
|
||||
|
||||
messages = [{"role": "user", "content": "What is 2+2?"}]
|
||||
optional_params = {"_add_reasoning_prompt": True}
|
||||
|
||||
result = mistral_config._add_reasoning_system_prompt_if_needed(messages, optional_params)
|
||||
|
||||
# Should add a new system message at the beginning
|
||||
assert len(result) == 2
|
||||
assert result[0]["role"] == "system"
|
||||
assert "<think>" in result[0]["content"]
|
||||
assert result[1]["role"] == "user"
|
||||
assert result[1]["content"] == "What is 2+2?"
|
||||
|
||||
# Should remove the internal flag
|
||||
assert "_add_reasoning_prompt" not in optional_params
|
||||
|
||||
def test_add_reasoning_system_prompt_with_existing_system_message(self):
|
||||
"""Test adding reasoning system prompt when system message already exists."""
|
||||
mistral_config = MistralConfig()
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": "You are a helpful assistant."},
|
||||
{"role": "user", "content": "What is 2+2?"},
|
||||
]
|
||||
optional_params = {"_add_reasoning_prompt": True}
|
||||
|
||||
result = mistral_config._add_reasoning_system_prompt_if_needed(messages, optional_params)
|
||||
|
||||
# Should modify existing system message
|
||||
assert len(result) == 2
|
||||
assert result[0]["role"] == "system"
|
||||
assert "<think>" in result[0]["content"]
|
||||
assert "You are a helpful assistant." in result[0]["content"]
|
||||
assert result[1]["role"] == "user"
|
||||
|
||||
# Should remove the internal flag
|
||||
assert "_add_reasoning_prompt" not in optional_params
|
||||
|
||||
def test_add_reasoning_system_prompt_with_existing_list_content(self):
|
||||
"""Test adding reasoning system prompt when system message has list content."""
|
||||
mistral_config = MistralConfig()
|
||||
|
||||
messages = [
|
||||
{
|
||||
"role": "system",
|
||||
"content": [
|
||||
{"type": "text", "text": "You are a helpful assistant."},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "You always provide detailed explanations.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{"role": "user", "content": "What is 2+2?"},
|
||||
]
|
||||
optional_params = {"_add_reasoning_prompt": True}
|
||||
|
||||
result = mistral_config._add_reasoning_system_prompt_if_needed(messages, optional_params)
|
||||
|
||||
# Should modify existing system message preserving list format
|
||||
assert len(result) == 2
|
||||
assert result[0]["role"] == "system"
|
||||
assert isinstance(result[0]["content"], list)
|
||||
|
||||
# First item should be the reasoning prompt
|
||||
assert result[0]["content"][0]["type"] == "text"
|
||||
assert "<think>" in result[0]["content"][0]["text"]
|
||||
|
||||
# Original content should be preserved
|
||||
assert "You are a helpful assistant." in result[0]["content"][1]["text"]
|
||||
assert "You always provide detailed explanations." in result[0]["content"][2]["text"]
|
||||
|
||||
assert result[1]["role"] == "user"
|
||||
|
||||
# Should remove the internal flag
|
||||
assert "_add_reasoning_prompt" not in optional_params
|
||||
|
||||
def test_add_reasoning_system_prompt_preserves_content_types(self):
|
||||
"""Test that reasoning prompt preserves original content types (string vs list)."""
|
||||
mistral_config = MistralConfig()
|
||||
|
||||
# Test with string content
|
||||
string_messages = [
|
||||
{"role": "system", "content": "You are helpful."},
|
||||
{"role": "user", "content": "Hello"},
|
||||
]
|
||||
string_params = {"_add_reasoning_prompt": True}
|
||||
|
||||
string_result = mistral_config._add_reasoning_system_prompt_if_needed(string_messages, string_params)
|
||||
assert isinstance(string_result[0]["content"], str)
|
||||
assert "<think>" in string_result[0]["content"]
|
||||
assert "You are helpful." in string_result[0]["content"]
|
||||
|
||||
# Test with list content
|
||||
list_messages = [
|
||||
{
|
||||
"role": "system",
|
||||
"content": [{"type": "text", "text": "You are helpful."}],
|
||||
},
|
||||
{"role": "user", "content": "Hello"},
|
||||
]
|
||||
list_params = {"_add_reasoning_prompt": True}
|
||||
|
||||
list_result = mistral_config._add_reasoning_system_prompt_if_needed(list_messages, list_params)
|
||||
assert isinstance(list_result[0]["content"], list)
|
||||
assert list_result[0]["content"][0]["type"] == "text"
|
||||
assert "<think>" in list_result[0]["content"][0]["text"]
|
||||
assert "You are helpful." in list_result[0]["content"][1]["text"]
|
||||
|
||||
def test_add_reasoning_system_prompt_no_flag(self):
|
||||
"""Test that no modification happens when _add_reasoning_prompt flag is not set."""
|
||||
mistral_config = MistralConfig()
|
||||
|
||||
messages = [{"role": "user", "content": "What is 2+2?"}]
|
||||
optional_params = {}
|
||||
|
||||
result = mistral_config._add_reasoning_system_prompt_if_needed(messages, optional_params)
|
||||
|
||||
# Should return messages unchanged
|
||||
assert result == messages
|
||||
assert len(result) == 1
|
||||
|
||||
def test_transform_request_magistral_with_reasoning(self):
|
||||
"""Test transform_request method for magistral model with reasoning."""
|
||||
mistral_config = MistralConfig()
|
||||
|
||||
messages = [{"role": "user", "content": "What is 15 * 7?"}]
|
||||
optional_params = {"_add_reasoning_prompt": True}
|
||||
|
||||
result = mistral_config.transform_request(
|
||||
model="mistral/magistral-medium-2506",
|
||||
request: Final = MistralConfig().transform_request(
|
||||
model="magistral-small-latest",
|
||||
messages=messages,
|
||||
optional_params=optional_params,
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
# Should have added system message
|
||||
assert len(result["messages"]) == 2
|
||||
assert result["messages"][0]["role"] == "system"
|
||||
assert "<think>" in result["messages"][0]["content"]
|
||||
assert result["messages"][1]["role"] == "user"
|
||||
|
||||
# Should remove internal flag from optional_params
|
||||
assert "_add_reasoning_prompt" not in result
|
||||
|
||||
def test_transform_request_magistral_without_reasoning(self):
|
||||
"""Test transform_request method for magistral model without reasoning."""
|
||||
mistral_config = MistralConfig()
|
||||
|
||||
messages = [{"role": "user", "content": "What is 15 * 7?"}]
|
||||
optional_params = {}
|
||||
|
||||
result = mistral_config.transform_request(
|
||||
model="mistral/magistral-medium-2506",
|
||||
messages=messages,
|
||||
optional_params=optional_params,
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
# Should not modify messages
|
||||
assert len(result["messages"]) == 1
|
||||
assert result["messages"][0]["role"] == "user"
|
||||
|
||||
def test_transform_request_non_magistral_with_reasoning_params(self):
|
||||
"""Test that non-magistral models ignore reasoning parameters."""
|
||||
mistral_config = MistralConfig()
|
||||
|
||||
messages = [{"role": "user", "content": "What is 15 * 7?"}]
|
||||
optional_params = {"_add_reasoning_prompt": True}
|
||||
|
||||
result = mistral_config.transform_request(
|
||||
model="mistral/mistral-large-latest",
|
||||
messages=messages,
|
||||
optional_params=optional_params,
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
# Should not add system message for non-magistral models
|
||||
assert len(result["messages"]) == 1
|
||||
assert result["messages"][0]["role"] == "user"
|
||||
assert request["reasoning_effort"] == "high"
|
||||
assert request["messages"] == [{"role": "user", "content": "What is 17*23?"}]
|
||||
|
||||
def test_case_insensitive_magistral_detection(self):
|
||||
"""Test that magistral model detection is case-insensitive."""
|
||||
|
|
@ -423,42 +342,6 @@ class TestMistralReasoningSupport:
|
|||
supported_params = mistral_config.get_supported_openai_params(model)
|
||||
assert "reasoning_effort" in supported_params, f"Failed for model: {model}"
|
||||
|
||||
def test_end_to_end_reasoning_workflow(self):
|
||||
"""Test the complete workflow from parameter to system prompt injection."""
|
||||
mistral_config = MistralConfig()
|
||||
|
||||
# Step 1: Map parameters
|
||||
optional_params = {}
|
||||
mapped_params = mistral_config.map_openai_params(
|
||||
non_default_params={"reasoning_effort": "high", "temperature": 0.7},
|
||||
optional_params=optional_params,
|
||||
model="mistral/magistral-medium-2506",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert mapped_params.get("_add_reasoning_prompt") is True
|
||||
assert mapped_params.get("temperature") == 0.7
|
||||
|
||||
# Step 2: Transform request
|
||||
messages = [{"role": "user", "content": "Solve for x: 2x + 5 = 13"}]
|
||||
|
||||
result = mistral_config.transform_request(
|
||||
model="mistral/magistral-medium-2506",
|
||||
messages=messages,
|
||||
optional_params=mapped_params,
|
||||
litellm_params={},
|
||||
headers={},
|
||||
)
|
||||
|
||||
# Verify final result
|
||||
assert len(result["messages"]) == 2
|
||||
assert result["messages"][0]["role"] == "system"
|
||||
assert "<think>" in result["messages"][0]["content"]
|
||||
assert result["messages"][1]["role"] == "user"
|
||||
assert result["messages"][1]["content"] == "Solve for x: 2x + 5 = 13"
|
||||
assert result.get("temperature") == 0.7
|
||||
assert "_add_reasoning_prompt" not in result
|
||||
|
||||
|
||||
def test_mistral_streaming_chunk_preserves_thinking_blocks():
|
||||
"""Ensure streaming chunks keep magistral reasoning content."""
|
||||
|
|
@ -1716,16 +1599,17 @@ def _warnings_naming(caplog: pytest.LogCaptureFixture, param: str) -> tuple[str,
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"param, value",
|
||||
"param, value, sent",
|
||||
(
|
||||
("reasoning_effort", "none"),
|
||||
("reasoning_effort", "xhigh"),
|
||||
("thinking", {"type": "disabled"}),
|
||||
("thinking", {"type": ["enabled"]}),
|
||||
("reasoning_effort", "none", "none"),
|
||||
("reasoning_effort", "xhigh", "high"),
|
||||
("thinking", {"type": "disabled"}, "none"),
|
||||
("thinking", {"type": "enabled", "budget_tokens": 1024}, "high"),
|
||||
("thinking", {"type": "adaptive"}, "high"),
|
||||
),
|
||||
)
|
||||
def test_magistral_reasoning_request_replaced_by_prompt_warns_once(
|
||||
param: str, value: object, caplog: pytest.LogCaptureFixture
|
||||
def test_magistral_reasoning_request_is_sent_as_a_level_without_a_drop_warning(
|
||||
fixed_reasoning_capabilities: None, param: str, value: object, sent: str, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
with caplog.at_level(logging.WARNING, logger=verbose_logger.name):
|
||||
result: Final = MistralConfig().map_openai_params(
|
||||
|
|
@ -1735,36 +1619,27 @@ def test_magistral_reasoning_request_replaced_by_prompt_warns_once(
|
|||
drop_params=False,
|
||||
)
|
||||
|
||||
warnings: Final = _warnings_naming(caplog, param)
|
||||
assert result["_add_reasoning_prompt"] is True
|
||||
assert len(warnings) == 1
|
||||
assert "magistral-medium-latest" in warnings[0]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"param, value",
|
||||
(
|
||||
("reasoning_effort", "low"),
|
||||
("reasoning_effort", "medium"),
|
||||
("reasoning_effort", "high"),
|
||||
("thinking", {"type": "enabled", "budget_tokens": 1024}),
|
||||
),
|
||||
)
|
||||
def test_magistral_reasoning_request_the_prompt_honors_does_not_warn(
|
||||
param: str, value: object, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
with caplog.at_level(logging.WARNING, logger=verbose_logger.name):
|
||||
result: Final = MistralConfig().map_openai_params(
|
||||
non_default_params={param: value},
|
||||
optional_params={},
|
||||
model="magistral-medium-latest",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert result["_add_reasoning_prompt"] is True
|
||||
assert result["reasoning_effort"] == sent
|
||||
assert _warnings_naming(caplog, param) == ()
|
||||
|
||||
|
||||
def test_magistral_thinking_budget_dropped_under_drop_params_warns_once_naming_the_model(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
with caplog.at_level(logging.WARNING, logger=verbose_logger.name):
|
||||
result: Final = MistralConfig().map_openai_params(
|
||||
non_default_params={"thinking": {"type": "enabled", "budget_tokens": "lots"}},
|
||||
optional_params={},
|
||||
model="magistral-small-latest",
|
||||
drop_params=True,
|
||||
)
|
||||
|
||||
warnings: Final = _warnings_naming(caplog, "thinking.budget_tokens")
|
||||
assert "reasoning_effort" not in result
|
||||
assert len(warnings) == 1
|
||||
assert "model='magistral-small-latest'" in warnings[0]
|
||||
|
||||
|
||||
def test_non_magistral_reasoning_effort_is_sent_without_warning(caplog: pytest.LogCaptureFixture) -> None:
|
||||
with caplog.at_level(logging.WARNING, logger=verbose_logger.name):
|
||||
result: Final = MistralConfig().map_openai_params(
|
||||
|
|
@ -1778,7 +1653,9 @@ def test_non_magistral_reasoning_effort_is_sent_without_warning(caplog: pytest.L
|
|||
assert _warnings_naming(caplog, "reasoning_effort") == ()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("reasoning_effort", [5, {"effort": 5}, {"effort": 5, "summary": "auto"}])
|
||||
@pytest.mark.parametrize(
|
||||
"reasoning_effort", [5, {"effort": 5}, {"effort": 5, "summary": "auto"}, "HIGH", "auto", "", {"effort": "HIGH"}]
|
||||
)
|
||||
def test_mistral_warns_once_when_drop_params_drops_a_level_it_cannot_send(
|
||||
caplog: pytest.LogCaptureFixture, reasoning_effort: object
|
||||
) -> None:
|
||||
|
|
@ -1797,7 +1674,10 @@ def test_mistral_warns_once_when_drop_params_drops_a_level_it_cannot_send(
|
|||
assert "(drop_params=True)" in warnings[0]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("reasoning_effort", [{"summary": "auto"}, {"effort": "high", "summary": "auto"}])
|
||||
@pytest.mark.parametrize(
|
||||
"reasoning_effort",
|
||||
[{"summary": "auto"}, {"effort": "high", "summary": "auto"}, {"effort": "default", "summary": "auto"}],
|
||||
)
|
||||
def test_mistral_stays_quiet_under_drop_params_when_no_level_is_lost(
|
||||
caplog: pytest.LogCaptureFixture, reasoning_effort: object
|
||||
) -> None:
|
||||
|
|
@ -1812,6 +1692,44 @@ def test_mistral_stays_quiet_under_drop_params_when_no_level_is_lost(
|
|||
assert _warnings_naming(caplog, "reasoning_effort") == ()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("reasoning_effort", ["HIGH", "auto", "", {"effort": "HIGH"}])
|
||||
def test_magistral_rejects_a_reasoning_effort_that_is_not_a_level(
|
||||
local_model_cost_map: None, reasoning_effort: object
|
||||
) -> None:
|
||||
import litellm
|
||||
|
||||
with pytest.raises(litellm.UnsupportedParamsError, match=r"\['reasoning_effort'\]"):
|
||||
litellm.get_optional_params(
|
||||
model="magistral-small-latest",
|
||||
custom_llm_provider="mistral",
|
||||
reasoning_effort=reasoning_effort,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("drop_params", [False, True])
|
||||
@pytest.mark.parametrize("reasoning_effort", ["default", {"effort": "default"}])
|
||||
@pytest.mark.parametrize("model", ["magistral-small-latest", "mistral-medium-latest"])
|
||||
def test_default_reasoning_effort_leaves_the_level_to_mistral(
|
||||
local_model_cost_map: None,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
model: str,
|
||||
reasoning_effort: object,
|
||||
drop_params: bool,
|
||||
) -> None:
|
||||
import litellm
|
||||
|
||||
with caplog.at_level(logging.WARNING, logger=verbose_logger.name):
|
||||
optional_params: Final = litellm.get_optional_params(
|
||||
model=model,
|
||||
custom_llm_provider="mistral",
|
||||
reasoning_effort=reasoning_effort,
|
||||
drop_params=drop_params,
|
||||
)
|
||||
|
||||
assert "reasoning_effort" not in optional_params
|
||||
assert _warnings_naming(caplog, "reasoning_effort") == ()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("model", "custom_llm_provider"),
|
||||
[
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue