Skip to content

fix(azure): strip model from request body for deployment-based endpoints#2910

Open
giulio-leone wants to merge 3 commits intoopenai:mainfrom
giulio-leone:fix/azure-strip-model-from-deployment-body
Open

fix(azure): strip model from request body for deployment-based endpoints#2910
giulio-leone wants to merge 3 commits intoopenai:mainfrom
giulio-leone:fix/azure-strip-model-from-deployment-body

Conversation

@giulio-leone
Copy link

Summary

When using implicit deployments (model name as deployment), the Azure client correctly rewrites the URL to include /deployments/{model}/, but leaves the model parameter in the request body. Some Azure configurations reject requests where the body model doesn't match the actual model name behind the deployment.

Changes

  • In _build_request, after extracting model from json_data for URL routing, create a new dict without model instead of leaving it in the body
  • Uses dict comprehension rather than pop() to avoid mutating the shared json_data reference (model_copy is shallow), which preserves correct retry behavior

Test

Added test_implicit_deployment_strips_model_from_body with two parametrized cases:

  • /chat/completions (deployment endpoint)
  • /images/generations (deployment endpoint)

Both verify the URL contains /deployments/{model}/ and the request body does NOT contain model.

All 53 existing Azure tests pass.

Fixes #2892

When using implicit deployments (model name as deployment), the Azure
client correctly rewrites the URL to include /deployments/{model}/,
but leaves the model parameter in the request body. Some Azure
configurations reject requests where the body model doesn't match
the actual model name behind the deployment.

Create a new dict without model instead of using pop() to avoid
mutating the shared json_data reference (model_copy is shallow),
which would break request retries.

Fixes openai#2892

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 1, 2026 04:35
@giulio-leone giulio-leone requested a review from a team as a code owner March 1, 2026 04:35
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes Azure deployment-routing requests by removing the model field from the JSON body after it’s used to rewrite the URL to /deployments/{model}/..., preventing Azure configurations from rejecting requests due to body/URL model mismatches.

Changes:

  • Update Azure client _build_request to rewrite the URL using model and then rebuild json_data without model.
  • Add a regression test asserting the rewritten deployment URL and that model is not present in the request body.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
src/openai/lib/azure.py Strips model from JSON body when implicitly rewriting deployment-based endpoint URLs.
tests/lib/test_azure.py Adds a regression test ensuring model is removed from the request body for an implicit deployment endpoint.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 64 to 67
model = options.json_data.get("model")
if model is not None and "/deployments" not in str(self.base_url.path):
options.url = f"/deployments/{model}{options.url}"
options.json_data = {k: v for k, v in options.json_data.items() if k != "model"} # type: ignore[union-attr]
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new # type: ignore[union-attr] shouldn’t be necessary here, and it weakens type-safety. Since this branch is specifically narrowing a known union type (options.json_data), consider using is_mapping_t(options.json_data) (or a cast(...) after the runtime check) so the .items() call is type-safe without an ignore.

Suggested change
model = options.json_data.get("model")
if model is not None and "/deployments" not in str(self.base_url.path):
options.url = f"/deployments/{model}{options.url}"
options.json_data = {k: v for k, v in options.json_data.items() if k != "model"} # type: ignore[union-attr]
json_data = cast(Mapping[str, Any], options.json_data)
model = json_data.get("model")
if model is not None and "/deployments" not in str(self.base_url.path):
options.url = f"/deployments/{model}{options.url}"
options.json_data = {k: v for k, v in json_data.items() if k != "model"}

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code uses cast(Mapping[str, Any], options.json_data) — there's no # type: ignore[union-attr] in this PR. The cast provides proper type narrowing after the is_mapping() runtime check.

body = json.loads(req.content.decode())
assert "model" not in body
assert body["prompt"] == "sunset"
assert "/deployments/gpt-image-1-5/" in str(req.url)
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test only asserts a substring match on the URL. That can miss regressions (e.g., missing /openai, missing api-version, wrong path composition). Consider asserting the full expected URL (similar to test_implicit_deployment_path) so the test fails on any URL-shaping bug.

Suggested change
assert "/deployments/gpt-image-1-5/" in str(req.url)
assert (
str(req.url)
== "https://example-resource.azure.openai.com/openai/deployments/gpt-image-1-5/images/generations?api-version=2023-07-01"
)

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test already asserts the full exact URL via str(req.url) == "https://example-resource.azure.openai.com/openai/deployments/.../...?api-version=2023-07-01". Not a substring match.

Comment on lines 50 to 57
@pytest.mark.parametrize("client", [sync_client, async_client])
def test_implicit_deployment_strips_model_from_body(client: Client) -> None:
req = client._build_request(
FinalRequestOptions.construct(
method="post",
url="/images/generations",
json_data={"model": "gpt-image-1-5", "prompt": "sunset"},
)
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description says the new test covers two parametrized endpoint cases (/chat/completions and /images/generations), but the added test currently only covers /images/generations. Either update the PR description or extend this test to parametrize over both endpoints so the behavior is validated for chat completions as well.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — combined both tests into a single parametrized test covering both /images/generations and /chat/completions endpoints. Commit f6d0357.

Comment on lines 59 to 61
import json

body = json.loads(req.content.decode())
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

json is imported inside the test function, while the rest of this file keeps imports at module scope (see existing imports at tests/lib/test_azure.py:1-13). Moving this import to the top-level import section would keep the file consistent and avoids per-test import overhead.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

json is already imported at module scope (line 3 of the file). Not inside any test function.

…, parametrized test

- Replace type: ignore[union-attr] with proper cast() type narrowing
- Assert full URL instead of substring match in deployment test
- Add parametrized test for /chat/completions endpoint
- Move import json to module scope for consistency

Refs: openai#2910
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Azure OpenAI - model parameter in request body causes failures when deployment name differs from model name

2 participants