mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
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:
parent
49affa7c01
commit
e5c3df2da2
13 changed files with 473 additions and 44 deletions
|
|
@ -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.",
|
||||
"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,
|
||||
"audio_transcription_config": STRING,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,19 +19,19 @@ class AzureOpenAIGPT5Config(AzureOpenAIConfig, OpenAIGPT5Config):
|
|||
GPT5_SERIES_ROUTE = "gpt5_series/"
|
||||
|
||||
@classmethod
|
||||
def _supports_reasoning_effort_level(cls, model: str, level: str) -> bool:
|
||||
"""Override to handle gpt5_series/ prefix used for Azure routing.
|
||||
def _model_map_lookup_name(cls, model: str) -> str:
|
||||
"""Normalise an Azure routing name to its cost-map key.
|
||||
|
||||
The parent class calls ``_supports_factory(model, custom_llm_provider=None)``
|
||||
which fails to resolve ``gpt5_series/gpt-5.1`` to the correct Azure model
|
||||
entry. Strip the prefix and prepend ``azure/`` so the lookup finds
|
||||
``azure/gpt-5.1`` in model_prices_and_context_window.json.
|
||||
Neither ``gpt5_series/gpt-5.1`` nor a bare ``gpt-5.1`` is a key in
|
||||
model_prices_and_context_window.json; ``azure/gpt-5.1`` is. Overriding the shared
|
||||
resolver rather than one lookup means the supports, explicitly-disabled and
|
||||
default-effort answers all read the same entry.
|
||||
"""
|
||||
if model.startswith(cls.GPT5_SERIES_ROUTE):
|
||||
model = "azure/" + model[len(cls.GPT5_SERIES_ROUTE) :]
|
||||
elif not model.startswith("azure/"):
|
||||
model = "azure/" + model
|
||||
return super()._supports_reasoning_effort_level(model, level)
|
||||
return "azure/" + model[len(cls.GPT5_SERIES_ROUTE) :]
|
||||
if model.startswith("azure/"):
|
||||
return model
|
||||
return "azure/" + model
|
||||
|
||||
@classmethod
|
||||
def is_model_gpt_5_model(cls, model: str) -> bool:
|
||||
|
|
|
|||
|
|
@ -6,11 +6,28 @@ import litellm
|
|||
from litellm.utils import (
|
||||
_is_explicitly_disabled_factory,
|
||||
_supports_factory,
|
||||
declared_value_factory,
|
||||
)
|
||||
|
||||
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(
|
||||
value: str | dict | None,
|
||||
) -> str | None:
|
||||
|
|
@ -114,6 +131,17 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
|
|||
except (ValueError, IndexError):
|
||||
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
|
||||
def _supports_reasoning_effort_level(cls, model: str, level: str) -> bool:
|
||||
"""Check if the model supports a specific reasoning_effort level.
|
||||
|
|
@ -123,11 +151,40 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
|
|||
Returns False for unknown models (safe fallback).
|
||||
"""
|
||||
return _supports_factory(
|
||||
model=model,
|
||||
model=cls._model_map_lookup_name(model),
|
||||
custom_llm_provider=None,
|
||||
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
|
||||
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.
|
||||
|
|
@ -140,7 +197,7 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
|
|||
Use this for opt-out checks where unknown models should be allowed through.
|
||||
"""
|
||||
return _is_explicitly_disabled_factory(
|
||||
model=model,
|
||||
model=cls._model_map_lookup_name(model),
|
||||
custom_llm_provider=None,
|
||||
key=f"supports_{level}_reasoning_effort",
|
||||
)
|
||||
|
|
@ -260,15 +317,16 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
|
|||
if supports_none:
|
||||
sampling_params: Final = ["logprobs", "top_logprobs", "top_p"]
|
||||
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:
|
||||
for p in sampling_params:
|
||||
non_default_params.pop(p, None)
|
||||
else:
|
||||
raise litellm.utils.UnsupportedParamsError(
|
||||
message=(
|
||||
"gpt-5.1/5.2/5.4 only support logprobs, top_p, top_logprobs when "
|
||||
f"reasoning_effort='none'. Current reasoning_effort='{effective_effort}'. "
|
||||
f"{model} only supports logprobs, top_p, top_logprobs when reasoning_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`"
|
||||
),
|
||||
status_code=400,
|
||||
|
|
@ -277,17 +335,19 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
|
|||
if "temperature" in non_default_params:
|
||||
temperature_value: Final[float | None] = non_default_params.pop("temperature")
|
||||
if temperature_value is not None:
|
||||
# models supporting reasoning_effort="none" also support flexible temperature
|
||||
if supports_none and (effective_effort == "none" or effective_effort is None) or temperature_value == 1:
|
||||
# a non-default temperature rides on the effort resolving to "none", not on
|
||||
# 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
|
||||
elif litellm.drop_params or drop_params:
|
||||
pass
|
||||
else:
|
||||
raise litellm.utils.UnsupportedParamsError(
|
||||
message=(
|
||||
f"gpt-5 models (including gpt-5-codex) don't support temperature={temperature_value}. "
|
||||
"Only temperature=1 is supported. "
|
||||
"For gpt-5.1, temperature is supported when reasoning_effort='none' (or not specified, as it defaults to 'none'). "
|
||||
f"{model} doesn't support temperature={temperature_value} while reasoning is "
|
||||
"active. Only temperature=1 is supported unless reasoning_effort resolves to "
|
||||
"'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`"
|
||||
),
|
||||
status_code=400,
|
||||
|
|
|
|||
|
|
@ -61,6 +61,20 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
|||
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
|
||||
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.
|
||||
|
|
@ -116,17 +130,17 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
|||
reasoning: Final = params.get("reasoning") or {}
|
||||
effort: Final = reasoning.get("effort") if isinstance(reasoning, dict) else None
|
||||
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
|
||||
elif drop_params or litellm.drop_params:
|
||||
params.pop("temperature", None)
|
||||
else:
|
||||
raise litellm.UnsupportedParamsError(
|
||||
message=(
|
||||
f"gpt-5 models don't support temperature={temperature}. "
|
||||
"Only temperature=1 is supported. "
|
||||
"For models like gpt-5.1/5.4, temperature is supported "
|
||||
"when reasoning.effort='none' (or not specified). "
|
||||
f"{model} doesn't support temperature={temperature} while reasoning is "
|
||||
"active. Only temperature=1 is supported unless reasoning.effort resolves "
|
||||
"to '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`"
|
||||
),
|
||||
status_code=400,
|
||||
|
|
|
|||
|
|
@ -3409,6 +3409,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -3456,6 +3457,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -3589,6 +3591,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": false
|
||||
},
|
||||
|
|
@ -3630,6 +3633,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": false
|
||||
},
|
||||
|
|
@ -3671,6 +3675,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": false
|
||||
},
|
||||
|
|
@ -3712,6 +3717,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": false
|
||||
},
|
||||
|
|
@ -3937,7 +3943,8 @@
|
|||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none"
|
||||
},
|
||||
"azure/eu/gpt-5.1-chat": {
|
||||
"cache_read_input_token_cost": 1.4e-07,
|
||||
|
|
@ -3972,7 +3979,8 @@
|
|||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none"
|
||||
},
|
||||
"azure/eu/gpt-5.1-codex": {
|
||||
"deprecation_date": "2027-05-15",
|
||||
|
|
@ -4247,7 +4255,8 @@
|
|||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none"
|
||||
},
|
||||
"azure/global/gpt-5.1-chat": {
|
||||
"cache_read_input_token_cost": 1.25e-07,
|
||||
|
|
@ -4282,7 +4291,8 @@
|
|||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none"
|
||||
},
|
||||
"azure/global/gpt-5.1-codex": {
|
||||
"deprecation_date": "2027-05-15",
|
||||
|
|
@ -5367,6 +5377,7 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"azure/gpt-5.1-chat-2025-11-13": {
|
||||
|
|
@ -5404,7 +5415,8 @@
|
|||
"supports_system_messages": true,
|
||||
"supports_tool_choice": false,
|
||||
"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": {
|
||||
"cache_read_input_token_cost": 1.25e-07,
|
||||
|
|
@ -5833,7 +5845,8 @@
|
|||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none"
|
||||
},
|
||||
"azure/gpt-5.1-chat": {
|
||||
"cache_read_input_token_cost": 1.25e-07,
|
||||
|
|
@ -5868,7 +5881,8 @@
|
|||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none"
|
||||
},
|
||||
"azure/gpt-5.1-codex": {
|
||||
"deprecation_date": "2027-05-15",
|
||||
|
|
@ -6315,6 +6329,7 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -6354,6 +6369,7 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -6393,6 +6409,7 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -6438,6 +6455,7 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -6477,6 +6495,7 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -6516,6 +6535,7 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -7663,6 +7683,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true
|
||||
},
|
||||
"azure/gpt-5.4-mini-2026-03-17": {
|
||||
|
|
@ -7704,6 +7725,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true
|
||||
},
|
||||
"azure/gpt-5.4-nano": {
|
||||
|
|
@ -7745,6 +7767,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true
|
||||
},
|
||||
"azure/gpt-5.4-nano-2026-03-17": {
|
||||
|
|
@ -7786,6 +7809,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true
|
||||
},
|
||||
"azure/gpt-image-1": {
|
||||
|
|
@ -8856,7 +8880,8 @@
|
|||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none"
|
||||
},
|
||||
"azure/us/gpt-5.1-chat": {
|
||||
"cache_read_input_token_cost": 1.4e-07,
|
||||
|
|
@ -8891,7 +8916,8 @@
|
|||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none"
|
||||
},
|
||||
"azure/us/gpt-5.1-codex": {
|
||||
"deprecation_date": "2027-05-15",
|
||||
|
|
@ -26292,6 +26318,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": false,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -26336,6 +26363,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": false,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -26381,6 +26409,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": false,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -26426,6 +26455,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -26471,6 +26501,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -27295,6 +27326,7 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -27343,6 +27375,7 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -27492,6 +27525,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": false
|
||||
},
|
||||
|
|
@ -27543,6 +27577,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": false
|
||||
},
|
||||
|
|
@ -27591,6 +27626,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": false
|
||||
},
|
||||
|
|
@ -27639,6 +27675,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": false
|
||||
},
|
||||
|
|
|
|||
|
|
@ -165,6 +165,7 @@ class ProviderSpecificModelInfo(TypedDict, total=False):
|
|||
supports_xhigh_reasoning_effort: bool | None
|
||||
supports_max_reasoning_effort: bool | 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_image_size: bool | None
|
||||
bedrock_output_config_effort_ceiling: Literal["low", "medium", "high", "max", "xhigh"] | None
|
||||
|
|
|
|||
|
|
@ -2596,6 +2596,46 @@ def _supports_factory(model: str, custom_llm_provider: str | None, key: str) ->
|
|||
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:
|
||||
"""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_max_reasoning_effort=_model_info.get("supports_max_reasoning_effort", 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_converse_supports_strict_tools=_model_info.get("bedrock_converse_supports_strict_tools", None),
|
||||
supports_computer_use=_model_info.get("supports_computer_use", None),
|
||||
|
|
|
|||
|
|
@ -3409,6 +3409,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -3456,6 +3457,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -3589,6 +3591,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": false
|
||||
},
|
||||
|
|
@ -3630,6 +3633,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": false
|
||||
},
|
||||
|
|
@ -3671,6 +3675,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": false
|
||||
},
|
||||
|
|
@ -3712,6 +3717,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": false
|
||||
},
|
||||
|
|
@ -3937,7 +3943,8 @@
|
|||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none"
|
||||
},
|
||||
"azure/eu/gpt-5.1-chat": {
|
||||
"cache_read_input_token_cost": 1.4e-07,
|
||||
|
|
@ -3972,7 +3979,8 @@
|
|||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none"
|
||||
},
|
||||
"azure/eu/gpt-5.1-codex": {
|
||||
"deprecation_date": "2027-05-15",
|
||||
|
|
@ -4247,7 +4255,8 @@
|
|||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none"
|
||||
},
|
||||
"azure/global/gpt-5.1-chat": {
|
||||
"cache_read_input_token_cost": 1.25e-07,
|
||||
|
|
@ -4282,7 +4291,8 @@
|
|||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none"
|
||||
},
|
||||
"azure/global/gpt-5.1-codex": {
|
||||
"deprecation_date": "2027-05-15",
|
||||
|
|
@ -5367,6 +5377,7 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"azure/gpt-5.1-chat-2025-11-13": {
|
||||
|
|
@ -5404,7 +5415,8 @@
|
|||
"supports_system_messages": true,
|
||||
"supports_tool_choice": false,
|
||||
"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": {
|
||||
"cache_read_input_token_cost": 1.25e-07,
|
||||
|
|
@ -5833,7 +5845,8 @@
|
|||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none"
|
||||
},
|
||||
"azure/gpt-5.1-chat": {
|
||||
"cache_read_input_token_cost": 1.25e-07,
|
||||
|
|
@ -5868,7 +5881,8 @@
|
|||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none"
|
||||
},
|
||||
"azure/gpt-5.1-codex": {
|
||||
"deprecation_date": "2027-05-15",
|
||||
|
|
@ -6315,6 +6329,7 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -6354,6 +6369,7 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -6393,6 +6409,7 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -6438,6 +6455,7 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -6477,6 +6495,7 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -6516,6 +6535,7 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -7663,6 +7683,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true
|
||||
},
|
||||
"azure/gpt-5.4-mini-2026-03-17": {
|
||||
|
|
@ -7704,6 +7725,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true
|
||||
},
|
||||
"azure/gpt-5.4-nano": {
|
||||
|
|
@ -7745,6 +7767,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true
|
||||
},
|
||||
"azure/gpt-5.4-nano-2026-03-17": {
|
||||
|
|
@ -7786,6 +7809,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true
|
||||
},
|
||||
"azure/gpt-image-1": {
|
||||
|
|
@ -8856,7 +8880,8 @@
|
|||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none"
|
||||
},
|
||||
"azure/us/gpt-5.1-chat": {
|
||||
"cache_read_input_token_cost": 1.4e-07,
|
||||
|
|
@ -8891,7 +8916,8 @@
|
|||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none"
|
||||
},
|
||||
"azure/us/gpt-5.1-codex": {
|
||||
"deprecation_date": "2027-05-15",
|
||||
|
|
@ -26292,6 +26318,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": false,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -26336,6 +26363,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": false,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -26381,6 +26409,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": false,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -26426,6 +26455,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -26471,6 +26501,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -27295,6 +27326,7 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -27343,6 +27375,7 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
|
|
@ -27492,6 +27525,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": false
|
||||
},
|
||||
|
|
@ -27543,6 +27577,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": false
|
||||
},
|
||||
|
|
@ -27591,6 +27626,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": false
|
||||
},
|
||||
|
|
@ -27639,6 +27675,7 @@
|
|||
"supports_vision": true,
|
||||
"supports_web_search": true,
|
||||
"supports_none_reasoning_effort": true,
|
||||
"default_reasoning_effort": "none",
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": false
|
||||
},
|
||||
|
|
|
|||
|
|
@ -179,6 +179,18 @@
|
|||
"comment": {
|
||||
"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": {
|
||||
"type": "string",
|
||||
"description": "Date the provider deprecates the model, YYYY-MM-DD.",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import pytest
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
|
@ -9,6 +10,15 @@ def config() -> 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):
|
||||
assert "reasoning_effort" in config.get_supported_openai_params(model="gpt-5")
|
||||
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")
|
||||
assert "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
|
||||
|
|
|
|||
|
|
@ -1593,3 +1593,36 @@ class TestPromptCacheOptionsOnResponsesPath:
|
|||
"text": "hi",
|
||||
"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
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
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."""
|
||||
with pytest.raises(
|
||||
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(
|
||||
non_default_params={"temperature": 0.7},
|
||||
|
|
@ -1385,3 +1387,121 @@ def test_gpt5_drops_xhigh_when_requested(config: OpenAIConfig):
|
|||
drop_params=True,
|
||||
)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1018,6 +1018,10 @@ def test_aaamodel_prices_and_context_window_json_is_valid():
|
|||
"type": "array",
|
||||
"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_legacy_thinking": {"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)
|
||||
assert snapshot.__suppress_context__ is False
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue