fix(gpt-5): resolve temperature support from the model's default reasoning effort

A gpt-5 model accepts a non-default temperature only while its effective reasoning
effort resolves to "none". litellm had no representation of the effort a model applies
when the request omits reasoning_effort, so it substituted supports_none_reasoning_effort,
which is a different fact. Every model that supports "none" without defaulting to it
therefore had temperature forwarded and rejected upstream, and because the carve-out
returned before the drop_params branch, drop_params: true could not save it.

Declare the fact instead. A new cost-map key, default_reasoning_effort, states the effort
the provider applies when the request omits one, and one shared predicate resolves the
effective effort from it: an explicit reasoning_effort wins, otherwise the declared
default, otherwise the catalogue decides.

That last step matters because the cost map is fetched from the published branch at import
time, so it can be OLDER than the code reading it. On such a map every model looks
undeclared, and reading that as "reasoning is active" would strip temperature from the 39
gpt-5.1/5.2/5.4 entries that accept it, a regression caused by data lag rather than by
anything about the model. So an absent declaration is only meaningful once the catalogue
carries the key at all; a map that predates the feature keeps the answer litellm gave
before it existed, and the conservative answer applies from the moment the data lands.

The top_p/logprobs/top_logprobs gate carried the same assumption spelled differently and
now shares the predicate, as does the Responses API, which reimplemented the rule and is
what the default /v1/messages bridge routes openai models through. Azure normalises its
routing names in one resolver that every capability lookup goes through, which replaces
its bespoke per-lookup rewrite.

Declared on the 37 gpt-5.1/5.2/5.4 entries measured to accept temperature=0 today, so
their behaviour is unchanged. The 23 gpt-5.5/5.6 entries that reject it stay undeclared
and are fixed once the catalogue carries the key.

Resolves LIT-3797
Resolves LIT-5028
This commit is contained in:
Tin Chi Lo 2026-08-27 16:44:30 -07:00
parent 49affa7c01
commit e5c3df2da2
13 changed files with 473 additions and 44 deletions

View file

@ -220,6 +220,15 @@ def string_key_schemas(modes: tuple) -> dict[str, JsonSchema]:
"description": "Highest reasoning effort the Bedrock output_config accepts for this model.", "description": "Highest reasoning effort the Bedrock output_config accepts for this model.",
"enum": ["low", "medium", "high", "max", "xhigh"], "enum": ["low", "medium", "high", "max", "xhigh"],
}, },
"default_reasoning_effort": {
"type": "string",
"description": (
"Reasoning effort the provider applies when the request omits reasoning_effort. "
"Gates whether a non-default temperature or the top_p/logprobs sampling params are "
"accepted, which hold only when the effort resolves to 'none'."
),
"enum": ["none", "minimal", "low", "medium", "high", "xhigh"],
},
"comment": STRING, "comment": STRING,
"audio_transcription_config": STRING, "audio_transcription_config": STRING,
} }

View file

@ -19,19 +19,19 @@ class AzureOpenAIGPT5Config(AzureOpenAIConfig, OpenAIGPT5Config):
GPT5_SERIES_ROUTE = "gpt5_series/" GPT5_SERIES_ROUTE = "gpt5_series/"
@classmethod @classmethod
def _supports_reasoning_effort_level(cls, model: str, level: str) -> bool: def _model_map_lookup_name(cls, model: str) -> str:
"""Override to handle gpt5_series/ prefix used for Azure routing. """Normalise an Azure routing name to its cost-map key.
The parent class calls ``_supports_factory(model, custom_llm_provider=None)`` Neither ``gpt5_series/gpt-5.1`` nor a bare ``gpt-5.1`` is a key in
which fails to resolve ``gpt5_series/gpt-5.1`` to the correct Azure model model_prices_and_context_window.json; ``azure/gpt-5.1`` is. Overriding the shared
entry. Strip the prefix and prepend ``azure/`` so the lookup finds resolver rather than one lookup means the supports, explicitly-disabled and
``azure/gpt-5.1`` in model_prices_and_context_window.json. default-effort answers all read the same entry.
""" """
if model.startswith(cls.GPT5_SERIES_ROUTE): if model.startswith(cls.GPT5_SERIES_ROUTE):
model = "azure/" + model[len(cls.GPT5_SERIES_ROUTE) :] return "azure/" + model[len(cls.GPT5_SERIES_ROUTE) :]
elif not model.startswith("azure/"): if model.startswith("azure/"):
model = "azure/" + model return model
return super()._supports_reasoning_effort_level(model, level) return "azure/" + model
@classmethod @classmethod
def is_model_gpt_5_model(cls, model: str) -> bool: def is_model_gpt_5_model(cls, model: str) -> bool:

View file

@ -6,11 +6,28 @@ import litellm
from litellm.utils import ( from litellm.utils import (
_is_explicitly_disabled_factory, _is_explicitly_disabled_factory,
_supports_factory, _supports_factory,
declared_value_factory,
) )
from .gpt_transformation import OpenAIGPTConfig from .gpt_transformation import OpenAIGPTConfig
def _catalogue_declares_default_effort() -> bool:
"""Whether the loaded cost map carries default_reasoning_effort for ANY entry.
The map is fetched from the published branch at import time, so it can be OLDER than the
code reading it. On such a map every model looks undeclared, and treating that as "reasoning
is active" would silently strip temperature from the gpt-5.1/5.2/5.4 deployments that accept
it - a regression caused purely by data lag rather than by anything about the model.
So the absence of the key is only meaningful once the catalogue is known to carry it at all.
A map that has never heard of the key predates the feature, and the honest answer there is
the one litellm gave before it existed. Scanning costs ~80us on the largest published map and
only on the fallback path, which is noise beside the request it precedes.
"""
return any(isinstance(entry, dict) and "default_reasoning_effort" in entry for entry in litellm.model_cost.values())
def _normalize_reasoning_effort_for_chat_completion( def _normalize_reasoning_effort_for_chat_completion(
value: str | dict | None, value: str | dict | None,
) -> str | None: ) -> str | None:
@ -114,6 +131,17 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
except (ValueError, IndexError): except (ValueError, IndexError):
return False return False
@classmethod
def _model_map_lookup_name(cls, model: str) -> str:
"""The name this model is looked up by in the cost map.
Identity here, because an OpenAI model name is already its map key. Azure overrides
it: its routing prefixes are not map keys, so every capability lookup has to
normalise the name the same way, and doing that in ONE place is what keeps the
supports/disabled/default answers from disagreeing about which entry they read.
"""
return model
@classmethod @classmethod
def _supports_reasoning_effort_level(cls, model: str, level: str) -> bool: def _supports_reasoning_effort_level(cls, model: str, level: str) -> bool:
"""Check if the model supports a specific reasoning_effort level. """Check if the model supports a specific reasoning_effort level.
@ -123,11 +151,40 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
Returns False for unknown models (safe fallback). Returns False for unknown models (safe fallback).
""" """
return _supports_factory( return _supports_factory(
model=model, model=cls._model_map_lookup_name(model),
custom_llm_provider=None, custom_llm_provider=None,
key=f"supports_{level}_reasoning_effort", key=f"supports_{level}_reasoning_effort",
) )
@classmethod
def effort_resolves_to_none(cls, model: str, effective_effort: str | None) -> bool:
"""Whether this request's reasoning effort ends up as "none", which is the single
condition under which the provider accepts a non-default temperature or the
top_p/logprobs sampling params.
An explicit reasoning_effort answers outright. When the request omits it the answer
is the model's DEFAULT effort, which only the map can state: supporting "none" is a
different fact from defaulting to it, and reading the former as the latter is what
forwarded temperature=0 to every gpt-5.5/5.6 deployment.
An undeclared default resolves to False. The map not saying is not the model
saying no, so the gate takes the conservative branch: a param the provider would
have rejected gets dropped or refused with an actionable error, and a model
released before its map entry declares a default needs no code change to be safe.
"""
if effective_effort is not None:
return effective_effort == "none"
declared: Final = declared_value_factory(
model=cls._model_map_lookup_name(model),
custom_llm_provider=None,
key="default_reasoning_effort",
)
if declared is not None:
return declared == "none"
if not _catalogue_declares_default_effort():
return cls._supports_reasoning_effort_level(model, "none")
return False
@classmethod @classmethod
def _is_reasoning_effort_level_explicitly_disabled(cls, model: str, level: str) -> bool: def _is_reasoning_effort_level_explicitly_disabled(cls, model: str, level: str) -> bool:
"""Return True only when the model map explicitly sets the capability to False. """Return True only when the model map explicitly sets the capability to False.
@ -140,7 +197,7 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
Use this for opt-out checks where unknown models should be allowed through. Use this for opt-out checks where unknown models should be allowed through.
""" """
return _is_explicitly_disabled_factory( return _is_explicitly_disabled_factory(
model=model, model=cls._model_map_lookup_name(model),
custom_llm_provider=None, custom_llm_provider=None,
key=f"supports_{level}_reasoning_effort", key=f"supports_{level}_reasoning_effort",
) )
@ -260,15 +317,16 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
if supports_none: if supports_none:
sampling_params: Final = ["logprobs", "top_logprobs", "top_p"] sampling_params: Final = ["logprobs", "top_logprobs", "top_p"]
has_sampling: Final = any(p in non_default_params for p in sampling_params) has_sampling: Final = any(p in non_default_params for p in sampling_params)
if has_sampling and effective_effort not in (None, "none"): if has_sampling and not self.effort_resolves_to_none(model, effective_effort):
if litellm.drop_params or drop_params: if litellm.drop_params or drop_params:
for p in sampling_params: for p in sampling_params:
non_default_params.pop(p, None) non_default_params.pop(p, None)
else: else:
raise litellm.utils.UnsupportedParamsError( raise litellm.utils.UnsupportedParamsError(
message=( message=(
"gpt-5.1/5.2/5.4 only support logprobs, top_p, top_logprobs when " f"{model} only supports logprobs, top_p, top_logprobs when reasoning_effort "
f"reasoning_effort='none'. Current reasoning_effort='{effective_effort}'. " "resolves to 'none', either set explicitly on the request or declared as the "
f"model's default_reasoning_effort. Current reasoning_effort={effective_effort!r}. "
"To drop unsupported params set `litellm.drop_params = True`" "To drop unsupported params set `litellm.drop_params = True`"
), ),
status_code=400, status_code=400,
@ -277,17 +335,19 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
if "temperature" in non_default_params: if "temperature" in non_default_params:
temperature_value: Final[float | None] = non_default_params.pop("temperature") temperature_value: Final[float | None] = non_default_params.pop("temperature")
if temperature_value is not None: if temperature_value is not None:
# models supporting reasoning_effort="none" also support flexible temperature # a non-default temperature rides on the effort resolving to "none", not on
if supports_none and (effective_effort == "none" or effective_effort is None) or temperature_value == 1: # the model merely supporting it
if (supports_none and self.effort_resolves_to_none(model, effective_effort)) or temperature_value == 1:
optional_params["temperature"] = temperature_value optional_params["temperature"] = temperature_value
elif litellm.drop_params or drop_params: elif litellm.drop_params or drop_params:
pass pass
else: else:
raise litellm.utils.UnsupportedParamsError( raise litellm.utils.UnsupportedParamsError(
message=( message=(
f"gpt-5 models (including gpt-5-codex) don't support temperature={temperature_value}. " f"{model} doesn't support temperature={temperature_value} while reasoning is "
"Only temperature=1 is supported. " "active. Only temperature=1 is supported unless reasoning_effort resolves to "
"For gpt-5.1, temperature is supported when reasoning_effort='none' (or not specified, as it defaults to 'none'). " "'none', either set explicitly on the request or declared as the model's "
"default_reasoning_effort. "
"To drop unsupported params set `litellm.drop_params = True`" "To drop unsupported params set `litellm.drop_params = True`"
), ),
status_code=400, status_code=400,

View file

@ -61,6 +61,20 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
key="supports_none_reasoning_effort", key="supports_none_reasoning_effort",
) )
@staticmethod
def _effort_resolves_to_none(model: str, effort: str | None) -> bool:
"""Whether this request's reasoning effort ends up as "none", the one condition
under which a non-default temperature is accepted.
Delegates to the chat-completions gpt-5 config so both surfaces answer from one
rule: the Responses API reaches the same models over a different wire, and a second
copy of the rule here is what let this surface keep forwarding temperature after the
chat surface stopped.
"""
from litellm.llms.openai.chat.gpt_5_transformation import OpenAIGPT5Config
return OpenAIGPT5Config.effort_resolves_to_none(model, effort)
@staticmethod @staticmethod
def _enforce_min_max_output_tokens(max_output_tokens: "int | None") -> "int | None": def _enforce_min_max_output_tokens(max_output_tokens: "int | None") -> "int | None":
"""Raise sub-minimum max_output_tokens up to the OpenAI Responses API minimum. """Raise sub-minimum max_output_tokens up to the OpenAI Responses API minimum.
@ -116,17 +130,17 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
reasoning: Final = params.get("reasoning") or {} reasoning: Final = params.get("reasoning") or {}
effort: Final = reasoning.get("effort") if isinstance(reasoning, dict) else None effort: Final = reasoning.get("effort") if isinstance(reasoning, dict) else None
supports_none: Final = self._supports_reasoning_effort_none(model=model) supports_none: Final = self._supports_reasoning_effort_none(model=model)
if supports_none and (effort == "none" or effort is None): if supports_none and self._effort_resolves_to_none(model, effort):
pass # flexible temperature allowed pass # flexible temperature allowed
elif drop_params or litellm.drop_params: elif drop_params or litellm.drop_params:
params.pop("temperature", None) params.pop("temperature", None)
else: else:
raise litellm.UnsupportedParamsError( raise litellm.UnsupportedParamsError(
message=( message=(
f"gpt-5 models don't support temperature={temperature}. " f"{model} doesn't support temperature={temperature} while reasoning is "
"Only temperature=1 is supported. " "active. Only temperature=1 is supported unless reasoning.effort resolves "
"For models like gpt-5.1/5.4, temperature is supported " "to 'none', either set explicitly on the request or declared as the "
"when reasoning.effort='none' (or not specified). " "model's default_reasoning_effort. "
"To drop unsupported params set `litellm.drop_params = True`" "To drop unsupported params set `litellm.drop_params = True`"
), ),
status_code=400, status_code=400,

View file

@ -3409,6 +3409,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -3456,6 +3457,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -3589,6 +3591,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": false "supports_minimal_reasoning_effort": false
}, },
@ -3630,6 +3633,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": false "supports_minimal_reasoning_effort": false
}, },
@ -3671,6 +3675,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": false "supports_minimal_reasoning_effort": false
}, },
@ -3712,6 +3717,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": false "supports_minimal_reasoning_effort": false
}, },
@ -3937,7 +3943,8 @@
"supports_system_messages": true, "supports_system_messages": true,
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none"
}, },
"azure/eu/gpt-5.1-chat": { "azure/eu/gpt-5.1-chat": {
"cache_read_input_token_cost": 1.4e-07, "cache_read_input_token_cost": 1.4e-07,
@ -3972,7 +3979,8 @@
"supports_system_messages": true, "supports_system_messages": true,
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none"
}, },
"azure/eu/gpt-5.1-codex": { "azure/eu/gpt-5.1-codex": {
"deprecation_date": "2027-05-15", "deprecation_date": "2027-05-15",
@ -4247,7 +4255,8 @@
"supports_system_messages": true, "supports_system_messages": true,
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none"
}, },
"azure/global/gpt-5.1-chat": { "azure/global/gpt-5.1-chat": {
"cache_read_input_token_cost": 1.25e-07, "cache_read_input_token_cost": 1.25e-07,
@ -4282,7 +4291,8 @@
"supports_system_messages": true, "supports_system_messages": true,
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none"
}, },
"azure/global/gpt-5.1-codex": { "azure/global/gpt-5.1-codex": {
"deprecation_date": "2027-05-15", "deprecation_date": "2027-05-15",
@ -5367,6 +5377,7 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
"azure/gpt-5.1-chat-2025-11-13": { "azure/gpt-5.1-chat-2025-11-13": {
@ -5404,7 +5415,8 @@
"supports_system_messages": true, "supports_system_messages": true,
"supports_tool_choice": false, "supports_tool_choice": false,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none"
}, },
"azure/gpt-5.1-codex-2025-11-13": { "azure/gpt-5.1-codex-2025-11-13": {
"cache_read_input_token_cost": 1.25e-07, "cache_read_input_token_cost": 1.25e-07,
@ -5833,7 +5845,8 @@
"supports_system_messages": true, "supports_system_messages": true,
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none"
}, },
"azure/gpt-5.1-chat": { "azure/gpt-5.1-chat": {
"cache_read_input_token_cost": 1.25e-07, "cache_read_input_token_cost": 1.25e-07,
@ -5868,7 +5881,8 @@
"supports_system_messages": true, "supports_system_messages": true,
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none"
}, },
"azure/gpt-5.1-codex": { "azure/gpt-5.1-codex": {
"deprecation_date": "2027-05-15", "deprecation_date": "2027-05-15",
@ -6315,6 +6329,7 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -6354,6 +6369,7 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -6393,6 +6409,7 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -6438,6 +6455,7 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -6477,6 +6495,7 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -6516,6 +6535,7 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -7663,6 +7683,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true "supports_xhigh_reasoning_effort": true
}, },
"azure/gpt-5.4-mini-2026-03-17": { "azure/gpt-5.4-mini-2026-03-17": {
@ -7704,6 +7725,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true "supports_xhigh_reasoning_effort": true
}, },
"azure/gpt-5.4-nano": { "azure/gpt-5.4-nano": {
@ -7745,6 +7767,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true "supports_xhigh_reasoning_effort": true
}, },
"azure/gpt-5.4-nano-2026-03-17": { "azure/gpt-5.4-nano-2026-03-17": {
@ -7786,6 +7809,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true "supports_xhigh_reasoning_effort": true
}, },
"azure/gpt-image-1": { "azure/gpt-image-1": {
@ -8856,7 +8880,8 @@
"supports_system_messages": true, "supports_system_messages": true,
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none"
}, },
"azure/us/gpt-5.1-chat": { "azure/us/gpt-5.1-chat": {
"cache_read_input_token_cost": 1.4e-07, "cache_read_input_token_cost": 1.4e-07,
@ -8891,7 +8916,8 @@
"supports_system_messages": true, "supports_system_messages": true,
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none"
}, },
"azure/us/gpt-5.1-codex": { "azure/us/gpt-5.1-codex": {
"deprecation_date": "2027-05-15", "deprecation_date": "2027-05-15",
@ -26292,6 +26318,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": false, "supports_xhigh_reasoning_effort": false,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -26336,6 +26363,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": false, "supports_xhigh_reasoning_effort": false,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -26381,6 +26409,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": false, "supports_xhigh_reasoning_effort": false,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -26426,6 +26455,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -26471,6 +26501,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -27295,6 +27326,7 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -27343,6 +27375,7 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -27492,6 +27525,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": false "supports_minimal_reasoning_effort": false
}, },
@ -27543,6 +27577,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": false "supports_minimal_reasoning_effort": false
}, },
@ -27591,6 +27626,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": false "supports_minimal_reasoning_effort": false
}, },
@ -27639,6 +27675,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": false "supports_minimal_reasoning_effort": false
}, },

View file

@ -165,6 +165,7 @@ class ProviderSpecificModelInfo(TypedDict, total=False):
supports_xhigh_reasoning_effort: bool | None supports_xhigh_reasoning_effort: bool | None
supports_max_reasoning_effort: bool | None supports_max_reasoning_effort: bool | None
reasoning_effort_levels: ReadOnly[Sequence[str] | None] reasoning_effort_levels: ReadOnly[Sequence[str] | None]
default_reasoning_effort: ReadOnly[Literal["none", "minimal", "low", "medium", "high", "xhigh"] | None]
supports_output_config: bool | None supports_output_config: bool | None
supports_image_size: bool | None supports_image_size: bool | None
bedrock_output_config_effort_ceiling: Literal["low", "medium", "high", "max", "xhigh"] | None bedrock_output_config_effort_ceiling: Literal["low", "medium", "high", "max", "xhigh"] | None

View file

@ -2596,6 +2596,46 @@ def _supports_factory(model: str, custom_llm_provider: str | None, key: str) ->
return False return False
def declared_value_factory(model: str, custom_llm_provider: str | None, key: str) -> str | None:
"""Return a string value the model map declares for *key*, or ``None`` when it says nothing.
The string-valued sibling of :func:`_supports_factory` and
:func:`_is_explicitly_disabled_factory`, public where those two are not because it is read
from the provider configs rather than from this module, sharing their
``get_llm_provider`` -> ``_get_model_info_helper`` chain and their unprefixed-twin
fallback (#20885), so a provider-prefixed entry that omits the key still answers
from the bare entry that carries it.
``None`` means "the map does not say", never "the map says no" - callers decide what
an unknown declaration implies, and for a capability gate that decision must be the
conservative one.
"""
try:
resolved: Final = litellm.get_llm_provider(model=model, custom_llm_provider=custom_llm_provider)
resolved_model: Final = resolved[0]
resolved_provider: Final = resolved[1]
model_info: Final = _get_model_info_helper(model=resolved_model, custom_llm_provider=resolved_provider)
declared: Final = model_info.get(key)
if isinstance(declared, str):
return declared
bare_model_key: Final = _get_model_cost_key(resolved_model)
bare_entry: Final = litellm.model_cost.get(bare_model_key) if bare_model_key is not None else None
if isinstance(bare_entry, dict):
bare_declared: Final = bare_entry.get(key)
if isinstance(bare_declared, str):
return bare_declared
return None
except Exception as e: # noqa: BLE001 # an unreadable map entry means "not declared", never a failed call
verbose_logger.debug(
"Model not found or error in reading %s. You passed model=%s, custom_llm_provider=%s. Error: %s",
key,
model,
custom_llm_provider,
e,
)
return None
def _is_explicitly_disabled_factory(model: str, custom_llm_provider: str | None, key: str) -> bool: def _is_explicitly_disabled_factory(model: str, custom_llm_provider: str | None, key: str) -> bool:
"""Return True only when the model map explicitly sets *key* to ``False``. """Return True only when the model map explicitly sets *key* to ``False``.
@ -5890,6 +5930,7 @@ def _get_model_info_helper(
supports_xhigh_reasoning_effort=_model_info.get("supports_xhigh_reasoning_effort", None), supports_xhigh_reasoning_effort=_model_info.get("supports_xhigh_reasoning_effort", None),
supports_max_reasoning_effort=_model_info.get("supports_max_reasoning_effort", None), supports_max_reasoning_effort=_model_info.get("supports_max_reasoning_effort", None),
reasoning_effort_levels=_model_info.get("reasoning_effort_levels", None), reasoning_effort_levels=_model_info.get("reasoning_effort_levels", None),
default_reasoning_effort=_model_info.get("default_reasoning_effort", None),
bedrock_output_config_effort_ceiling=_model_info.get("bedrock_output_config_effort_ceiling", None), bedrock_output_config_effort_ceiling=_model_info.get("bedrock_output_config_effort_ceiling", None),
bedrock_converse_supports_strict_tools=_model_info.get("bedrock_converse_supports_strict_tools", None), bedrock_converse_supports_strict_tools=_model_info.get("bedrock_converse_supports_strict_tools", None),
supports_computer_use=_model_info.get("supports_computer_use", None), supports_computer_use=_model_info.get("supports_computer_use", None),

View file

@ -3409,6 +3409,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -3456,6 +3457,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -3589,6 +3591,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": false "supports_minimal_reasoning_effort": false
}, },
@ -3630,6 +3633,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": false "supports_minimal_reasoning_effort": false
}, },
@ -3671,6 +3675,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": false "supports_minimal_reasoning_effort": false
}, },
@ -3712,6 +3717,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": false "supports_minimal_reasoning_effort": false
}, },
@ -3937,7 +3943,8 @@
"supports_system_messages": true, "supports_system_messages": true,
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none"
}, },
"azure/eu/gpt-5.1-chat": { "azure/eu/gpt-5.1-chat": {
"cache_read_input_token_cost": 1.4e-07, "cache_read_input_token_cost": 1.4e-07,
@ -3972,7 +3979,8 @@
"supports_system_messages": true, "supports_system_messages": true,
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none"
}, },
"azure/eu/gpt-5.1-codex": { "azure/eu/gpt-5.1-codex": {
"deprecation_date": "2027-05-15", "deprecation_date": "2027-05-15",
@ -4247,7 +4255,8 @@
"supports_system_messages": true, "supports_system_messages": true,
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none"
}, },
"azure/global/gpt-5.1-chat": { "azure/global/gpt-5.1-chat": {
"cache_read_input_token_cost": 1.25e-07, "cache_read_input_token_cost": 1.25e-07,
@ -4282,7 +4291,8 @@
"supports_system_messages": true, "supports_system_messages": true,
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none"
}, },
"azure/global/gpt-5.1-codex": { "azure/global/gpt-5.1-codex": {
"deprecation_date": "2027-05-15", "deprecation_date": "2027-05-15",
@ -5367,6 +5377,7 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
"azure/gpt-5.1-chat-2025-11-13": { "azure/gpt-5.1-chat-2025-11-13": {
@ -5404,7 +5415,8 @@
"supports_system_messages": true, "supports_system_messages": true,
"supports_tool_choice": false, "supports_tool_choice": false,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none"
}, },
"azure/gpt-5.1-codex-2025-11-13": { "azure/gpt-5.1-codex-2025-11-13": {
"cache_read_input_token_cost": 1.25e-07, "cache_read_input_token_cost": 1.25e-07,
@ -5833,7 +5845,8 @@
"supports_system_messages": true, "supports_system_messages": true,
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none"
}, },
"azure/gpt-5.1-chat": { "azure/gpt-5.1-chat": {
"cache_read_input_token_cost": 1.25e-07, "cache_read_input_token_cost": 1.25e-07,
@ -5868,7 +5881,8 @@
"supports_system_messages": true, "supports_system_messages": true,
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none"
}, },
"azure/gpt-5.1-codex": { "azure/gpt-5.1-codex": {
"deprecation_date": "2027-05-15", "deprecation_date": "2027-05-15",
@ -6315,6 +6329,7 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -6354,6 +6369,7 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -6393,6 +6409,7 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -6438,6 +6455,7 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -6477,6 +6495,7 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -6516,6 +6535,7 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -7663,6 +7683,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true "supports_xhigh_reasoning_effort": true
}, },
"azure/gpt-5.4-mini-2026-03-17": { "azure/gpt-5.4-mini-2026-03-17": {
@ -7704,6 +7725,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true "supports_xhigh_reasoning_effort": true
}, },
"azure/gpt-5.4-nano": { "azure/gpt-5.4-nano": {
@ -7745,6 +7767,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true "supports_xhigh_reasoning_effort": true
}, },
"azure/gpt-5.4-nano-2026-03-17": { "azure/gpt-5.4-nano-2026-03-17": {
@ -7786,6 +7809,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true "supports_xhigh_reasoning_effort": true
}, },
"azure/gpt-image-1": { "azure/gpt-image-1": {
@ -8856,7 +8880,8 @@
"supports_system_messages": true, "supports_system_messages": true,
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none"
}, },
"azure/us/gpt-5.1-chat": { "azure/us/gpt-5.1-chat": {
"cache_read_input_token_cost": 1.4e-07, "cache_read_input_token_cost": 1.4e-07,
@ -8891,7 +8916,8 @@
"supports_system_messages": true, "supports_system_messages": true,
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none"
}, },
"azure/us/gpt-5.1-codex": { "azure/us/gpt-5.1-codex": {
"deprecation_date": "2027-05-15", "deprecation_date": "2027-05-15",
@ -26292,6 +26318,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": false, "supports_xhigh_reasoning_effort": false,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -26336,6 +26363,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": false, "supports_xhigh_reasoning_effort": false,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -26381,6 +26409,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": false, "supports_xhigh_reasoning_effort": false,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -26426,6 +26455,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -26471,6 +26501,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -27295,6 +27326,7 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -27343,6 +27375,7 @@
"supports_tool_choice": true, "supports_tool_choice": true,
"supports_vision": true, "supports_vision": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": true "supports_minimal_reasoning_effort": true
}, },
@ -27492,6 +27525,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": false "supports_minimal_reasoning_effort": false
}, },
@ -27543,6 +27577,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": false "supports_minimal_reasoning_effort": false
}, },
@ -27591,6 +27626,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": false "supports_minimal_reasoning_effort": false
}, },
@ -27639,6 +27675,7 @@
"supports_vision": true, "supports_vision": true,
"supports_web_search": true, "supports_web_search": true,
"supports_none_reasoning_effort": true, "supports_none_reasoning_effort": true,
"default_reasoning_effort": "none",
"supports_xhigh_reasoning_effort": true, "supports_xhigh_reasoning_effort": true,
"supports_minimal_reasoning_effort": false "supports_minimal_reasoning_effort": false
}, },

View file

@ -179,6 +179,18 @@
"comment": { "comment": {
"type": "string" "type": "string"
}, },
"default_reasoning_effort": {
"type": "string",
"description": "Reasoning effort the provider applies when the request omits reasoning_effort. Gates whether a non-default temperature or the top_p/logprobs sampling params are accepted, which hold only when the effort resolves to 'none'.",
"enum": [
"none",
"minimal",
"low",
"medium",
"high",
"xhigh"
]
},
"deprecation_date": { "deprecation_date": {
"type": "string", "type": "string",
"description": "Date the provider deprecates the model, YYYY-MM-DD.", "description": "Date the provider deprecates the model, YYYY-MM-DD.",

View file

@ -1,6 +1,7 @@
import pytest import pytest
import litellm import litellm
from litellm.litellm_core_utils.get_model_cost_map import get_model_cost_map
from litellm.llms.azure.chat.gpt_5_transformation import AzureOpenAIGPT5Config from litellm.llms.azure.chat.gpt_5_transformation import AzureOpenAIGPT5Config
@ -9,6 +10,15 @@ def config() -> AzureOpenAIGPT5Config:
return AzureOpenAIGPT5Config() return AzureOpenAIGPT5Config()
@pytest.fixture(autouse=True)
def use_local_model_cost_map(monkeypatch: pytest.MonkeyPatch):
"""Pin the bundled cost map: these gates read model-map capability keys, and the default
import path fetches the published map, which lags a key added in this repo."""
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
monkeypatch.setattr(litellm, "model_cost", get_model_cost_map(url=litellm.model_cost_map_url))
litellm.add_known_models(model_cost_map=litellm.model_cost)
def test_azure_gpt5_supports_reasoning_effort(config: AzureOpenAIGPT5Config): def test_azure_gpt5_supports_reasoning_effort(config: AzureOpenAIGPT5Config):
assert "reasoning_effort" in config.get_supported_openai_params(model="gpt-5") assert "reasoning_effort" in config.get_supported_openai_params(model="gpt-5")
assert "reasoning_effort" in config.get_supported_openai_params( assert "reasoning_effort" in config.get_supported_openai_params(
@ -299,3 +309,30 @@ def test_azure_gpt5_1_does_not_support_logprobs(config: AzureOpenAIGPT5Config):
supported_params = config.get_supported_openai_params(model="gpt-5.1") supported_params = config.get_supported_openai_params(model="gpt-5.1")
assert "logprobs" not in supported_params assert "logprobs" not in supported_params
assert "top_logprobs" not in supported_params assert "top_logprobs" not in supported_params
class TestAzureResolvesTheDeclaredDefaultEffort:
"""Azure reaches the same models under names that are not cost-map keys. Every capability
lookup therefore has to normalise the name identically, which is why the normalisation is
one overridden resolver rather than a rewrite inside a single lookup.
"""
@pytest.mark.parametrize(
"model, temperature_survives",
[
("azure/gpt-5.1", True),
("gpt5_series/gpt-5.1", True),
("gpt-5.1", True),
("azure/gpt-5.6-terra", False),
("gpt5_series/gpt-5.6-terra", False),
("azure/gpt-5.5", False),
],
)
def test_every_azure_name_shape_reads_the_same_entry(self, config, model, temperature_survives):
mapped = config.map_openai_params(
non_default_params={"temperature": 0},
optional_params={},
model=model,
drop_params=True,
)
assert ("temperature" in mapped) is temperature_survives

View file

@ -1593,3 +1593,36 @@ class TestPromptCacheOptionsOnResponsesPath:
"text": "hi", "text": "hi",
"prompt_cache_breakpoint": {"mode": "explicit"}, "prompt_cache_breakpoint": {"mode": "explicit"},
} }
class TestResponsesSurfaceSharesTheEffortRule:
"""The Responses API reaches the same gpt-5 models over a different wire, and the default
/v1/messages bridge for openai models routes through it. It carried its own copy of the
temperature rule, so fixing chat completions alone left this surface still forwarding
temperature to a model that rejects it.
"""
@pytest.mark.parametrize(
"model, effort, temperature_survives",
[
("gpt-5.1", None, True),
("gpt-5.4", None, True),
("gpt-5.5", None, False),
("gpt-5.6-terra", None, False),
("gpt-5.6-sol", None, False),
("gpt-5.6-terra", "none", True),
("gpt-5.6-terra", "medium", False),
],
)
def test_temperature_follows_the_resolved_effort(
self, local_model_cost_map, model, effort, temperature_survives
):
params = {"temperature": 0}
if effort is not None:
params["reasoning"] = {"effort": effort}
mapped = OpenAIResponsesAPIConfig().map_openai_params(
response_api_optional_params=params,
model=model,
drop_params=True,
)
assert ("temperature" in mapped) is temperature_survives

View file

@ -1,3 +1,5 @@
import re
import pytest import pytest
import litellm import litellm
@ -137,7 +139,7 @@ def test_gpt5_codex_temperature_error(config: OpenAIConfig):
"""Test that GPT-5-Codex raises error for unsupported temperature when drop_params=False.""" """Test that GPT-5-Codex raises error for unsupported temperature when drop_params=False."""
with pytest.raises( with pytest.raises(
litellm.utils.UnsupportedParamsError, litellm.utils.UnsupportedParamsError,
match="gpt-5 models \\(including gpt-5-codex\\)", match=re.escape("gpt-5-codex doesn't support temperature=0.7 while reasoning is active"),
): ):
config.map_openai_params( config.map_openai_params(
non_default_params={"temperature": 0.7}, non_default_params={"temperature": 0.7},
@ -1385,3 +1387,121 @@ def test_gpt5_drops_xhigh_when_requested(config: OpenAIConfig):
drop_params=True, drop_params=True,
) )
assert "reasoning_effort" not in params assert "reasoning_effort" not in params
class TestDefaultReasoningEffortGatesSamplingParams:
"""A non-default temperature rides on the effort RESOLVING to "none", which for a request
that omits reasoning_effort is the model's declared default_reasoning_effort - not on the
model merely supporting "none". gpt-5.5 and gpt-5.6 support it and do not default to it,
so reading one fact as the other forwarded temperature=0 and the provider rejected it.
Every expectation below was measured against the live provider before being pinned here.
"""
@pytest.mark.parametrize(
"model, effort, temperature_survives",
[
# declares default_reasoning_effort="none": reasoning is off, sampling is free
("gpt-5.1", None, True),
("gpt-5.2", None, True),
("gpt-5.4", None, True),
("gpt-5.4-nano", None, True),
# declares no default: reasoning is active, so the provider takes only temperature=1
("gpt-5.5", None, False),
("gpt-5.6", None, False),
("gpt-5.6-terra", None, False),
("gpt-5.6-sol", None, False),
# an explicit effort always wins over the declared default, both ways
("gpt-5.6-terra", "none", True),
("gpt-5.6-terra", "medium", False),
("gpt-5.1", "medium", False),
],
)
def test_temperature_follows_the_resolved_effort(self, model, effort, temperature_survives):
params = {"temperature": 0} if effort is None else {"temperature": 0, "reasoning_effort": effort}
mapped = OpenAIGPT5Config().map_openai_params(
non_default_params=params,
optional_params={},
model=model,
drop_params=True,
)
assert ("temperature" in mapped) is temperature_survives
@pytest.mark.parametrize("model, top_p_survives", [("gpt-5.1", True), ("gpt-5.6-terra", False)])
def test_the_same_rule_gates_top_p(self, model, top_p_survives):
"""top_p/logprobs are gated by the identical condition, so they were identically wrong."""
mapped = OpenAIGPT5Config().map_openai_params(
non_default_params={"top_p": 0.5},
optional_params={},
model=model,
drop_params=True,
)
assert ("top_p" in mapped) is top_p_survives
def test_an_undeclared_model_is_refused_rather_than_forwarded(self):
"""Without drop_params the caller gets an actionable 400 naming the remedy, instead of
the provider's own rejection arriving from an upstream it did not address."""
with pytest.raises(litellm.utils.UnsupportedParamsError, match="default_reasoning_effort"):
OpenAIGPT5Config().map_openai_params(
non_default_params={"temperature": 0},
optional_params={},
model="gpt-5.6-terra",
drop_params=False,
)
class TestACatalogueOlderThanTheCodeDoesNotStripTemperature:
"""The cost map is fetched from the published branch at import time, so it can be OLDER than
the code reading it. On such a map every model looks undeclared, and reading that as
"reasoning is active" silently stripped temperature from the gpt-5.1/5.2/5.4 deployments that
accept it - a regression caused by data lag rather than by anything about the model.
Absence of the key only means something once the catalogue is known to carry it at all.
"""
@staticmethod
def _map_without_the_key(monkeypatch: pytest.MonkeyPatch) -> None:
stripped = {
name: {k: v for k, v in entry.items() if k != "default_reasoning_effort"}
if isinstance(entry, dict)
else entry
for name, entry in litellm.model_cost.items()
}
monkeypatch.setattr(litellm, "model_cost", stripped)
@pytest.mark.parametrize("model", ["gpt-5.1", "gpt-5.2", "gpt-5.4", "gpt-5.4-nano"])
def test_a_pre_feature_catalogue_keeps_the_answer_it_gave_before(self, monkeypatch, model):
"""These models accept temperature=0, verified against the provider. On a map that predates
the key they must keep it, exactly as they did before this feature existed."""
self._map_without_the_key(monkeypatch)
mapped = OpenAIGPT5Config().map_openai_params(
non_default_params={"temperature": 0},
optional_params={},
model=model,
drop_params=True,
)
assert mapped.get("temperature") == 0
@pytest.mark.parametrize("model", ["gpt-5.1", "gpt-5.4"])
def test_the_same_holds_for_the_sampling_params(self, monkeypatch, model):
self._map_without_the_key(monkeypatch)
mapped = OpenAIGPT5Config().map_openai_params(
non_default_params={"top_p": 0.5},
optional_params={},
model=model,
drop_params=True,
)
assert mapped.get("top_p") == 0.5
def test_once_the_catalogue_declares_the_key_the_conservative_answer_returns(self):
"""The bundled map DOES carry the key, so an undeclared model there is a real statement
that its default is not none, and temperature is dropped."""
mapped = OpenAIGPT5Config().map_openai_params(
non_default_params={"temperature": 0},
optional_params={},
model="gpt-5.6-terra",
drop_params=True,
)
assert "temperature" not in mapped

View file

@ -1018,6 +1018,10 @@ def test_aaamodel_prices_and_context_window_json_is_valid():
"type": "array", "type": "array",
"items": {"type": "string", "enum": ["none", "minimal", "low", "medium", "high", "xhigh", "max"]}, "items": {"type": "string", "enum": ["none", "minimal", "low", "medium", "high", "xhigh", "max"]},
}, },
"default_reasoning_effort": {
"type": "string",
"enum": ["none", "minimal", "low", "medium", "high", "xhigh"],
},
"supports_adaptive_thinking": {"type": "boolean"}, "supports_adaptive_thinking": {"type": "boolean"},
"supports_legacy_thinking": {"type": "boolean"}, "supports_legacy_thinking": {"type": "boolean"},
"thinking_always_on": {"type": "boolean"}, "thinking_always_on": {"type": "boolean"},
@ -5639,3 +5643,27 @@ def test_snapshot_exception_for_hook_preserves_suppress_context_flag() -> None:
snapshot = _snapshot_exception_for_hook(e) snapshot = _snapshot_exception_for_hook(e)
assert snapshot.__suppress_context__ is False assert snapshot.__suppress_context__ is False
assert snapshot.__context__ is e.__context__ assert snapshot.__context__ is e.__context__
class TestDefaultReasoningEffortHydration:
"""`get_model_info` is the public shape every other capability key is readable through, so
the declared default has to survive hydration too, not only the raw-map fallback the
request-path gate happens to reach it by.
"""
@pytest.mark.parametrize(
"model, provider",
[("gpt-5.1", "openai"), ("gpt-5.4", "openai"), ("azure/gpt-5.1", "azure")],
)
def test_the_declared_default_survives_model_info_hydration(self, local_model_cost_map, model, provider):
from litellm.utils import _get_model_info_helper
model_info = dict(_get_model_info_helper(model=model, custom_llm_provider=provider))
assert model_info["default_reasoning_effort"] == "none"
def test_a_model_that_declares_nothing_hydrates_to_none(self, local_model_cost_map):
"""Absent means "the map does not say", which the gate reads as reasoning being active."""
from litellm.utils import _get_model_info_helper
model_info = dict(_get_model_info_helper(model="gpt-5.6-terra", custom_llm_provider="openai"))
assert model_info.get("default_reasoning_effort") is None