fix: treat gpt-6 names as the gpt-5 request family in OpenAI and Azure configs (#39631)

Adapted for stable/1.98.x: requires_max_completion_tokens and its test are not on this line (added upstream by #36857), so that hunk is dropped. The Responses parametrize cases target a test class this line does not have, so the two gpt-6-astra cases are kept as a standalone test in the same file.

(cherry picked from commit 025a3ca42f)
This commit is contained in:
Mateo Wang 2026-09-03 14:00:59 -07:00 • committed by Yuneng Jiang
parent 86f64b0f88
commit c6024f3d5f
No known key found for this signature in database
8 changed files with 68 additions and 36 deletions

View file

@ -7,6 +7,7 @@ from litellm.exceptions import UnsupportedParamsError
from litellm.llms.openai.chat.gpt_5_transformation import (
OpenAIGPT5Config,
_get_effort_level,
is_gpt_reasoning_series_name,
)
from litellm.types.llms.openai import AllMessageValues
@ -35,26 +36,7 @@ class AzureOpenAIGPT5Config(AzureOpenAIConfig, OpenAIGPT5Config):
@classmethod
def is_model_gpt_5_model(cls, model: str) -> bool:
"""Check if the Azure model string refers to a gpt-5 variant.
Accepts both explicit gpt-5 model names and the ``gpt5_series/`` prefix
used for manual routing.
"""
# The gpt-5-chat* family (gpt-5-chat, gpt-5-chat-latest, gpt-5-chat-2025-08-07,
# …) are regular chat models: they support temperature and tool_choice but NOT
# reasoning_effort. They must NOT be routed through the GPT-5 reasoning path.
#
# Versioned chat models such as gpt-5.3-chat and gpt-5.1-chat ARE reasoning
# models and must stay on the GPT-5 path. The distinguishing feature is that
# the gpt-5-chat family has a literal "-chat" immediately after "gpt-5"
# (i.e. "gpt-5-chat…"), while versioned chat models interpose a minor version
# number (i.e. "gpt-5.<digit>-chat").
#
# Using a startswith("gpt-5-chat") prefix check on the normalized name (rather
# than a substring check) makes this boundary explicit and avoids any ambiguity
# if future model names coincidentally contain "gpt-5-chat" as an interior run.
_normalized: Final = model.split("/")[-1] # strip provider prefix, e.g. "azure/"
return ("gpt-5" in model and not _normalized.startswith("gpt-5-chat")) or "gpt5_series" in model
return is_gpt_reasoning_series_name(model) or "gpt5_series" in model
def get_supported_openai_params(self, model: str) -> list[str]:
"""Get supported parameters for Azure OpenAI GPT-5 models.

View file

@ -44,6 +44,14 @@ def _get_effort_level(value: str | dict | None) -> str | None:
return None
GPT_REASONING_SERIES_MARKERS: Final = ("gpt-5", "gpt-6")
def is_gpt_reasoning_series_name(model: str) -> bool:
normalized: Final = model.split("/")[-1]
return any(marker in model for marker in GPT_REASONING_SERIES_MARKERS) and not normalized.startswith("gpt-5-chat")
class OpenAIGPT5Config(OpenAIGPTConfig):
"""Configuration for gpt-5 models including GPT-5-Codex variants.
@ -56,21 +64,7 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
@classmethod
def is_model_gpt_5_model(cls, model: str) -> bool:
# The gpt-5-chat* family (gpt-5-chat, gpt-5-chat-latest, gpt-5-chat-2025-08-07,
# …) are regular chat models: they support temperature and tool_choice but NOT
# reasoning_effort. They must NOT be routed through the GPT-5 reasoning path.
#
# Versioned chat models such as gpt-5.3-chat and gpt-5.1-chat ARE reasoning
# models and must stay on the GPT-5 path. The distinguishing feature is that
# the gpt-5-chat family has a literal "-chat" immediately after "gpt-5"
# (i.e. "gpt-5-chat…"), while versioned chat models interpose a minor version
# number (i.e. "gpt-5.<digit>-chat").
#
# Using a startswith("gpt-5-chat") prefix check on the normalized name (rather
# than a substring check) makes this boundary explicit and avoids any ambiguity
# if future model names coincidentally contain "gpt-5-chat" as an interior run.
_normalized: Final = model.split("/")[-1] # strip provider prefix, e.g. "openai/"
return "gpt-5" in model and not _normalized.startswith("gpt-5-chat")
return is_gpt_reasoning_series_name(model)
@classmethod
def is_model_gpt_5_search_model(cls, model: str) -> bool:
@ -105,6 +99,8 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
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: Final = model.split("/")[-1]
if model_name.startswith("gpt-6"):
return True
if not model_name.startswith("gpt-5."):
return False
try:

View file

@ -12,6 +12,7 @@ from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response impo
)
from litellm.litellm_core_utils.url_utils import encode_url_path_segment
from litellm.llms.base_llm.responses.transformation import BaseResponsesAPIConfig
from litellm.llms.openai.chat.gpt_5_transformation import is_gpt_reasoning_series_name
from litellm.secret_managers.main import get_secret_str
from litellm.types.llms.openai import *
from litellm.types.responses.main import *
@ -48,7 +49,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
parts: Final = model.split("/")
if len(parts) > 1 and parts[0] not in ("openai",):
return False
return "gpt-5" in model and "gpt-5-chat" not in model
return is_gpt_reasoning_series_name(model)
@staticmethod
def _supports_reasoning_effort_none(model: str) -> bool:

View file

@ -299,3 +299,15 @@ 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
def test_azure_gpt_6_astra_takes_the_reasoning_series_request_shape():
params = litellm.get_optional_params(
model="gpt-6-astra",
custom_llm_provider="azure",
max_tokens=100,
reasoning_effort="max",
)
assert params["max_completion_tokens"] == 100
assert "max_tokens" not in params
assert params["reasoning_effort"] == "max"

View file

@ -1540,3 +1540,13 @@ class TestPhaseParameter:
assert validated[0]["phase"] == "commentary"
assert validated[1]["phase"] == "final_answer"
assert "phase" not in validated[2]
@pytest.mark.parametrize("effort", [None, "low"])
def test_gpt_6_astra_drops_temperature_on_the_responses_path(effort):
mapped = OpenAIResponsesAPIConfig().map_openai_params(
response_api_optional_params={"temperature": 0, **({"reasoning": {"effort": effort}} if effort else {})},
model="gpt-6-astra",
drop_params=True,
)
assert "temperature" not in mapped

View file

@ -1309,3 +1309,17 @@ def test_responses_gpt54_allow_temperature_effort_none(
drop_params=False,
)
assert params["temperature"] == 0.7
def test_gpt_6_astra_takes_the_reasoning_series_request_shape():
params = litellm.get_optional_params(
model="gpt-6-astra",
custom_llm_provider="openai",
max_tokens=100,
reasoning_effort="max",
verbosity="low",
)
assert params["max_completion_tokens"] == 100
assert "max_tokens" not in params
assert params["reasoning_effort"] == "max"
assert params["verbosity"] == "low"

View file

@ -41,6 +41,8 @@ from litellm.llms.azure.chat.gpt_5_transformation import AzureOpenAIGPT5Config
# Models that MUST be classified as GPT-5 (routed through GPT-5 reasoning path)
GPT5_MODELS = [
"gpt-6-astra",
"openai/gpt-6-astra",
"gpt-5",
"gpt-5.1",
"gpt-5.2",
@ -120,6 +122,8 @@ class TestOpenAIGPT5ConfigIsModelGpt5Model:
# /v1/responses bridge (when reasoning_effort is set and tools are passed) on
# is_model_gpt_5_4_plus_model, so the gpt-5.6 family must land on the True side.
GPT5_4_PLUS_MODELS = [
"gpt-6-astra",
"openai/gpt-6-astra",
"gpt-5.4",
"gpt-5.5",
"gpt-5.5-pro",

View file

@ -778,6 +778,19 @@ def test_responses_api_bridge_check_gpt_5_4_tools_plus_reasoning_routes_to_respo
assert model_info.get("mode") == "responses"
def test_responses_api_bridge_check_gpt_6_astra_tools_with_default_reasoning_routes_to_responses():
from litellm.main import responses_api_bridge_check
model_info, model = responses_api_bridge_check(
model="gpt-6-astra",
custom_llm_provider="openai",
tools=[{"type": "function", "function": {"name": "get_capital"}}],
)
assert model == "gpt-6-astra"
assert model_info.get("mode") == "responses"
def test_responses_api_bridge_check_gpt_5_5_tools_plus_reasoning_routes_to_responses():
"""gpt-5.5+ with both tools and reasoning_effort should route to Responses API."""
from litellm.main import responses_api_bridge_check