mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-11 03:38:38 +00:00
fix(azure): apply api_version gating to o-series and gpt-5 param mapping
Azure deployments backed by the dedicated o-series (AzureOpenAIO1Config) and GPT-5 (AzureOpenAIGPT5Config) transformations bypassed the Azure api_version gating that the standard Azure path applies to tool_choice and response_format. Two sibling Azure deployments behaved differently for the same request: the non-o-series path correctly raised on tool_choice="required" for old api_versions and converted json_schema response_format into a tool call, while o-series and GPT-5 forwarded both params unchanged. Root cause: get_optional_params did not pass api_version to the o-series and GPT-5 map_openai_params calls, and AzureOpenAIO1Config inherited the OpenAI mapper (which is unaware of api_version). This routes tool_choice and response_format through AzureOpenAIConfig.map_openai_params so the gating is consistent across all Azure model families.
This commit is contained in:
parent
2655d1dd5e
commit
c6aa604c2d
5 changed files with 225 additions and 7 deletions
|
|
@ -131,14 +131,36 @@ class AzureOpenAIGPT5Config(AzureOpenAIConfig, OpenAIGPT5Config):
|
|||
),
|
||||
)
|
||||
|
||||
azure_gated_params = {
|
||||
param: value
|
||||
for param, value in non_default_params.items()
|
||||
if param in ("tool_choice", "response_format")
|
||||
}
|
||||
|
||||
result = OpenAIGPT5Config.map_openai_params(
|
||||
self,
|
||||
non_default_params=non_default_params,
|
||||
non_default_params={
|
||||
param: value
|
||||
for param, value in non_default_params.items()
|
||||
if param not in azure_gated_params
|
||||
},
|
||||
optional_params=optional_params,
|
||||
model=model,
|
||||
drop_params=drop_params,
|
||||
)
|
||||
|
||||
# tool_choice and response_format need Azure's api_version gating, which the
|
||||
# OpenAI gpt-5 mapper does not apply.
|
||||
if azure_gated_params:
|
||||
result = AzureOpenAIConfig.map_openai_params(
|
||||
self,
|
||||
non_default_params=azure_gated_params,
|
||||
optional_params=result,
|
||||
model=model,
|
||||
drop_params=drop_params,
|
||||
api_version=api_version,
|
||||
)
|
||||
|
||||
# Only drop reasoning_effort='none' for models that don't support it
|
||||
result_effort = _get_effort_level(result.get("reasoning_effort"))
|
||||
if result_effort == "none" and not supports_none:
|
||||
|
|
|
|||
|
|
@ -20,9 +20,54 @@ from litellm.types.llms.openai import AllMessageValues
|
|||
from litellm.utils import get_model_info, supports_reasoning
|
||||
|
||||
from ...openai.chat.o_series_transformation import OpenAIOSeriesConfig
|
||||
from .gpt_transformation import AzureOpenAIConfig
|
||||
|
||||
|
||||
class AzureOpenAIO1Config(OpenAIOSeriesConfig):
|
||||
def map_openai_params(
|
||||
self,
|
||||
non_default_params: dict,
|
||||
optional_params: dict,
|
||||
model: str,
|
||||
drop_params: bool,
|
||||
api_version: str = "",
|
||||
) -> dict:
|
||||
"""
|
||||
Map OpenAI params for Azure O-Series models.
|
||||
|
||||
O-Series specific translations (e.g. max_tokens -> max_completion_tokens) are
|
||||
handled by the OpenAI o-series mapper. tool_choice and response_format must
|
||||
additionally go through the Azure api_version gating in AzureOpenAIConfig, which
|
||||
the OpenAI mapper is not aware of.
|
||||
"""
|
||||
azure_gated_params = {
|
||||
param: value
|
||||
for param, value in non_default_params.items()
|
||||
if param in ("tool_choice", "response_format")
|
||||
}
|
||||
|
||||
optional_params = super().map_openai_params(
|
||||
non_default_params={
|
||||
param: value
|
||||
for param, value in non_default_params.items()
|
||||
if param not in azure_gated_params
|
||||
},
|
||||
optional_params=optional_params,
|
||||
model=model,
|
||||
drop_params=drop_params,
|
||||
)
|
||||
|
||||
if azure_gated_params:
|
||||
optional_params = AzureOpenAIConfig().map_openai_params(
|
||||
non_default_params=azure_gated_params,
|
||||
optional_params=optional_params,
|
||||
model=model,
|
||||
drop_params=drop_params,
|
||||
api_version=api_version,
|
||||
)
|
||||
|
||||
return optional_params
|
||||
|
||||
def get_supported_openai_params(self, model: str) -> list:
|
||||
"""
|
||||
Get the supported OpenAI params for the Azure O-Series models
|
||||
|
|
|
|||
|
|
@ -4841,6 +4841,12 @@ def get_optional_params( # noqa: PLR0915
|
|||
)
|
||||
elif custom_llm_provider == "azure":
|
||||
_azure_detection_model = base_model or model
|
||||
api_version = (
|
||||
api_version
|
||||
or litellm.api_version
|
||||
or get_secret("AZURE_API_VERSION")
|
||||
or litellm.AZURE_DEFAULT_API_VERSION
|
||||
)
|
||||
if litellm.AzureOpenAIO1Config().is_o_series_model(
|
||||
model=_azure_detection_model
|
||||
):
|
||||
|
|
@ -4848,6 +4854,7 @@ def get_optional_params( # noqa: PLR0915
|
|||
non_default_params=non_default_params,
|
||||
optional_params=optional_params,
|
||||
model=_azure_detection_model,
|
||||
api_version=api_version, # type: ignore
|
||||
drop_params=(
|
||||
drop_params
|
||||
if drop_params is not None and isinstance(drop_params, bool)
|
||||
|
|
@ -4861,6 +4868,7 @@ def get_optional_params( # noqa: PLR0915
|
|||
non_default_params=non_default_params,
|
||||
optional_params=optional_params,
|
||||
model=_azure_detection_model,
|
||||
api_version=api_version, # type: ignore
|
||||
drop_params=(
|
||||
drop_params
|
||||
if drop_params is not None and isinstance(drop_params, bool)
|
||||
|
|
@ -4873,12 +4881,6 @@ def get_optional_params( # noqa: PLR0915
|
|||
api_version, litellm.api_version, get_secret("AZURE_API_VERSION")
|
||||
)
|
||||
)
|
||||
api_version = (
|
||||
api_version
|
||||
or litellm.api_version
|
||||
or get_secret("AZURE_API_VERSION")
|
||||
or litellm.AZURE_DEFAULT_API_VERSION
|
||||
)
|
||||
optional_params = litellm.AzureOpenAIConfig().map_openai_params(
|
||||
non_default_params=non_default_params,
|
||||
optional_params=optional_params,
|
||||
|
|
|
|||
|
|
@ -28,3 +28,84 @@ async def test_azure_chat_o_series_transformation():
|
|||
)
|
||||
print(response)
|
||||
assert response["model"] == "web-interface-o1-mini"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model", ["o1", "o3-mini"])
|
||||
def test_azure_o_series_tool_choice_required_gated_by_api_version(model):
|
||||
"""
|
||||
tool_choice='required' is not supported by Azure on api_version<=2024-05-01.
|
||||
|
||||
Azure o-series deployments must honor the same api_version gating as the
|
||||
non-o-series Azure path instead of silently forwarding 'required'.
|
||||
"""
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "f",
|
||||
"parameters": {"type": "object", "properties": {}},
|
||||
},
|
||||
}
|
||||
]
|
||||
with pytest.raises(litellm.UnsupportedParamsError):
|
||||
litellm.get_optional_params(
|
||||
model=model,
|
||||
custom_llm_provider="azure",
|
||||
tool_choice="required",
|
||||
tools=tools,
|
||||
api_version="2024-05-01-preview",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
# newer api_version supports it
|
||||
params = litellm.get_optional_params(
|
||||
model=model,
|
||||
custom_llm_provider="azure",
|
||||
tool_choice="required",
|
||||
tools=tools,
|
||||
api_version="2025-01-01-preview",
|
||||
drop_params=False,
|
||||
)
|
||||
assert params["tool_choice"] == "required"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model", ["o1", "o3-mini"])
|
||||
def test_azure_o_series_response_format_falls_back_to_tools_on_old_api_version(model):
|
||||
"""
|
||||
On api_versions that predate native json_schema support, Azure o-series should
|
||||
convert response_format into a tool call, matching the non-o-series Azure path.
|
||||
"""
|
||||
response_format = {
|
||||
"type": "json_schema",
|
||||
"json_schema": {
|
||||
"name": "r",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"a": {"type": "string"}},
|
||||
"required": ["a"],
|
||||
},
|
||||
"strict": True,
|
||||
},
|
||||
}
|
||||
params = litellm.get_optional_params(
|
||||
model=model,
|
||||
custom_llm_provider="azure",
|
||||
response_format=response_format,
|
||||
api_version="2024-02-01",
|
||||
drop_params=False,
|
||||
)
|
||||
assert "response_format" not in params
|
||||
assert "tools" in params
|
||||
|
||||
|
||||
def test_azure_o_series_still_maps_max_tokens():
|
||||
"""The o-series max_tokens -> max_completion_tokens translation must be preserved."""
|
||||
params = litellm.get_optional_params(
|
||||
model="o3-mini",
|
||||
custom_llm_provider="azure",
|
||||
max_tokens=64,
|
||||
api_version="2025-01-01-preview",
|
||||
drop_params=False,
|
||||
)
|
||||
assert params["max_completion_tokens"] == 64
|
||||
assert "max_tokens" not in params
|
||||
|
|
|
|||
|
|
@ -299,3 +299,71 @@ 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
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model", ["gpt-5", "gpt-5.4-mini"])
|
||||
def test_azure_gpt5_tool_choice_required_gated_by_api_version(model):
|
||||
"""
|
||||
tool_choice='required' is not supported by Azure on api_version<=2024-05-01.
|
||||
|
||||
Azure GPT-5 deployments must honor the same api_version gating as the
|
||||
non-GPT-5 Azure path instead of silently forwarding 'required'.
|
||||
"""
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "f",
|
||||
"parameters": {"type": "object", "properties": {}},
|
||||
},
|
||||
}
|
||||
]
|
||||
with pytest.raises(litellm.UnsupportedParamsError):
|
||||
litellm.get_optional_params(
|
||||
model=model,
|
||||
custom_llm_provider="azure",
|
||||
tool_choice="required",
|
||||
tools=tools,
|
||||
api_version="2024-05-01-preview",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
# newer api_version supports it
|
||||
params = litellm.get_optional_params(
|
||||
model=model,
|
||||
custom_llm_provider="azure",
|
||||
tool_choice="required",
|
||||
tools=tools,
|
||||
api_version="2025-01-01-preview",
|
||||
drop_params=False,
|
||||
)
|
||||
assert params["tool_choice"] == "required"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model", ["gpt-5", "gpt-5.4-mini"])
|
||||
def test_azure_gpt5_response_format_falls_back_to_tools_on_old_api_version(model):
|
||||
"""
|
||||
On api_versions that predate native json_schema support, Azure GPT-5 should
|
||||
convert response_format into a tool call, matching the non-GPT-5 Azure path.
|
||||
"""
|
||||
response_format = {
|
||||
"type": "json_schema",
|
||||
"json_schema": {
|
||||
"name": "r",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"a": {"type": "string"}},
|
||||
"required": ["a"],
|
||||
},
|
||||
"strict": True,
|
||||
},
|
||||
}
|
||||
params = litellm.get_optional_params(
|
||||
model=model,
|
||||
custom_llm_provider="azure",
|
||||
response_format=response_format,
|
||||
api_version="2024-02-01",
|
||||
drop_params=False,
|
||||
)
|
||||
assert "response_format" not in params
|
||||
assert "tools" in params
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue