mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
Merge pull request #38593 from BerriAI/litellm_gpt5_default_reasoning_effort
fix(gpt-5): stop forwarding temperature and top_p to reasoning models that reject them
This commit is contained in:
commit
27c09248e4
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",
|
||||
|
|
@ -26315,6 +26341,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
|
||||
},
|
||||
|
|
@ -26359,6 +26386,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
|
||||
},
|
||||
|
|
@ -26404,6 +26432,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
|
||||
},
|
||||
|
|
@ -26449,6 +26478,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
|
||||
},
|
||||
|
|
@ -26494,6 +26524,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
|
||||
},
|
||||
|
|
@ -27318,6 +27349,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
|
||||
},
|
||||
|
|
@ -27366,6 +27398,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
|
||||
},
|
||||
|
|
@ -27515,6 +27548,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
|
||||
},
|
||||
|
|
@ -27566,6 +27600,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
|
||||
},
|
||||
|
|
@ -27614,6 +27649,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
|
||||
},
|
||||
|
|
@ -27662,6 +27698,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
|
||||
|
|
|
|||
|
|
@ -2606,6 +2606,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``.
|
||||
|
||||
|
|
@ -5910,6 +5950,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",
|
||||
|
|
@ -26315,6 +26341,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
|
||||
},
|
||||
|
|
@ -26359,6 +26386,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
|
||||
},
|
||||
|
|
@ -26404,6 +26432,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
|
||||
},
|
||||
|
|
@ -26449,6 +26478,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
|
||||
},
|
||||
|
|
@ -26494,6 +26524,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
|
||||
},
|
||||
|
|
@ -27318,6 +27349,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
|
||||
},
|
||||
|
|
@ -27366,6 +27398,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
|
||||
},
|
||||
|
|
@ -27515,6 +27548,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
|
||||
},
|
||||
|
|
@ -27566,6 +27600,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
|
||||
},
|
||||
|
|
@ -27614,6 +27649,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
|
||||
},
|
||||
|
|
@ -27662,6 +27698,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
|
||||
|
|
|
|||
|
|
@ -1033,6 +1033,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"},
|
||||
|
|
@ -5661,3 +5665,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