mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-13 08:52:02 +00:00
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.
This commit is contained in:
parent
c3118e96ee
commit
9eea89f700
2 changed files with 54 additions and 5 deletions
|
|
@ -27,6 +27,7 @@ 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,
|
||||
)
|
||||
|
|
@ -92,6 +93,8 @@ _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)
|
||||
_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', so Mistral applies its default effort."
|
||||
)
|
||||
|
|
@ -294,13 +297,16 @@ class MistralConfig(OpenAIGPTConfig):
|
|||
) -> str | 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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1556,7 +1556,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:
|
||||
|
|
@ -1575,7 +1577,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:
|
||||
|
|
@ -1590,6 +1595,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