mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-22 22:24:10 +00:00
fix: restore _get_effort_level and is_model_gpt_5_4_plus_model (PR #23151)
Independent fix (base: main) collaterally removed by PR #23276. Restores: - _get_effort_level() for extracting effort from string or dict - is_model_gpt_5_4_plus_model() classmethod - effective_effort usage in xhigh/tool-drop/sampling/temperature guards - Azure: _get_effort_level import and usage for dict reasoning_effort - Azure: gpt-5.4+ tool+reasoning drop logic
This commit is contained in:
@@ -4,7 +4,10 @@ from typing import List
|
||||
|
||||
import litellm
|
||||
from litellm.exceptions import UnsupportedParamsError
|
||||
from litellm.llms.openai.chat.gpt_5_transformation import OpenAIGPT5Config
|
||||
from litellm.llms.openai.chat.gpt_5_transformation import (
|
||||
OpenAIGPT5Config,
|
||||
_get_effort_level,
|
||||
)
|
||||
from litellm.types.llms.openai import AllMessageValues
|
||||
|
||||
from .gpt_transformation import AzureOpenAIConfig
|
||||
@@ -85,20 +88,21 @@ class AzureOpenAIGPT5Config(AzureOpenAIConfig, OpenAIGPT5Config):
|
||||
non_default_params.get("reasoning_effort")
|
||||
or optional_params.get("reasoning_effort")
|
||||
)
|
||||
effective_effort = _get_effort_level(reasoning_effort_value)
|
||||
|
||||
# gpt-5.1/5.2/5.4 support reasoning_effort='none', but other gpt-5 models don't
|
||||
# See: https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/reasoning
|
||||
supports_none = self._supports_reasoning_effort_level(model, "none")
|
||||
|
||||
if reasoning_effort_value == "none" and not supports_none:
|
||||
if effective_effort == "none" and not supports_none:
|
||||
if litellm.drop_params is True or (
|
||||
drop_params is not None and drop_params is True
|
||||
):
|
||||
non_default_params = non_default_params.copy()
|
||||
optional_params = optional_params.copy()
|
||||
if non_default_params.get("reasoning_effort") == "none":
|
||||
if _get_effort_level(non_default_params.get("reasoning_effort")) == "none":
|
||||
non_default_params.pop("reasoning_effort")
|
||||
if optional_params.get("reasoning_effort") == "none":
|
||||
if _get_effort_level(optional_params.get("reasoning_effort")) == "none":
|
||||
optional_params.pop("reasoning_effort")
|
||||
else:
|
||||
raise UnsupportedParamsError(
|
||||
@@ -121,9 +125,19 @@ class AzureOpenAIGPT5Config(AzureOpenAIConfig, OpenAIGPT5Config):
|
||||
)
|
||||
|
||||
# Only drop reasoning_effort='none' for models that don't support it
|
||||
if result.get("reasoning_effort") == "none" and not supports_none:
|
||||
result_effort = _get_effort_level(result.get("reasoning_effort"))
|
||||
if result_effort == "none" and not supports_none:
|
||||
result.pop("reasoning_effort")
|
||||
|
||||
# Azure Chat Completions: gpt-5.4+ does not support tools + reasoning together.
|
||||
# Drop reasoning_effort when both are present (OpenAI routes to Responses API; Azure does not).
|
||||
if self.is_model_gpt_5_4_plus_model(model):
|
||||
has_tools = bool(
|
||||
non_default_params.get("tools") or optional_params.get("tools")
|
||||
)
|
||||
if has_tools and result_effort not in (None, "none"):
|
||||
result.pop("reasoning_effort", None)
|
||||
|
||||
return result
|
||||
|
||||
def transform_request(
|
||||
|
||||
@@ -25,6 +25,22 @@ def _normalize_reasoning_effort_for_chat_completion(
|
||||
return None
|
||||
|
||||
|
||||
def _get_effort_level(value: Union[str, dict, None]) -> Optional[str]:
|
||||
"""Extract the effective effort level from reasoning_effort (string or dict).
|
||||
|
||||
Use this for guards that compare effort level (e.g. xhigh validation, "none" checks).
|
||||
Ensures dict inputs like {"effort": "none", "summary": "detailed"} are correctly
|
||||
treated as effort="none" for validation purposes.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, str):
|
||||
return value
|
||||
if isinstance(value, dict) and "effort" in value:
|
||||
return value["effort"]
|
||||
return None
|
||||
|
||||
|
||||
class OpenAIGPT5Config(OpenAIGPTConfig):
|
||||
"""Configuration for gpt-5 models including GPT-5-Codex variants.
|
||||
|
||||
@@ -70,6 +86,19 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
|
||||
model_name = model.split("/")[-1]
|
||||
return model_name.startswith("gpt-5.4")
|
||||
|
||||
@classmethod
|
||||
def is_model_gpt_5_4_plus_model(cls, model: str) -> bool:
|
||||
"""Check if the model is gpt-5.4 or newer (5.4, 5.5, 5.6, etc., including pro)."""
|
||||
model_name = model.split("/")[-1]
|
||||
if not model_name.startswith("gpt-5."):
|
||||
return False
|
||||
try:
|
||||
version_str = model_name.replace("gpt-5.", "").split("-")[0]
|
||||
major = version_str.split(".")[0]
|
||||
return int(major) >= 4
|
||||
except (ValueError, IndexError):
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _supports_reasoning_effort_level(cls, model: str, level: str) -> bool:
|
||||
"""Check if the model supports a specific reasoning_effort level.
|
||||
@@ -150,21 +179,32 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
|
||||
drop_params=drop_params,
|
||||
)
|
||||
|
||||
# Normalize reasoning_effort: chat completion API expects a string, not a dict
|
||||
# (e.g. {'effort': 'high', 'summary': 'detailed'} -> 'high')
|
||||
# Get raw reasoning_effort and effective effort level for all guards.
|
||||
# Use effective_effort (extracted string) for xhigh validation, "none" checks, and
|
||||
# tool/sampling guards — dict inputs like {"effort": "none", "summary": "detailed"}
|
||||
# must be treated as effort="none" to avoid incorrect tool-drop or sampling errors.
|
||||
raw_reasoning_effort = (
|
||||
non_default_params.get("reasoning_effort")
|
||||
or optional_params.get("reasoning_effort")
|
||||
)
|
||||
normalized = _normalize_reasoning_effort_for_chat_completion(raw_reasoning_effort)
|
||||
if raw_reasoning_effort is not None and normalized is not None:
|
||||
if "reasoning_effort" in non_default_params:
|
||||
non_default_params["reasoning_effort"] = normalized
|
||||
if "reasoning_effort" in optional_params:
|
||||
optional_params["reasoning_effort"] = normalized
|
||||
effective_effort = _get_effort_level(raw_reasoning_effort)
|
||||
|
||||
reasoning_effort = normalized or raw_reasoning_effort
|
||||
if reasoning_effort is not None and reasoning_effort == "xhigh":
|
||||
# Normalize to string for Chat Completions API when dict has only "effort".
|
||||
# Preserve full dict (e.g. {"effort": "high", "summary": "detailed"}) for Responses API.
|
||||
if isinstance(raw_reasoning_effort, dict) and set(raw_reasoning_effort.keys()) <= {"effort"}:
|
||||
normalized = _normalize_reasoning_effort_for_chat_completion(raw_reasoning_effort)
|
||||
if normalized is not None:
|
||||
if "reasoning_effort" in non_default_params:
|
||||
non_default_params["reasoning_effort"] = normalized
|
||||
if "reasoning_effort" in optional_params:
|
||||
optional_params["reasoning_effort"] = normalized
|
||||
|
||||
reasoning_effort = (
|
||||
non_default_params.get("reasoning_effort")
|
||||
or optional_params.get("reasoning_effort")
|
||||
or raw_reasoning_effort
|
||||
)
|
||||
if effective_effort is not None and effective_effort == "xhigh":
|
||||
if not self._supports_reasoning_effort_level(model, "xhigh"):
|
||||
if litellm.drop_params or drop_params:
|
||||
non_default_params.pop("reasoning_effort", None)
|
||||
@@ -191,17 +231,20 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
|
||||
has_tools = bool(
|
||||
non_default_params.get("tools") or optional_params.get("tools")
|
||||
)
|
||||
if has_tools and reasoning_effort not in (None, "none"):
|
||||
non_default_params.pop("reasoning_effort", None)
|
||||
optional_params.pop("reasoning_effort", None)
|
||||
reasoning_effort = None
|
||||
if has_tools and effective_effort not in (None, "none"):
|
||||
# Check if this will be routed to Responses API
|
||||
# If so, keep reasoning_effort; otherwise drop it for chat completions API
|
||||
if not self.is_model_gpt_5_4_plus_model(model):
|
||||
non_default_params.pop("reasoning_effort", None)
|
||||
optional_params.pop("reasoning_effort", None)
|
||||
reasoning_effort = None
|
||||
|
||||
# gpt-5.1/5.2 support logprobs, top_p, top_logprobs only when reasoning_effort="none"
|
||||
supports_none = self._supports_reasoning_effort_level(model, "none")
|
||||
if supports_none:
|
||||
sampling_params = ["logprobs", "top_logprobs", "top_p"]
|
||||
has_sampling = any(p in non_default_params for p in sampling_params)
|
||||
if has_sampling and reasoning_effort not in (None, "none"):
|
||||
if has_sampling and effective_effort not in (None, "none"):
|
||||
if litellm.drop_params or drop_params:
|
||||
for p in sampling_params:
|
||||
non_default_params.pop(p, None)
|
||||
@@ -211,7 +254,7 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
|
||||
"gpt-5.1/5.2/5.4 only support logprobs, top_p, top_logprobs when "
|
||||
"reasoning_effort='none'. Current reasoning_effort='{}'. "
|
||||
"To drop unsupported params set `litellm.drop_params = True`"
|
||||
).format(reasoning_effort),
|
||||
).format(effective_effort),
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
@@ -219,7 +262,7 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
|
||||
temperature_value: Optional[float] = non_default_params.pop("temperature")
|
||||
if temperature_value is not None:
|
||||
# models supporting reasoning_effort="none" also support flexible temperature
|
||||
if supports_none and (reasoning_effort == "none" or reasoning_effort is None):
|
||||
if supports_none and (effective_effort == "none" or effective_effort is None):
|
||||
optional_params["temperature"] = temperature_value
|
||||
elif temperature_value == 1:
|
||||
optional_params["temperature"] = temperature_value
|
||||
|
||||
Reference in New Issue
Block a user