mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
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:
parent
e1fd49ce91
commit
79e262d12b
5 changed files with 129 additions and 108 deletions
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue