From d5a658b6125d5d877aaf04c9b6db72eae4255fcb Mon Sep 17 00:00:00 2001 From: ScarabSystems Date: Mon, 27 Jul 2026 22:32:46 -0400 Subject: [PATCH 1/3] Promote Azure served model header for Responses Promote non-empty x-ms-served-model values into Azure Responses model fields before response construction. This keeps the behavior scoped to Azure clients and covers both parsed Responses objects and streamed response events without touching generated resource files. Add sync and async Azure regression coverage for non-streaming and streaming Responses calls. --- src/openai/lib/azure.py | 48 ++++++++++++++- tests/lib/test_azure.py | 131 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 177 insertions(+), 2 deletions(-) diff --git a/src/openai/lib/azure.py b/src/openai/lib/azure.py index 4ebe0a98aa..6033ebbab1 100644 --- a/src/openai/lib/azure.py +++ b/src/openai/lib/azure.py @@ -8,7 +8,7 @@ import httpx from ..auth import WorkloadIdentity -from .._types import NOT_GIVEN, Omit, Query, Headers, Timeout, NotGiven +from .._types import NOT_GIVEN, Omit, Query, Headers, Timeout, NotGiven, ResponseT from .._utils import is_given, is_mapping from .._client import OpenAI, AsyncOpenAI from .._compat import model_copy @@ -43,6 +43,7 @@ # as we don't want to make the `api_key` in the main client Optional # and Azure AD tokens may be retrieved on a per-request basis API_KEY_SENTINEL = "".join(["<", "missing API key", ">"]) +_AZURE_RESPONSES_SERVED_MODEL_HEADER = "x-ms-served-model" def _has_header(headers: Headers, header: str) -> bool: @@ -54,6 +55,37 @@ def _has_auth_header(headers: Headers) -> bool: return _has_header(headers, "Authorization") or _has_header(headers, "api-key") +def _is_responses_request(response: httpx.Response) -> bool: + path = response.request.url.path.rstrip("/") + return path.endswith("/responses") or "/responses/" in path + + +def _served_model_from_response(response: httpx.Response) -> str | None: + if not _is_responses_request(response): + return None + + served_model = response.headers.get(_AZURE_RESPONSES_SERVED_MODEL_HEADER) + if served_model is None: + return None + + served_model = served_model.strip() + return served_model or None + + +def _replace_response_model(data: object, served_model: str | None) -> object: + if served_model is None or not is_mapping(data): + return data + + nested_response = data.get("response") + if is_mapping(nested_response): + return {**data, "response": {**nested_response, "model": served_model}} + + if "model" in data: + return {**data, "model": served_model} + + return data + + class MutuallyExclusiveAuthError(OpenAIError): def __init__(self) -> None: super().__init__( @@ -65,6 +97,20 @@ class BaseAzureClient(BaseClient[_HttpxClientT, _DefaultStreamT]): _azure_endpoint: httpx.URL | None _azure_deployment: str | None + @override + def _process_response_data( + self, + *, + data: object, + cast_to: type[ResponseT], + response: httpx.Response, + ) -> ResponseT: + return super()._process_response_data( + data=_replace_response_model(data, _served_model_from_response(response)), + cast_to=cast_to, + response=response, + ) + @override def _build_request( self, diff --git a/tests/lib/test_azure.py b/tests/lib/test_azure.py index 3e1d783e2c..b1fe7ab766 100644 --- a/tests/lib/test_azure.py +++ b/tests/lib/test_azure.py @@ -1,7 +1,8 @@ from __future__ import annotations +import json import logging -from typing import Union, cast +from typing import Union, Iterator, AsyncIterator, cast from typing_extensions import Literal, Protocol import httpx @@ -30,11 +31,139 @@ azure_endpoint="https://example-resource.azure.openai.com", ) +AZURE_RESPONSES_URL = "https://example-resource.azure.openai.com/openai/responses?api-version=2024-02-01" +AZURE_DEPLOYMENT_MODEL = "gpt-5-nano" +AZURE_SERVED_MODEL = "gpt-5-nano-2025-08-07" + + +def _azure_response_payload(*, model: str = AZURE_DEPLOYMENT_MODEL) -> dict[str, object]: + return { + "id": "resp_123", + "object": "response", + "created_at": 0, + "model": model, + "output": [], + "parallel_tool_calls": True, + "tool_choice": "auto", + "tools": [], + } + + +def _azure_response_stream_body() -> Iterator[bytes]: + yield b"event: response.created\n" + yield ( + b'data: {"type":"response.created","sequence_number":0,"response":' + + json.dumps(_azure_response_payload(), separators=(",", ":")).encode() + + b"}\n\n" + ) + yield b"data: [DONE]\n\n" + + +async def _async_azure_response_stream_body() -> AsyncIterator[bytes]: + for chunk in _azure_response_stream_body(): + yield chunk + class MockRequestCall(Protocol): request: httpx.Request +@pytest.mark.respx() +def test_azure_responses_uses_served_model_header(respx_mock: MockRouter) -> None: + respx_mock.post(AZURE_RESPONSES_URL).mock( + return_value=httpx.Response( + 200, + headers={"x-ms-served-model": f" {AZURE_SERVED_MODEL} "}, + json=_azure_response_payload(), + ) + ) + + client = AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + ) + + response = client.responses.create(model=AZURE_DEPLOYMENT_MODEL, input="ping") + + assert response.model == AZURE_SERVED_MODEL + + +@pytest.mark.asyncio +@pytest.mark.respx() +async def test_async_azure_responses_uses_served_model_header(respx_mock: MockRouter) -> None: + respx_mock.post(AZURE_RESPONSES_URL).mock( + return_value=httpx.Response( + 200, + headers={"x-ms-served-model": AZURE_SERVED_MODEL}, + json=_azure_response_payload(), + ) + ) + + client = AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + ) + + response = await client.responses.create(model=AZURE_DEPLOYMENT_MODEL, input="ping") + + assert response.model == AZURE_SERVED_MODEL + + +@pytest.mark.respx() +def test_azure_responses_stream_uses_served_model_header(respx_mock: MockRouter) -> None: + respx_mock.post(AZURE_RESPONSES_URL).mock( + return_value=httpx.Response( + 200, + headers={ + "content-type": "text/event-stream", + "x-ms-served-model": AZURE_SERVED_MODEL, + }, + content=_azure_response_stream_body(), + ) + ) + + client = AzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + ) + + stream = client.responses.create(model=AZURE_DEPLOYMENT_MODEL, input="ping", stream=True) + event = next(stream) + + assert event.type == "response.created" + assert event.response.model == AZURE_SERVED_MODEL + + +@pytest.mark.asyncio +@pytest.mark.respx() +async def test_async_azure_responses_stream_uses_served_model_header(respx_mock: MockRouter) -> None: + respx_mock.post(AZURE_RESPONSES_URL).mock( + return_value=httpx.Response( + 200, + headers={ + "content-type": "text/event-stream", + "x-ms-served-model": AZURE_SERVED_MODEL, + }, + content=_async_azure_response_stream_body(), + ) + ) + + client = AsyncAzureOpenAI( + api_version="2024-02-01", + api_key="example API key", + azure_endpoint="https://example-resource.azure.openai.com", + ) + + stream = await client.responses.create(model=AZURE_DEPLOYMENT_MODEL, input="ping", stream=True) + event = await stream.__anext__() + + assert event.type == "response.created" + assert event.response.model == AZURE_SERVED_MODEL + + @pytest.mark.parametrize("client", [sync_client, async_client]) def test_implicit_deployment_path(client: Client) -> None: req = client._build_request( From 642587044f879042a697ec63cb5da0df50f5b55c Mon Sep 17 00:00:00 2001 From: ScarabSystems Date: Mon, 3 Aug 2026 19:55:16 -0400 Subject: [PATCH 2/3] Split Azure Responses tests Move Azure Responses served-model header coverage out of the catch-all Azure test module into a focused tests/lib/test_azure_responses.py module. Keep tests/lib/test_azure.py below the 1k-line decomposition boundary and add focused guard coverage for absent or blank served-model headers and non-Responses resources. --- tests/lib/test_azure.py | 131 +---------------------- tests/lib/test_azure_responses.py | 168 ++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 130 deletions(-) create mode 100644 tests/lib/test_azure_responses.py diff --git a/tests/lib/test_azure.py b/tests/lib/test_azure.py index b1fe7ab766..3e1d783e2c 100644 --- a/tests/lib/test_azure.py +++ b/tests/lib/test_azure.py @@ -1,8 +1,7 @@ from __future__ import annotations -import json import logging -from typing import Union, Iterator, AsyncIterator, cast +from typing import Union, cast from typing_extensions import Literal, Protocol import httpx @@ -31,139 +30,11 @@ azure_endpoint="https://example-resource.azure.openai.com", ) -AZURE_RESPONSES_URL = "https://example-resource.azure.openai.com/openai/responses?api-version=2024-02-01" -AZURE_DEPLOYMENT_MODEL = "gpt-5-nano" -AZURE_SERVED_MODEL = "gpt-5-nano-2025-08-07" - - -def _azure_response_payload(*, model: str = AZURE_DEPLOYMENT_MODEL) -> dict[str, object]: - return { - "id": "resp_123", - "object": "response", - "created_at": 0, - "model": model, - "output": [], - "parallel_tool_calls": True, - "tool_choice": "auto", - "tools": [], - } - - -def _azure_response_stream_body() -> Iterator[bytes]: - yield b"event: response.created\n" - yield ( - b'data: {"type":"response.created","sequence_number":0,"response":' - + json.dumps(_azure_response_payload(), separators=(",", ":")).encode() - + b"}\n\n" - ) - yield b"data: [DONE]\n\n" - - -async def _async_azure_response_stream_body() -> AsyncIterator[bytes]: - for chunk in _azure_response_stream_body(): - yield chunk - class MockRequestCall(Protocol): request: httpx.Request -@pytest.mark.respx() -def test_azure_responses_uses_served_model_header(respx_mock: MockRouter) -> None: - respx_mock.post(AZURE_RESPONSES_URL).mock( - return_value=httpx.Response( - 200, - headers={"x-ms-served-model": f" {AZURE_SERVED_MODEL} "}, - json=_azure_response_payload(), - ) - ) - - client = AzureOpenAI( - api_version="2024-02-01", - api_key="example API key", - azure_endpoint="https://example-resource.azure.openai.com", - ) - - response = client.responses.create(model=AZURE_DEPLOYMENT_MODEL, input="ping") - - assert response.model == AZURE_SERVED_MODEL - - -@pytest.mark.asyncio -@pytest.mark.respx() -async def test_async_azure_responses_uses_served_model_header(respx_mock: MockRouter) -> None: - respx_mock.post(AZURE_RESPONSES_URL).mock( - return_value=httpx.Response( - 200, - headers={"x-ms-served-model": AZURE_SERVED_MODEL}, - json=_azure_response_payload(), - ) - ) - - client = AsyncAzureOpenAI( - api_version="2024-02-01", - api_key="example API key", - azure_endpoint="https://example-resource.azure.openai.com", - ) - - response = await client.responses.create(model=AZURE_DEPLOYMENT_MODEL, input="ping") - - assert response.model == AZURE_SERVED_MODEL - - -@pytest.mark.respx() -def test_azure_responses_stream_uses_served_model_header(respx_mock: MockRouter) -> None: - respx_mock.post(AZURE_RESPONSES_URL).mock( - return_value=httpx.Response( - 200, - headers={ - "content-type": "text/event-stream", - "x-ms-served-model": AZURE_SERVED_MODEL, - }, - content=_azure_response_stream_body(), - ) - ) - - client = AzureOpenAI( - api_version="2024-02-01", - api_key="example API key", - azure_endpoint="https://example-resource.azure.openai.com", - ) - - stream = client.responses.create(model=AZURE_DEPLOYMENT_MODEL, input="ping", stream=True) - event = next(stream) - - assert event.type == "response.created" - assert event.response.model == AZURE_SERVED_MODEL - - -@pytest.mark.asyncio -@pytest.mark.respx() -async def test_async_azure_responses_stream_uses_served_model_header(respx_mock: MockRouter) -> None: - respx_mock.post(AZURE_RESPONSES_URL).mock( - return_value=httpx.Response( - 200, - headers={ - "content-type": "text/event-stream", - "x-ms-served-model": AZURE_SERVED_MODEL, - }, - content=_async_azure_response_stream_body(), - ) - ) - - client = AsyncAzureOpenAI( - api_version="2024-02-01", - api_key="example API key", - azure_endpoint="https://example-resource.azure.openai.com", - ) - - stream = await client.responses.create(model=AZURE_DEPLOYMENT_MODEL, input="ping", stream=True) - event = await stream.__anext__() - - assert event.type == "response.created" - assert event.response.model == AZURE_SERVED_MODEL - - @pytest.mark.parametrize("client", [sync_client, async_client]) def test_implicit_deployment_path(client: Client) -> None: req = client._build_request( diff --git a/tests/lib/test_azure_responses.py b/tests/lib/test_azure_responses.py new file mode 100644 index 0000000000..e1ef7ce1e4 --- /dev/null +++ b/tests/lib/test_azure_responses.py @@ -0,0 +1,168 @@ +from __future__ import annotations + +import json +from typing import Iterator, AsyncIterator + +import httpx +import pytest +from respx import MockRouter + +from openai.lib.azure import AzureOpenAI, AsyncAzureOpenAI + +AZURE_ENDPOINT = "https://example-resource.azure.openai.com" +AZURE_API_VERSION = "2024-02-01" +AZURE_RESPONSES_URL = f"{AZURE_ENDPOINT}/openai/responses?api-version={AZURE_API_VERSION}" +AZURE_CHAT_COMPLETIONS_URL = ( + f"{AZURE_ENDPOINT}/openai/deployments/gpt-4/chat/completions?api-version={AZURE_API_VERSION}" +) +AZURE_DEPLOYMENT_MODEL = "gpt-5-nano" +AZURE_SERVED_MODEL = "gpt-5-nano-2025-08-07" + + +def make_sync_client() -> AzureOpenAI: + return AzureOpenAI( + api_version=AZURE_API_VERSION, + api_key="example API key", + azure_endpoint=AZURE_ENDPOINT, + ) + + +def make_async_client() -> AsyncAzureOpenAI: + return AsyncAzureOpenAI( + api_version=AZURE_API_VERSION, + api_key="example API key", + azure_endpoint=AZURE_ENDPOINT, + ) + + +def azure_response_payload(*, model: str = AZURE_DEPLOYMENT_MODEL) -> dict[str, object]: + return { + "id": "resp_123", + "object": "response", + "created_at": 0, + "model": model, + "output": [], + "parallel_tool_calls": True, + "tool_choice": "auto", + "tools": [], + } + + +def response_created_stream_body() -> Iterator[bytes]: + yield b"event: response.created\n" + yield ( + b'data: {"type":"response.created","sequence_number":0,"response":' + + json.dumps(azure_response_payload(), separators=(",", ":")).encode() + + b"}\n\n" + ) + yield b"data: [DONE]\n\n" + + +async def async_response_created_stream_body() -> AsyncIterator[bytes]: + for chunk in response_created_stream_body(): + yield chunk + + +def mock_responses_create( + respx_mock: MockRouter, + *, + served_model_header: str | None, + stream: bool = False, +) -> None: + headers = {"x-ms-served-model": served_model_header} if served_model_header is not None else {} + if stream: + headers["content-type"] = "text/event-stream" + respx_mock.post(AZURE_RESPONSES_URL).mock( + return_value=httpx.Response( + 200, + headers=headers, + content=response_created_stream_body(), + ) + ) + else: + respx_mock.post(AZURE_RESPONSES_URL).mock( + return_value=httpx.Response( + 200, + headers=headers, + json=azure_response_payload(), + ) + ) + + +@pytest.mark.respx() +def test_azure_responses_uses_served_model_header(respx_mock: MockRouter) -> None: + mock_responses_create(respx_mock, served_model_header=f" {AZURE_SERVED_MODEL} ") + + response = make_sync_client().responses.create(model=AZURE_DEPLOYMENT_MODEL, input="ping") + + assert response.model == AZURE_SERVED_MODEL + + +@pytest.mark.asyncio +@pytest.mark.respx() +async def test_async_azure_responses_uses_served_model_header(respx_mock: MockRouter) -> None: + mock_responses_create(respx_mock, served_model_header=AZURE_SERVED_MODEL) + + response = await make_async_client().responses.create(model=AZURE_DEPLOYMENT_MODEL, input="ping") + + assert response.model == AZURE_SERVED_MODEL + + +@pytest.mark.respx() +def test_azure_responses_stream_uses_served_model_header(respx_mock: MockRouter) -> None: + mock_responses_create(respx_mock, served_model_header=AZURE_SERVED_MODEL, stream=True) + + stream = make_sync_client().responses.create(model=AZURE_DEPLOYMENT_MODEL, input="ping", stream=True) + event = next(stream) + + assert event.type == "response.created" + assert event.response.model == AZURE_SERVED_MODEL + + +@pytest.mark.asyncio +@pytest.mark.respx() +async def test_async_azure_responses_stream_uses_served_model_header(respx_mock: MockRouter) -> None: + respx_mock.post(AZURE_RESPONSES_URL).mock( + return_value=httpx.Response( + 200, + headers={ + "content-type": "text/event-stream", + "x-ms-served-model": AZURE_SERVED_MODEL, + }, + content=async_response_created_stream_body(), + ) + ) + + stream = await make_async_client().responses.create(model=AZURE_DEPLOYMENT_MODEL, input="ping", stream=True) + event = await stream.__anext__() + + assert event.type == "response.created" + assert event.response.model == AZURE_SERVED_MODEL + + +@pytest.mark.parametrize("served_model_header", [None, " "]) +@pytest.mark.respx() +def test_azure_responses_preserves_body_model_without_served_model_header( + respx_mock: MockRouter, + served_model_header: str | None, +) -> None: + mock_responses_create(respx_mock, served_model_header=served_model_header) + + response = make_sync_client().responses.create(model=AZURE_DEPLOYMENT_MODEL, input="ping") + + assert response.model == AZURE_DEPLOYMENT_MODEL + + +@pytest.mark.respx() +def test_azure_served_model_header_does_not_apply_to_non_responses_resources(respx_mock: MockRouter) -> None: + respx_mock.post(AZURE_CHAT_COMPLETIONS_URL).mock( + return_value=httpx.Response( + 200, + headers={"x-ms-served-model": AZURE_SERVED_MODEL}, + json={"model": "gpt-4"}, + ) + ) + + response = make_sync_client().chat.completions.create(messages=[], model="gpt-4") + + assert response.model == "gpt-4" From 126ca7a9fcab3e2caf9d1fcfd1c86412cf894023 Mon Sep 17 00:00:00 2001 From: Scarab Diagnostics <286812545+scarab-systems@users.noreply.github.com> Date: Thu, 5 Nov 2026 02:10:41 -0500 Subject: [PATCH 3/3] Exclude deployment names from Azure Responses detection Inspect the encoded resource path after its deployment segment so a deployment named responses cannot change Chat Completions model values. Preserve genuine Responses routes, including deployment-scoped and proxy-prefixed base URLs. Add synchronous and asynchronous JSON, streaming, and retrieval regressions. Validation: - 12 new Chat Completions cases failed before the fix; all 25 focused tests pass. - Full SDK suite: 21,457 passed and 205 skipped with Pydantic v2; 21,441 passed and 221 skipped with Pydantic v1. - Ruff lint and formatting, full type checking, import smoke test, and diff checks pass. --- src/openai/lib/azure.py | 8 ++- tests/lib/test_azure_responses.py | 108 +++++++++++++++++++++++++++++- 2 files changed, 112 insertions(+), 4 deletions(-) diff --git a/src/openai/lib/azure.py b/src/openai/lib/azure.py index df3d4aba55..4f56011fb9 100644 --- a/src/openai/lib/azure.py +++ b/src/openai/lib/azure.py @@ -82,8 +82,12 @@ async def _async_strip_azure_api_key_on_redirect(request: httpx2.Request) -> Non def _is_responses_request(response: httpx2.Response) -> bool: - path = response.request.url.path.rstrip("/") - return path.endswith("/responses") or "/responses/" in path + # Keep encoded slashes within deployment names rather than treating them as route separators. + path = response.request.url.raw_path.split(b"?", 1)[0].rstrip(b"/") + if b"/deployments/" in path: + path = b"/" + path.split(b"/deployments/", 1)[1].partition(b"/")[2] + + return path.endswith(b"/responses") or b"/responses/" in path def _served_model_from_response(response: httpx2.Response) -> str | None: diff --git a/tests/lib/test_azure_responses.py b/tests/lib/test_azure_responses.py index d1ceed6fac..0d42c640a8 100644 --- a/tests/lib/test_azure_responses.py +++ b/tests/lib/test_azure_responses.py @@ -19,19 +19,21 @@ AZURE_SERVED_MODEL = "gpt-5-nano-2025-08-07" -def make_sync_client() -> AzureOpenAI: +def make_sync_client(*, azure_deployment: str | None = None) -> AzureOpenAI: return AzureOpenAI( api_version=AZURE_API_VERSION, api_key="example API key", azure_endpoint=AZURE_ENDPOINT, + azure_deployment=azure_deployment, ) -def make_async_client() -> AsyncAzureOpenAI: +def make_async_client(*, azure_deployment: str | None = None) -> AsyncAzureOpenAI: return AsyncAzureOpenAI( api_version=AZURE_API_VERSION, api_key="example API key", azure_endpoint=AZURE_ENDPOINT, + azure_deployment=azure_deployment, ) @@ -166,3 +168,105 @@ def test_azure_served_model_header_does_not_apply_to_non_responses_resources(res response = make_sync_client().chat.completions.create(messages=[], model="gpt-4") assert response.model == "gpt-4" + + +def mock_chat_completions(respx2_mock: MockRouter, *, deployment_path: str, stream: bool) -> None: + payload = { + "id": "chatcmpl_example", + "object": "chat.completion.chunk" if stream else "chat.completion", + "created": 0, + "model": "gpt-4-body", + "choices": [], + } + headers = {"x-ms-served-model": AZURE_SERVED_MODEL} + if stream: + headers["content-type"] = "text/event-stream" + response = httpx2.Response( + 200, headers=headers, content=b"data: " + json.dumps(payload).encode() + b"\n\ndata: [DONE]\n\n" + ) + else: + response = httpx2.Response(200, headers=headers, json=payload) + respx2_mock.post( + f"{AZURE_ENDPOINT}/openai/deployments/{deployment_path}/chat/completions?api-version={AZURE_API_VERSION}" + ).mock(return_value=response) + + +@pytest.mark.parametrize( + "model,azure_deployment,deployment_path", + [ + ("responses", None, "responses"), + ("responses", "responses", "responses"), + ("prefix/responses", None, "prefix%2Fresponses"), + ], +) +@pytest.mark.parametrize("stream", [False, True]) +@pytest.mark.respx2() +def test_chat_model_is_preserved_when_deployment_contains_responses( + respx2_mock: MockRouter, model: str, azure_deployment: str | None, deployment_path: str, stream: bool +) -> None: + mock_chat_completions(respx2_mock, deployment_path=deployment_path, stream=stream) + + with make_sync_client(azure_deployment=azure_deployment) as client: + if stream: + with client.chat.completions.create(messages=[], model=model, stream=True) as chunks: + assert next(chunks).model == "gpt-4-body" + else: + response = client.chat.completions.create(messages=[], model=model) + assert response.model == "gpt-4-body" + + +@pytest.mark.parametrize( + "model,azure_deployment,deployment_path", + [ + ("responses", None, "responses"), + ("responses", "responses", "responses"), + ("prefix/responses", None, "prefix%2Fresponses"), + ], +) +@pytest.mark.parametrize("stream", [False, True]) +@pytest.mark.asyncio +@pytest.mark.respx2() +async def test_async_chat_model_is_preserved_when_deployment_contains_responses( + respx2_mock: MockRouter, model: str, azure_deployment: str | None, deployment_path: str, stream: bool +) -> None: + mock_chat_completions(respx2_mock, deployment_path=deployment_path, stream=stream) + + async with make_async_client(azure_deployment=azure_deployment) as client: + if stream: + async with await client.chat.completions.create(messages=[], model=model, stream=True) as chunks: + assert (await chunks.__anext__()).model == "gpt-4-body" + else: + response = await client.chat.completions.create(messages=[], model=model) + assert response.model == "gpt-4-body" + + +@pytest.mark.parametrize("base_path", ["/openai", "/openai/deployments/responses", "/gateway/deployments/blue/openai"]) +@pytest.mark.respx2() +def test_azure_responses_retrieve_uses_served_model_header(respx2_mock: MockRouter, base_path: str) -> None: + respx2_mock.get(f"{AZURE_ENDPOINT}{base_path}/responses/resp_123?api-version={AZURE_API_VERSION}").mock( + return_value=httpx2.Response( + 200, headers={"x-ms-served-model": AZURE_SERVED_MODEL}, json=azure_response_payload() + ) + ) + + with AzureOpenAI( + api_version=AZURE_API_VERSION, api_key="example API key", base_url=f"{AZURE_ENDPOINT}{base_path}" + ) as client: + assert client.responses.retrieve("resp_123").model == AZURE_SERVED_MODEL + + +@pytest.mark.parametrize("base_path", ["/openai", "/openai/deployments/responses", "/gateway/deployments/blue/openai"]) +@pytest.mark.asyncio +@pytest.mark.respx2() +async def test_async_azure_responses_retrieve_uses_served_model_header(respx2_mock: MockRouter, base_path: str) -> None: + respx2_mock.get(f"{AZURE_ENDPOINT}{base_path}/responses/resp_123?api-version={AZURE_API_VERSION}").mock( + return_value=httpx2.Response( + 200, headers={"x-ms-served-model": AZURE_SERVED_MODEL}, json=azure_response_payload() + ) + ) + + async with AsyncAzureOpenAI( + api_version=AZURE_API_VERSION, api_key="example API key", base_url=f"{AZURE_ENDPOINT}{base_path}" + ) as client: + response = await client.responses.retrieve("resp_123") + assert response.model == AZURE_SERVED_MODEL