mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
[LLM Translation] Litellm azure o series drop params (#13353)
* added route check * fix ruff * Added support for dropping o_series params * Added ruff fix * fix tests
This commit is contained in:
parent
6184e898b7
commit
10a1fe21c5
6 changed files with 267 additions and 37 deletions
|
|
@ -1134,6 +1134,7 @@ from .llms.azure_ai.chat.transformation import AzureAIStudioConfig
|
|||
from .llms.mistral.chat.transformation import MistralConfig
|
||||
from .llms.openai.responses.transformation import OpenAIResponsesAPIConfig
|
||||
from .llms.azure.responses.transformation import AzureOpenAIResponsesAPIConfig
|
||||
from .llms.azure.responses.o_series_transformation import AzureOpenAIOSeriesResponsesAPIConfig
|
||||
from .llms.openai.chat.o_series_transformation import (
|
||||
OpenAIOSeriesConfig as OpenAIO1Config, # maintain backwards compatibility
|
||||
OpenAIOSeriesConfig,
|
||||
|
|
|
|||
|
|
@ -662,6 +662,11 @@ class BaseAzureLLM(BaseOpenAILLM):
|
|||
headers: dict, litellm_params: Optional[GenericLiteLLMParams]
|
||||
) -> dict:
|
||||
litellm_params = litellm_params or GenericLiteLLMParams()
|
||||
|
||||
# If api-key is already in headers, preserve it
|
||||
if "api-key" in headers:
|
||||
return headers
|
||||
|
||||
api_key = (
|
||||
litellm_params.api_key
|
||||
or litellm.api_key
|
||||
|
|
|
|||
93
litellm/llms/azure/responses/o_series_transformation.py
Normal file
93
litellm/llms/azure/responses/o_series_transformation.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
"""
|
||||
Support for Azure OpenAI O-series models (o1, o3, etc.) in Responses API
|
||||
|
||||
https://platform.openai.com/docs/guides/reasoning
|
||||
|
||||
Translations handled by LiteLLM:
|
||||
- temperature => drop param (if user opts in to dropping param)
|
||||
- Other parameters follow base Azure OpenAI Responses API behavior
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Dict
|
||||
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.types.llms.openai import ResponsesAPIOptionalRequestParams
|
||||
from litellm.utils import supports_reasoning
|
||||
|
||||
from .transformation import AzureOpenAIResponsesAPIConfig
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj
|
||||
|
||||
LiteLLMLoggingObj = _LiteLLMLoggingObj
|
||||
else:
|
||||
LiteLLMLoggingObj = Any
|
||||
|
||||
|
||||
class AzureOpenAIOSeriesResponsesAPIConfig(AzureOpenAIResponsesAPIConfig):
|
||||
"""
|
||||
Configuration for Azure OpenAI O-series models in Responses API.
|
||||
|
||||
O-series models (o1, o3, etc.) do not support the temperature parameter
|
||||
in the responses API, so we need to drop it when drop_params is enabled.
|
||||
"""
|
||||
|
||||
def get_supported_openai_params(self, model: str) -> list:
|
||||
"""
|
||||
Get supported parameters for Azure OpenAI O-series Responses API.
|
||||
|
||||
O-series models don't support temperature parameter in responses API.
|
||||
"""
|
||||
# Get the base Azure supported params
|
||||
base_supported_params = super().get_supported_openai_params(model)
|
||||
|
||||
# O-series models don't support temperature parameter in responses API
|
||||
o_series_unsupported_params = ["temperature"]
|
||||
|
||||
# Filter out unsupported parameters for O-series models
|
||||
o_series_supported_params = [
|
||||
param for param in base_supported_params
|
||||
if param not in o_series_unsupported_params
|
||||
]
|
||||
|
||||
return o_series_supported_params
|
||||
|
||||
def map_openai_params(
|
||||
self,
|
||||
response_api_optional_params: ResponsesAPIOptionalRequestParams,
|
||||
model: str,
|
||||
drop_params: bool,
|
||||
) -> Dict:
|
||||
"""
|
||||
Map OpenAI parameters for Azure OpenAI O-series Responses API.
|
||||
|
||||
Drops temperature parameter if drop_params is True since O-series models
|
||||
don't support temperature in the responses API.
|
||||
"""
|
||||
mapped_params = dict(response_api_optional_params)
|
||||
|
||||
# If drop_params is enabled, remove temperature parameter for O-series models
|
||||
if drop_params and "temperature" in mapped_params:
|
||||
verbose_logger.debug(
|
||||
f"Dropping unsupported parameter 'temperature' for Azure OpenAI O-series responses API model {model}"
|
||||
)
|
||||
mapped_params.pop("temperature", None)
|
||||
|
||||
return mapped_params
|
||||
|
||||
def is_o_series_model(self, model: str) -> bool:
|
||||
"""
|
||||
Check if the model is an O-series model.
|
||||
|
||||
Args:
|
||||
model: The model name to check
|
||||
|
||||
Returns:
|
||||
True if it's an O-series model, False otherwise
|
||||
"""
|
||||
# Check if model name contains o_series or if it's a known O-series model
|
||||
if "o_series" in model.lower():
|
||||
return True
|
||||
|
||||
# Check if the model supports reasoning (which is O-series specific)
|
||||
return supports_reasoning(model)
|
||||
|
|
@ -7074,7 +7074,11 @@ class ProviderConfigManager:
|
|||
if litellm.LlmProviders.OPENAI == provider:
|
||||
return litellm.OpenAIResponsesAPIConfig()
|
||||
elif litellm.LlmProviders.AZURE == provider:
|
||||
return litellm.AzureOpenAIResponsesAPIConfig()
|
||||
# Check if it's an O-series model
|
||||
if model and ("o_series" in model.lower() or supports_reasoning(model)):
|
||||
return litellm.AzureOpenAIOSeriesResponsesAPIConfig()
|
||||
else:
|
||||
return litellm.AzureOpenAIResponsesAPIConfig()
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ sys.path.insert(
|
|||
) # Adds the parent directory to the system path
|
||||
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.serial
|
||||
|
|
@ -54,47 +56,172 @@ def test_validate_environment_azure_key_within_litellm():
|
|||
assert result == expected
|
||||
|
||||
@pytest.mark.serial
|
||||
def test_validate_environment_azure_openai_api_key_within_secret_str():
|
||||
def test_validate_environment_azure_key_within_headers():
|
||||
azure_openai_responses_apiconfig = AzureOpenAIResponsesAPIConfig()
|
||||
headers = {"api-key": "test-api-key-from-headers"}
|
||||
litellm_params = GenericLiteLLMParams()
|
||||
|
||||
with patch("litellm.api_key", None), \
|
||||
patch("litellm.azure_key", None), \
|
||||
patch("litellm.llms.azure.common_utils.get_secret_str") as mock_get_secret_str:
|
||||
# Configure the mock to return "test-api-key" when called with "AZURE_OPENAI_API_KEY"
|
||||
mock_get_secret_str.side_effect = (
|
||||
lambda key: "test-api-key" if key == "AZURE_OPENAI_API_KEY" else None
|
||||
)
|
||||
result = azure_openai_responses_apiconfig.validate_environment(
|
||||
headers=headers, model="", litellm_params=litellm_params
|
||||
)
|
||||
|
||||
litellm_params = GenericLiteLLMParams()
|
||||
result = azure_openai_responses_apiconfig.validate_environment(
|
||||
headers={}, model="", litellm_params=litellm_params
|
||||
)
|
||||
expected = {"api-key": "test-api-key"}
|
||||
expected = {"api-key": "test-api-key-from-headers"}
|
||||
|
||||
assert result == expected
|
||||
|
||||
assert result == expected
|
||||
|
||||
@pytest.mark.serial
|
||||
def test_validate_environment_azure_api_key_within_secret_str():
|
||||
def test_get_complete_url():
|
||||
"""
|
||||
Test the get_complete_url function
|
||||
"""
|
||||
azure_openai_responses_apiconfig = AzureOpenAIResponsesAPIConfig()
|
||||
api_base = "https://litellm8397336933.openai.azure.com"
|
||||
litellm_params = {"api_version": "2024-05-01-preview"}
|
||||
|
||||
with patch("litellm.api_key", None), \
|
||||
patch("litellm.azure_key", None), \
|
||||
patch("litellm.llms.azure.common_utils.get_secret_str") as mock_get_secret_str:
|
||||
# Configure the mock to return None for "AZURE_OPENAI_API_KEY" and "test-api-key" for "AZURE_API_KEY"
|
||||
def mock_side_effect(key):
|
||||
if key == "AZURE_OPENAI_API_KEY":
|
||||
return None
|
||||
elif key == "AZURE_API_KEY":
|
||||
return "test-api-key"
|
||||
else:
|
||||
return None
|
||||
|
||||
mock_get_secret_str.side_effect = mock_side_effect
|
||||
result = azure_openai_responses_apiconfig.get_complete_url(
|
||||
api_base=api_base, litellm_params=litellm_params
|
||||
)
|
||||
|
||||
litellm_params = GenericLiteLLMParams()
|
||||
result = azure_openai_responses_apiconfig.validate_environment(
|
||||
headers={}, model="", litellm_params=litellm_params
|
||||
)
|
||||
expected = {"api-key": "test-api-key"}
|
||||
expected = "https://litellm8397336933.openai.azure.com/openai/responses?api-version=2024-05-01-preview"
|
||||
|
||||
assert result == expected
|
||||
assert result == expected
|
||||
|
||||
|
||||
@pytest.mark.serial
|
||||
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
|
||||
assert "stream" in supported_params
|
||||
assert "top_p" in supported_params
|
||||
|
||||
|
||||
@pytest.mark.serial
|
||||
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
|
||||
)
|
||||
|
||||
# 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
|
||||
)
|
||||
|
||||
# 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
|
||||
)
|
||||
|
||||
# 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
|
||||
assert mapped_params_without_drop["top_p"] == 0.9
|
||||
|
||||
|
||||
@pytest.mark.serial
|
||||
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
|
||||
)
|
||||
|
||||
# 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
|
||||
)
|
||||
|
||||
assert "temperature" not in mapped_params
|
||||
assert mapped_params["max_output_tokens"] == 1000
|
||||
assert mapped_params["top_p"] == 0.9
|
||||
|
||||
|
||||
@pytest.mark.serial
|
||||
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
|
||||
assert "stream" in supported_params
|
||||
assert "top_p" in supported_params
|
||||
|
||||
|
||||
@pytest.mark.serial
|
||||
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
|
||||
|
||||
|
||||
@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
|
||||
|
||||
# Test O-series model selection
|
||||
o_series_config = ProviderConfigManager.get_provider_responses_api_config(
|
||||
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"
|
||||
)
|
||||
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
|
||||
)
|
||||
assert isinstance(default_config, AzureOpenAIResponsesAPIConfig)
|
||||
assert not isinstance(default_config, AzureOpenAIOSeriesResponsesAPIConfig)
|
||||
|
|
|
|||
|
|
@ -1117,9 +1117,9 @@ async def test_chat_completion_result_no_nested_none_values():
|
|||
)
|
||||
|
||||
mock_model_response.choices = [mock_choice]
|
||||
mock_model_response.usage = litellm.Usage(
|
||||
setattr(mock_model_response, "usage", litellm.Usage(
|
||||
prompt_tokens=10, completion_tokens=5, total_tokens=15
|
||||
)
|
||||
))
|
||||
|
||||
# Verify the mock has None values before serialization
|
||||
raw_dict = mock_model_response.model_dump()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue