Merge pull request #41062 from BerriAI/litellm_mistral_codex_reasoning_effort_client_metadata

fix(mistral): accept reasoning_effort on all models and drop client_metadata for Codex compatibility
This commit is contained in:
Mateo Wang 2026-09-18 14:43:58 -07:00 committed by GitHub
commit d45e04a9fd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 358 additions and 20 deletions

View file

@ -1700,6 +1700,9 @@ if TYPE_CHECKING:
from .llms.vertex_ai.vertex_ai_partner_models.ai21.transformation import (
VertexAIAi21Config as VertexAIAi21Config,
)
from .llms.vertex_ai.vertex_ai_partner_models.mistral.transformation import (
VertexAIMistralConfig as VertexAIMistralConfig,
)
from .llms.bedrock.chat.invoke_handler import (
AmazonCohereChatConfig as AmazonCohereChatConfig,
)

View file

@ -184,6 +184,7 @@ LLM_CONFIG_NAMES: Final = (
"VertexAIAnthropicConfig",
"VertexAILlama3Config",
"VertexAIAi21Config",
"VertexAIMistralConfig",
"AmazonCohereChatConfig",
"AmazonBedrockGlobalConfig",
"AmazonAI21Config",
@ -771,6 +772,10 @@ _LLM_CONFIGS_IMPORT_MAP: Final = {
".llms.vertex_ai.vertex_ai_partner_models.ai21.transformation",
"VertexAIAi21Config",
),
"VertexAIMistralConfig": (
".llms.vertex_ai.vertex_ai_partner_models.mistral.transformation",
"VertexAIMistralConfig",
),
"AmazonCohereChatConfig": (
".llms.bedrock.chat.invoke_handler",
"AmazonCohereChatConfig",

View file

@ -190,7 +190,7 @@ def get_supported_openai_params(
elif custom_llm_provider == "vertex_ai" or custom_llm_provider == "vertex_ai_beta":
if request_type == "chat_completion":
if model.startswith("mistral"):
return litellm.MistralConfig().get_supported_openai_params(model=model)
return litellm.VertexAIMistralConfig().get_supported_openai_params(model=model)
elif model.startswith("codestral"):
return litellm.CodestralTextCompletionConfig().get_supported_openai_params(model=model)
elif model.startswith("claude"):

View file

@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, Any, Final, Literal, cast, get_type_hints, ove
import httpx
from litellm._logging import verbose_logger
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
from litellm.litellm_core_utils.prompt_templates.common_utils import (
handle_messages_with_content_list_to_str_conversion,
@ -20,16 +21,37 @@ from litellm.llms.openai.chat.gpt_transformation import (
OpenAIChatCompletionStreamingHandler,
OpenAIGPTConfig,
)
from litellm.router_utils.reasoning_effort_capability import (
declared_reasoning_efforts_for_model,
nearest_declared_reasoning_effort,
)
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
def _accepted_reasoning_effort(model: str, requested: str, custom_llm_provider: str) -> str:
declared: Final = declared_reasoning_efforts_for_model(model, custom_llm_provider)
if declared is None:
return requested
accepted: Final = nearest_declared_reasoning_effort(requested, declared)
if accepted != requested:
verbose_logger.debug(
"%s: %s takes reasoning_effort %s, sending %s in place of %s",
custom_llm_provider,
model,
declared,
accepted,
requested,
)
return accepted
class MistralConfig(OpenAIGPTConfig):
"""
Reference: https://docs.mistral.ai/api/
@ -86,8 +108,16 @@ class MistralConfig(OpenAIGPTConfig):
def get_config(cls):
return super().get_config()
@property
def custom_llm_provider(self) -> str:
return "mistral"
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=self.custom_llm_provider
)
return [
"stream",
"temperature",
"top_p",
@ -99,14 +129,10 @@ class MistralConfig(OpenAIGPTConfig):
"stop",
"response_format",
"parallel_tool_calls",
*(("thinking",) if is_magistral else ()),
*(("reasoning_effort",) if accepts_reasoning_effort else ()),
]
# Add reasoning support for magistral models
if "magistral" in model.lower():
supported_params.extend(["thinking", "reasoning_effort"])
return supported_params
def _map_tool_choice(self, tool_choice: str) -> str:
if tool_choice == "auto" or tool_choice == "none":
return tool_choice
@ -171,10 +197,9 @@ 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():
if param == "reasoning_effort" and "magistral" not in model.lower():
optional_params["reasoning_effort"] = _accepted_reasoning_effort(model, value, self.custom_llm_provider)
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
if param == "parallel_tool_calls":
@ -534,11 +559,13 @@ class MistralConfig(OpenAIGPTConfig):
if "magistral" in model.lower() and optional_params.get("_add_reasoning_prompt", False):
messages = self._add_reasoning_system_prompt_if_needed(messages, optional_params)
upstream_params: Final = {key: value for key, value in optional_params.items() if key != "client_metadata"}
# Call parent transform_request which handles _transform_messages
return super().transform_request(
model=model,
messages=messages,
optional_params=optional_params,
optional_params=upstream_params,
litellm_params=litellm_params,
headers=headers,
)

View file

@ -0,0 +1,7 @@
from litellm.llms.mistral.chat.transformation import MistralConfig
class VertexAIMistralConfig(MistralConfig):
@property
def custom_llm_provider(self) -> str:
return "vertex_ai"

View file

@ -37097,6 +37097,10 @@
"max_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,
@ -37178,6 +37182,15 @@
"max_tokens": 131072,
"mode": "chat",
"output_cost_per_token": 4.4e-06,
"reasoning_effort_levels": [
"none",
"minimal",
"low",
"medium",
"high",
"xhigh",
"max"
],
"source": "https://docs.mistral.ai/models/zai-glm-5-2",
"supports_assistant_prefill": true,
"supports_function_calling": true,
@ -37195,6 +37208,11 @@
"max_tokens": 131072,
"mode": "chat",
"output_cost_per_token": 4.4e-06,
"reasoning_effort_levels": [
"low",
"high",
"max"
],
"source": "https://docs.mistral.ai/models/zai-glm-5-3",
"supports_assistant_prefill": true,
"supports_function_calling": true,
@ -37212,6 +37230,11 @@
"max_tokens": 131072,
"mode": "chat",
"output_cost_per_token": 4.4e-06,
"reasoning_effort_levels": [
"low",
"high",
"max"
],
"source": "https://docs.mistral.ai/models/zai-glm-5-3",
"supports_assistant_prefill": true,
"supports_function_calling": true,
@ -37229,6 +37252,11 @@
"max_tokens": 131072,
"mode": "chat",
"output_cost_per_token": 4.4e-06,
"reasoning_effort_levels": [
"low",
"high",
"max"
],
"source": "https://docs.mistral.ai/models/zai-glm-5-3",
"supports_assistant_prefill": true,
"supports_function_calling": true,
@ -37246,6 +37274,15 @@
"max_tokens": 131072,
"mode": "chat",
"output_cost_per_token": 4.4e-06,
"reasoning_effort_levels": [
"none",
"minimal",
"low",
"medium",
"high",
"xhigh",
"max"
],
"source": "https://docs.mistral.ai/models/zai-glm-5-2",
"supports_assistant_prefill": true,
"supports_function_calling": true,
@ -37541,6 +37578,10 @@
"max_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,
@ -37601,6 +37642,10 @@
"max_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,
@ -37618,6 +37663,10 @@
"max_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,
@ -37651,6 +37700,10 @@
"max_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,
@ -37682,6 +37735,10 @@
"max_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,
@ -60027,6 +60084,10 @@
"max_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,
@ -63352,6 +63413,10 @@
"max_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,
@ -63369,6 +63434,10 @@
"max_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,
@ -63386,6 +63455,10 @@
"max_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,
@ -63403,6 +63476,10 @@
"max_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,

View file

@ -103,6 +103,25 @@ def declared_reasoning_efforts_for_model(model: str, custom_llm_provider: str) -
return declared_reasoning_efforts(entry)
REASONING_EFFORT_STRENGTH_ORDER: Final = ("minimal", "low", "medium", "high", "xhigh", "max")
_STRENGTH_RANK: Final = MappingProxyType({effort: rank for rank, effort in enumerate(REASONING_EFFORT_STRENGTH_ORDER)})
def nearest_declared_reasoning_effort(requested: str, declared: Sequence[str]) -> str:
"""Rounds a request up to the weakest declared level at least as strong as it, and down to the
strongest declared level when it asks for more than the model has, so the caller gets no less
reasoning than it asked for instead of a rejected call. none is the off switch rather than a
strength, so it is never rounded onto the ladder and no level is rounded down to it: a caller
who turned reasoning off must not be billed for it, and a model that cannot turn it off says so
itself. A level outside the strength order is likewise returned as is for upstream to judge."""
ranked: Final = sorted(
(effort for effort in declared if effort in _STRENGTH_RANK), key=lambda effort: _STRENGTH_RANK[effort]
)
if requested in ranked or requested not in _STRENGTH_RANK or not ranked:
return requested
return next((effort for effort in ranked if _STRENGTH_RANK[effort] >= _STRENGTH_RANK[requested]), ranked[-1])
def _supports_none_reasoning_effort(model_info: Mapping[str, object], flag: object) -> bool:
"""Opt-in only where a request path refuses the level. AzureOpenAIGPT5Config raises
UnsupportedParamsError on reasoning_effort='none' without an explicit true, and it is selected

View file

@ -4526,7 +4526,7 @@ def get_optional_params(
drop_params=bool(drop_params),
)
else:
optional_params = litellm.MistralConfig().map_openai_params(
optional_params = litellm.VertexAIMistralConfig().map_openai_params(
model=model,
non_default_params=non_default_params,
optional_params=optional_params,
@ -8396,7 +8396,7 @@ class ProviderConfigManager:
elif model in litellm.vertex_mistral_models:
if "codestral" in model:
return litellm.CodestralTextCompletionConfig()
return litellm.MistralConfig()
return litellm.VertexAIMistralConfig()
elif model in litellm.vertex_ai_ai21_models:
return litellm.VertexAIAi21Config()
else:

View file

@ -37097,6 +37097,10 @@
"max_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,
@ -37178,6 +37182,15 @@
"max_tokens": 131072,
"mode": "chat",
"output_cost_per_token": 4.4e-06,
"reasoning_effort_levels": [
"none",
"minimal",
"low",
"medium",
"high",
"xhigh",
"max"
],
"source": "https://docs.mistral.ai/models/zai-glm-5-2",
"supports_assistant_prefill": true,
"supports_function_calling": true,
@ -37195,6 +37208,11 @@
"max_tokens": 131072,
"mode": "chat",
"output_cost_per_token": 4.4e-06,
"reasoning_effort_levels": [
"low",
"high",
"max"
],
"source": "https://docs.mistral.ai/models/zai-glm-5-3",
"supports_assistant_prefill": true,
"supports_function_calling": true,
@ -37212,6 +37230,11 @@
"max_tokens": 131072,
"mode": "chat",
"output_cost_per_token": 4.4e-06,
"reasoning_effort_levels": [
"low",
"high",
"max"
],
"source": "https://docs.mistral.ai/models/zai-glm-5-3",
"supports_assistant_prefill": true,
"supports_function_calling": true,
@ -37229,6 +37252,11 @@
"max_tokens": 131072,
"mode": "chat",
"output_cost_per_token": 4.4e-06,
"reasoning_effort_levels": [
"low",
"high",
"max"
],
"source": "https://docs.mistral.ai/models/zai-glm-5-3",
"supports_assistant_prefill": true,
"supports_function_calling": true,
@ -37246,6 +37274,15 @@
"max_tokens": 131072,
"mode": "chat",
"output_cost_per_token": 4.4e-06,
"reasoning_effort_levels": [
"none",
"minimal",
"low",
"medium",
"high",
"xhigh",
"max"
],
"source": "https://docs.mistral.ai/models/zai-glm-5-2",
"supports_assistant_prefill": true,
"supports_function_calling": true,
@ -37541,6 +37578,10 @@
"max_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,
@ -37601,6 +37642,10 @@
"max_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,
@ -37618,6 +37663,10 @@
"max_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,
@ -37651,6 +37700,10 @@
"max_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,
@ -37682,6 +37735,10 @@
"max_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,
@ -60027,6 +60084,10 @@
"max_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,
@ -63352,6 +63413,10 @@
"max_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,
@ -63369,6 +63434,10 @@
"max_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,
@ -63386,6 +63455,10 @@
"max_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,
@ -63403,6 +63476,10 @@
"max_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,

View file

@ -51,14 +51,19 @@ class TestMistralReasoningSupport:
assert "reasoning_effort" in supported_params
assert "thinking" in supported_params
# Test non-magistral model doesn't include reasoning parameters
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
supported_params_normal = mistral_config.get_supported_openai_params(
"mistral/mistral-large-latest"
)
assert "reasoning_effort" not in supported_params_normal
assert "thinking" not in supported_params_normal
def test_map_openai_params_reasoning_effort(self):
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()
@ -73,16 +78,93 @@ class TestMistralReasoningSupport:
assert result.get("_add_reasoning_prompt") is True
# Test reasoning_effort ignored for non-magistral model
optional_params_normal = {}
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"] == "high"
@pytest.mark.parametrize(
("model", "requested", "sent"),
[
("mistral-medium-latest", "high", "high"),
("mistral-medium-latest", "none", "none"),
("mistral-medium-latest", "low", "high"),
("mistral-medium-latest", "medium", "high"),
("mistral-medium-latest", "xhigh", "high"),
("mistral-small-latest", "medium", "high"),
("mistral-vibe-cli-latest", "medium", "high"),
("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"),
],
)
def test_reasoning_effort_is_sent_as_a_level_the_model_accepts(self, local_model_cost_map, model, requested, sent):
import litellm
optional_params = litellm.get_optional_params(
model=model,
custom_llm_provider="mistral",
reasoning_effort=requested,
)
assert optional_params["reasoning_effort"] == sent
def test_reasoning_effort_is_forwarded_verbatim_when_the_map_declares_no_levels(
self, local_model_cost_map, monkeypatch
):
import litellm
monkeypatch.setitem(
litellm.model_cost,
"mistral/undeclared-reasoner",
{"litellm_provider": "mistral", "mode": "chat", "supports_reasoning": True},
)
optional_params = 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):
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):
mistral_config = MistralConfig()
request = mistral_config.transform_request(
model="mistral-medium-latest",
messages=[{"role": "user", "content": "hi"}],
optional_params={"client_metadata": {"originator": "codex_cli_rs"}, "temperature": 0.2},
litellm_params={},
headers={},
)
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."""

View file

@ -0,0 +1,17 @@
import litellm
def test_reasoning_effort_stays_unsupported_on_vertex_partner_models(local_model_cost_map):
assert "reasoning_effort" in litellm.get_supported_openai_params(
model="mistral-medium-3", custom_llm_provider="mistral"
)
assert "reasoning_effort" not in litellm.get_supported_openai_params(
model="mistral-medium-3", custom_llm_provider="vertex_ai"
)
dropped = litellm.get_optional_params(
model="mistral-medium-3",
custom_llm_provider="vertex_ai",
reasoning_effort="high",
drop_params=True,
)
assert "reasoning_effort" not in dropped

View file

@ -4,6 +4,7 @@ import litellm
from litellm.router_utils.reasoning_effort_capability import (
deployment_is_catalog_mapped,
intersect_supported_reasoning_efforts,
nearest_declared_reasoning_effort,
resolve_supported_reasoning_efforts,
)
@ -415,3 +416,26 @@ class TestGpt6AstraAdvertisesItsDocumentedLevels:
"high",
"xhigh",
)
class TestNearestDeclaredReasoningEffort:
def test_a_declared_level_is_kept(self):
assert nearest_declared_reasoning_effort("high", ("none", "high")) == "high"
assert nearest_declared_reasoning_effort("none", ("none", "high")) == "none"
def test_an_undeclared_level_rounds_up_to_the_next_declared_one(self):
assert nearest_declared_reasoning_effort("medium", ("none", "high")) == "high"
assert nearest_declared_reasoning_effort("minimal", ("low", "high", "max")) == "low"
assert nearest_declared_reasoning_effort("xhigh", ("low", "high", "max")) == "max"
def test_none_is_a_switch_that_is_never_rounded_in_either_direction(self):
assert nearest_declared_reasoning_effort("none", ("low", "high", "max")) == "none"
assert nearest_declared_reasoning_effort("medium", ("none",)) == "medium"
def test_a_level_above_the_ceiling_takes_the_strongest_declared_one(self):
assert nearest_declared_reasoning_effort("max", ("none", "high")) == "high"
assert nearest_declared_reasoning_effort("xhigh", ("none", "low", "medium", "high")) == "high"
def test_a_level_outside_the_strength_order_is_left_for_upstream(self):
assert nearest_declared_reasoning_effort("turbo", ("none", "high")) == "turbo"
assert nearest_declared_reasoning_effort("medium", ()) == "medium"