feat(common_utils.py): make default azure openai responses api use /openai/v1/responses logic

Fixes https://github.com/BerriAI/litellm/issues/13527#issuecomment-3177882103
This commit is contained in:
Krrish Dholakia 2025-08-11 23:40:05 -07:00
parent e1fd49ce91
commit 79e262d12b
5 changed files with 129 additions and 108 deletions

View File

@ -2,7 +2,7 @@ import os
from typing import List, Literal
AZURE_DEFAULT_RESPONSES_API_VERSION = str(
os.getenv("AZURE_DEFAULT_RESPONSES_API_VERSION", "2025-04-01-preview")
os.getenv("AZURE_DEFAULT_RESPONSES_API_VERSION", "preview")
)
ROUTER_MAX_FALLBACKS = int(os.getenv("ROUTER_MAX_FALLBACKS", 5))
DEFAULT_BATCH_SIZE = int(os.getenv("DEFAULT_BATCH_SIZE", 512))

View File

@ -694,9 +694,17 @@ class BaseAzureLLM(BaseOpenAILLM):
api_base: Optional[str],
litellm_params: Optional[Union[GenericLiteLLMParams, Dict[str, Any]]],
route: Literal["/openai/responses", "/openai/vector_stores"],
default_api_version: Optional[str] = None,
default_api_version: Optional[Union[str, Literal["latest", "preview"]]] = None,
) -> str:
from litellm.constants import AZURE_DEFAULT_RESPONSES_API_VERSION
"""
Get the base Azure URL for the given route and API version.
Args:
api_base: The base URL of the Azure API.
litellm_params: The litellm parameters.
route: The route to the API.
default_api_version: The default API version to use if no api_version is provided. If 'latest', it will use `openai/v1/...` route.
"""
api_base = api_base or litellm.api_base or get_secret_str("AZURE_API_BASE")
if api_base is None:

View File

@ -630,3 +630,17 @@ def test_azure_openai_responses_bridge():
== "test-azure-computer-use-preview"
)
assert mock_responses.call_args.kwargs["custom_llm_provider"] == "azure"
def test_azure_openai_gpt_5_responses_api():
from litellm import responses
litellm._turn_on_debug()
response = responses(
model="azure/gpt-5",
input="Hello world",
api_key=os.getenv("AZURE_SWEDEN_API_KEY"),
api_base=os.getenv("AZURE_SWEDEN_API_BASE"),
)
print(f"response: {response}")

View File

@ -8,10 +8,14 @@ sys.path.insert(
0, os.path.abspath("../../../../..")
) # Adds the parent directory to the system path
from unittest.mock import MagicMock
from litellm.llms.azure.responses.o_series_transformation import (
AzureOpenAIOSeriesResponsesAPIConfig,
)
from litellm.llms.azure.responses.transformation import AzureOpenAIResponsesAPIConfig
from litellm.llms.azure.responses.o_series_transformation import AzureOpenAIOSeriesResponsesAPIConfig
from litellm.types.router import GenericLiteLLMParams
from litellm.types.llms.openai import ResponsesAPIOptionalRequestParams
from litellm.types.router import GenericLiteLLMParams
@pytest.mark.serial
@ -27,6 +31,7 @@ def test_validate_environment_api_key_within_litellm_params():
assert result == expected
@pytest.mark.serial
def test_validate_environment_api_key_within_litellm():
azure_openai_responses_apiconfig = AzureOpenAIResponsesAPIConfig()
@ -41,6 +46,7 @@ def test_validate_environment_api_key_within_litellm():
assert result == expected
@pytest.mark.serial
def test_validate_environment_azure_key_within_litellm():
azure_openai_responses_apiconfig = AzureOpenAIResponsesAPIConfig()
@ -55,6 +61,7 @@ def test_validate_environment_azure_key_within_litellm():
assert result == expected
@pytest.mark.serial
def test_validate_environment_azure_key_within_headers():
azure_openai_responses_apiconfig = AzureOpenAIResponsesAPIConfig()
@ -93,10 +100,10 @@ def test_azure_o_series_responses_api_supported_params():
"""Test that Azure OpenAI O-series responses API excludes temperature from supported parameters."""
config = AzureOpenAIOSeriesResponsesAPIConfig()
supported_params = config.get_supported_openai_params("o_series/gpt-o1")
# Temperature should not be in supported params for O-series models
assert "temperature" not in supported_params
# Other parameters should still be supported
assert "input" in supported_params
assert "max_output_tokens" in supported_params
@ -108,35 +115,32 @@ def test_azure_o_series_responses_api_supported_params():
def test_azure_o_series_responses_api_drop_temperature_param():
"""Test that temperature parameter is dropped when drop_params is True for O-series models."""
config = AzureOpenAIOSeriesResponsesAPIConfig()
# Create request params with temperature
request_params = ResponsesAPIOptionalRequestParams(
temperature=0.7,
max_output_tokens=1000,
stream=False,
top_p=0.9
temperature=0.7, max_output_tokens=1000, stream=False, top_p=0.9
)
# Test with drop_params=True
mapped_params_with_drop = config.map_openai_params(
response_api_optional_params=request_params,
model="o_series/gpt-o1",
drop_params=True
drop_params=True,
)
# Temperature should be dropped
assert "temperature" not in mapped_params_with_drop
# Other params should remain
assert mapped_params_with_drop["max_output_tokens"] == 1000
assert mapped_params_with_drop["top_p"] == 0.9
# Test with drop_params=False
mapped_params_without_drop = config.map_openai_params(
response_api_optional_params=request_params,
model="o_series/gpt-o1",
drop_params=False
drop_params=False,
)
# Temperature should still be present when drop_params=False
assert mapped_params_without_drop["temperature"] == 0.7
assert mapped_params_without_drop["max_output_tokens"] == 1000
@ -147,21 +151,19 @@ def test_azure_o_series_responses_api_drop_temperature_param():
def test_azure_o_series_responses_api_drop_params_no_temperature():
"""Test that map_openai_params works correctly when temperature is not present for O-series models."""
config = AzureOpenAIOSeriesResponsesAPIConfig()
# Create request params without temperature
request_params = ResponsesAPIOptionalRequestParams(
max_output_tokens=1000,
stream=False,
top_p=0.9
max_output_tokens=1000, stream=False, top_p=0.9
)
# Should work fine even with drop_params=True
mapped_params = config.map_openai_params(
response_api_optional_params=request_params,
model="o_series/gpt-o1",
drop_params=True
drop_params=True,
)
assert "temperature" not in mapped_params
assert mapped_params["max_output_tokens"] == 1000
assert mapped_params["top_p"] == 0.9
@ -172,10 +174,10 @@ def test_azure_regular_responses_api_supports_temperature():
"""Test that regular Azure OpenAI responses API (non-O-series) supports temperature parameter."""
config = AzureOpenAIResponsesAPIConfig()
supported_params = config.get_supported_openai_params("gpt-4o")
# Regular Azure models should support temperature
assert "temperature" in supported_params
# Other parameters should still be supported
assert "input" in supported_params
assert "max_output_tokens" in supported_params
@ -187,11 +189,11 @@ def test_azure_regular_responses_api_supports_temperature():
def test_o_series_model_detection():
"""Test that the O-series configuration correctly identifies O-series models."""
config = AzureOpenAIOSeriesResponsesAPIConfig()
# Test explicit o_series naming
assert config.is_o_series_model("o_series/gpt-o1") == True
assert config.is_o_series_model("azure/o_series/gpt-o3") == True
# Test regular models
assert config.is_o_series_model("gpt-4o") == False
assert config.is_o_series_model("gpt-3.5-turbo") == False
@ -200,28 +202,94 @@ def test_o_series_model_detection():
@pytest.mark.serial
def test_provider_config_manager_o_series_selection():
"""Test that ProviderConfigManager returns the correct config for O-series vs regular models."""
from litellm.utils import ProviderConfigManager
import litellm
from litellm.utils import ProviderConfigManager
# Test O-series model selection
o_series_config = ProviderConfigManager.get_provider_responses_api_config(
provider=litellm.LlmProviders.AZURE,
model="o_series/gpt-o1"
provider=litellm.LlmProviders.AZURE, model="o_series/gpt-o1"
)
assert isinstance(o_series_config, AzureOpenAIOSeriesResponsesAPIConfig)
# Test regular model selection
regular_config = ProviderConfigManager.get_provider_responses_api_config(
provider=litellm.LlmProviders.AZURE,
model="gpt-4o"
provider=litellm.LlmProviders.AZURE, model="gpt-4o"
)
assert isinstance(regular_config, AzureOpenAIResponsesAPIConfig)
assert not isinstance(regular_config, AzureOpenAIOSeriesResponsesAPIConfig)
# Test with no model specified (should default to regular)
default_config = ProviderConfigManager.get_provider_responses_api_config(
provider=litellm.LlmProviders.AZURE,
model=None
provider=litellm.LlmProviders.AZURE, model=None
)
assert isinstance(default_config, AzureOpenAIResponsesAPIConfig)
assert not isinstance(default_config, AzureOpenAIOSeriesResponsesAPIConfig)
class TestAzureResponsesAPIConfig:
def setup_method(self):
self.config = AzureOpenAIResponsesAPIConfig()
self.model = "gpt-4o"
self.logging_obj = MagicMock()
def test_azure_get_complete_url_with_version_types(self):
"""Test Azure get_complete_url with different API version types"""
base_url = "https://litellm8397336933.openai.azure.com"
# Test with preview version - should use openai/v1/responses
result_preview = self.config.get_complete_url(
api_base=base_url,
litellm_params={"api_version": "preview"},
)
assert (
result_preview
== "https://litellm8397336933.openai.azure.com/openai/v1/responses?api-version=preview"
)
# Test with latest version - should use openai/v1/responses
result_latest = self.config.get_complete_url(
api_base=base_url,
litellm_params={"api_version": "latest"},
)
assert (
result_latest
== "https://litellm8397336933.openai.azure.com/openai/v1/responses?api-version=latest"
)
# Test with date-based version - should use openai/responses
result_date = self.config.get_complete_url(
api_base=base_url,
litellm_params={"api_version": "2025-01-01"},
)
assert (
result_date
== "https://litellm8397336933.openai.azure.com/openai/responses?api-version=2025-01-01"
)
def test_azure_get_complete_url_with_default_api_version(self):
"""Test Azure get_complete_url uses default API version when none is provided"""
from litellm.constants import AZURE_DEFAULT_RESPONSES_API_VERSION
base_url = "https://litellm8397336933.openai.azure.com"
# Test with no api_version provided - should use default
result_no_version = self.config.get_complete_url(
api_base=base_url,
litellm_params={},
)
expected_url = f"https://litellm8397336933.openai.azure.com/openai/v1/responses?api-version={AZURE_DEFAULT_RESPONSES_API_VERSION}"
assert result_no_version == expected_url
# Test with empty litellm_params - should use default
result_empty_params = self.config.get_complete_url(
api_base=base_url,
litellm_params={},
)
assert result_empty_params == expected_url
# Test with None api_version - should use default
result_none_version = self.config.get_complete_url(
api_base=base_url,
litellm_params={"api_version": None},
)
assert result_none_version == expected_url

View File

@ -283,75 +283,6 @@ class TestOpenAIResponsesAPIConfig:
assert result.type == "test"
class TestAzureResponsesAPIConfig:
def setup_method(self):
self.config = AzureOpenAIResponsesAPIConfig()
self.model = "gpt-4o"
self.logging_obj = MagicMock()
def test_azure_get_complete_url_with_version_types(self):
"""Test Azure get_complete_url with different API version types"""
base_url = "https://litellm8397336933.openai.azure.com"
# Test with preview version - should use openai/v1/responses
result_preview = self.config.get_complete_url(
api_base=base_url,
litellm_params={"api_version": "preview"},
)
assert (
result_preview
== "https://litellm8397336933.openai.azure.com/openai/v1/responses?api-version=preview"
)
# Test with latest version - should use openai/v1/responses
result_latest = self.config.get_complete_url(
api_base=base_url,
litellm_params={"api_version": "latest"},
)
assert (
result_latest
== "https://litellm8397336933.openai.azure.com/openai/v1/responses?api-version=latest"
)
# Test with date-based version - should use openai/responses
result_date = self.config.get_complete_url(
api_base=base_url,
litellm_params={"api_version": "2025-01-01"},
)
assert (
result_date
== "https://litellm8397336933.openai.azure.com/openai/responses?api-version=2025-01-01"
)
def test_azure_get_complete_url_with_default_api_version(self):
"""Test Azure get_complete_url uses default API version when none is provided"""
from litellm.constants import AZURE_DEFAULT_RESPONSES_API_VERSION
base_url = "https://litellm8397336933.openai.azure.com"
# Test with no api_version provided - should use default
result_no_version = self.config.get_complete_url(
api_base=base_url,
litellm_params={},
)
expected_url = f"https://litellm8397336933.openai.azure.com/openai/responses?api-version={AZURE_DEFAULT_RESPONSES_API_VERSION}"
assert result_no_version == expected_url
# Test with empty litellm_params - should use default
result_empty_params = self.config.get_complete_url(
api_base=base_url,
litellm_params={},
)
assert result_empty_params == expected_url
# Test with None api_version - should use default
result_none_version = self.config.get_complete_url(
api_base=base_url,
litellm_params={"api_version": None},
)
assert result_none_version == expected_url
class TestTransformListInputItemsRequest:
"""Test suite for transform_list_input_items_request function"""