fix(azure): use supported API versions for Responses requests (#51116)

This commit is contained in:
kenneth-berri 2026-11-11 10:53:54 -08:00 • committed by GitHub
parent 8fb9f2f090
commit 13089b191b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 72 additions and 12 deletions

View file

@ -37,6 +37,16 @@ _AZURE_RESPONSES_PROMPT_CACHE_BREAKPOINT: Final = TypeAdapter(AzureResponsesProm
_DATED_GA_API_VERSION: Final = re.compile(r"\d{4}-\d{2}-\d{2}")
_DATED_API_VERSION: Final = re.compile(r"\d{4}-\d{2}-\d{2}(?:-preview)?")
_MIN_RESPONSES_API_DATE: Final = "2025-03-01"
def _predates_responses_api(api_version: str | None) -> bool:
return (
api_version is not None
and _DATED_API_VERSION.fullmatch(api_version) is not None
and api_version[:10] < _MIN_RESPONSES_API_DATE
)
def _str_param(litellm_params: Mapping[str, object], key: str) -> str | None:
@ -45,16 +55,14 @@ def _str_param(litellm_params: Mapping[str, object], key: str) -> str | None:
def _resolve_responses_api_version(api_base: str | None, litellm_params: Mapping[str, object]) -> str | None:
"""
Dated GA api_versions (e.g. 2024-10-21) predate the Responses API on Azure and 404 there, so a deployment
pinned to one for chat gets the default Responses version unless it sets responses_api_version.
"""
api_version: Final = _str_param(litellm_params, "api_version")
if api_base is not None and "api-version" in httpx.URL(api_base).params:
return api_version
responses_api_version: Final = _str_param(litellm_params, "responses_api_version")
if responses_api_version is not None:
return responses_api_version
api_version: Final = _str_param(litellm_params, "api_version")
if api_base is not None and "api-version" in httpx.URL(api_base).params:
return api_version
if _predates_responses_api(api_version):
return None
if api_version is not None and _DATED_GA_API_VERSION.fullmatch(api_version):
return AZURE_DEFAULT_RESPONSES_API_VERSION
return api_version
@ -188,7 +196,7 @@ class AzureOpenAIResponsesAPIConfig(OpenAIResponsesAPIConfig):
def get_complete_url(
self,
api_base: str | None,
litellm_params: dict,
litellm_params: dict[str, object],
) -> str:
"""
Constructs a complete URL for the API request.
@ -206,7 +214,17 @@ class AzureOpenAIResponsesAPIConfig(OpenAIResponsesAPIConfig):
- A complete URL string, e.g.,
"https://litellm8397336933.openai.azure.com/openai/responses?api-version=2024-05-01-preview"
"""
resolved_api_base: Final = BaseAzureLLM.resolve_azure_api_base(api_base)
base: Final = BaseAzureLLM.resolve_azure_api_base(api_base)
parsed_url: Final = httpx.URL(base) if base is not None else None
resolved_api_base: Final = (
str(parsed_url.copy_with(params=parsed_url.params.remove("api-version")))
if parsed_url is not None
and (
_predates_responses_api(parsed_url.params.get("api-version"))
or _str_param(litellm_params, "responses_api_version") is not None
)
else base
)
responses_params: Final = _with_responses_api_version(resolved_api_base, litellm_params)
parsed_base: Final = urlsplit(resolved_api_base or "")
path: Final = parsed_base.path.rstrip("/")

View file

@ -93,13 +93,13 @@ def test_get_complete_url():
"""
azure_openai_responses_apiconfig = AzureOpenAIResponsesAPIConfig()
api_base = "https://litellm8397336933.openai.azure.com"
litellm_params = {"api_version": "2024-05-01-preview"}
litellm_params = {"api_version": "2025-04-01-preview"}
result = azure_openai_responses_apiconfig.get_complete_url(
api_base=api_base, litellm_params=litellm_params
)
expected = "https://litellm8397336933.openai.azure.com/openai/responses?api-version=2024-05-01-preview"
expected = "https://litellm8397336933.openai.azure.com/openai/responses?api-version=2025-04-01-preview"
assert result == expected
@ -467,7 +467,12 @@ class TestAzureResponsesAPIConfig:
litellm_params={"api_version": "2024-10-21", "responses_api_version": responses_api_version},
)
assert url == api_base
expected: Final = (
"https://litellm8397336933.openai.azure.com/openai/v1/responses?api-version=v1"
if responses_api_version is not None
else api_base
)
assert url == expected
def test_api_version_in_env_api_base_query_wins_over_a_dated_ga_api_version(self, monkeypatch: pytest.MonkeyPatch):
env_api_base: Final = (
@ -1015,6 +1020,43 @@ def _unset_api_version_responses_url() -> httpx.URL:
return httpx.URL(AzureOpenAIResponsesAPIConfig().get_complete_url(api_base=_AZURE_RESOURCE, litellm_params={}))
@pytest.mark.parametrize("api_version", ["2025-02-01-preview", "2025-02-28", "2024-05-01-preview"])
@pytest.mark.parametrize("source", ["params", "query", "both"])
def test_pre_responses_api_version_uses_the_native_responses_default(api_version: str, source: str) -> None:
base: Final = (
f"{_AZURE_RESOURCE}?api-version={api_version}&tenant=test"
if source in ("query", "both")
else f"{_AZURE_RESOURCE}?tenant=test"
)
params: Final = {"api_version": api_version} if source in ("params", "both") else {}
url: Final = httpx.URL(AzureOpenAIResponsesAPIConfig().get_complete_url(api_base=base, litellm_params=params))
expected: Final = _unset_api_version_responses_url()
assert url.copy_with(query=None) == expected.copy_with(query=None)
assert dict(url.params) == {**dict(expected.params), "tenant": "test"}
@pytest.mark.parametrize("api_version", ["2025-03-01-preview", "2025-04-01-preview", "preview", "latest", "v1"])
@pytest.mark.parametrize("source", ["params", "query"])
def test_supported_responses_api_versions_are_preserved(api_version: str, source: str) -> None:
base: Final = f"{_AZURE_RESOURCE}?api-version={api_version}" if source == "query" else _AZURE_RESOURCE
params: Final = {"api_version": api_version} if source == "params" else {}
url: Final = httpx.URL(AzureOpenAIResponsesAPIConfig().get_complete_url(api_base=base, litellm_params=params))
assert url.params["api-version"] == api_version
@pytest.mark.parametrize("api_version", ["2025-02-01-preview", "2025-04-01-preview"])
def test_responses_override_wins_over_query_and_chat_version(api_version: str) -> None:
config: Final = AzureOpenAIResponsesAPIConfig()
params: Final = {"api_version": api_version, "responses_api_version": "2025-03-01-preview"}
url: Final = config.get_complete_url(
api_base=f"{_AZURE_RESOURCE}?api-version={api_version}", litellm_params=params
)
assert url == f"{_AZURE_RESOURCE}/openai/responses?api-version=2025-03-01-preview"
def _azure_responses_create(api_version: str) -> str | None:
response: Final = litellm.responses(
model="azure/gpt-5.4-nano",