Skip to content

fix(azure): preserve deployment routing across copy/with_options - #3593

Open
hsusul wants to merge 1 commit into
openai:mainfrom
hsusul:fix/azure-copy-preserve-deployment
Open

fix(azure): preserve deployment routing across copy/with_options#3593
hsusul wants to merge 1 commit into
openai:mainfrom
hsusul:fix/azure-copy-preserve-deployment

Conversation

@hsusul

@hsusul hsusul commented Aug 10, 2026

Copy link
Copy Markdown
  • I understand that this repository is auto-generated and my pull request may not be merged

Changes being requested

Fixes a routing regression in AzureOpenAI / AsyncAzureOpenAI after .copy() / .with_options().

This change is in hand-maintained code — src/openai/lib/azure.py has no File generated from our OpenAPI spec by Stainless header, and copy()/with_options() there is custom Azure logic, so it is not affected by codegen.

Problem

AzureOpenAI.copy() (aliased as with_options()) delegates to the base OpenAI.copy(), which reconstructs the client from base_url and does not pass azure_endpoint / azure_deployment back to AzureOpenAI.__init__. Because _azure_endpoint and _azure_deployment are only set from those constructor arguments, they get reset to None on the copied client.

_prepare_url() uses those attributes to bypass the deployment path for non-deployment endpoints:

if self._azure_deployment and self._azure_endpoint and url not in _deployments_endpoints:
    # -> {endpoint}/openai/{url}   (no /deployments/<name>/)

Once they are None, that bypass no longer runs, so a client configured with azure_deployment starts routing non-deployment endpoints (e.g. /models) under the deployment path, which 404s.

Reproduction (no network / key required)

from openai import AzureOpenAI

c = AzureOpenAI(
    azure_endpoint="https://example.openai.azure.com",
    azure_deployment="my-deploy",
    api_version="2024-06-01",
    azure_ad_token="fake-token",
)
print(c._prepare_url("/models"))
# https://example.openai.azure.com/openai/models        ✅

c2 = c.with_options(timeout=30)
print(c2._azure_endpoint, c2._azure_deployment)          # None None
print(c2._prepare_url("/models"))
# https://example.openai.azure.com/openai/deployments/my-deploy/models   ❌ (404)

Fix

Preserve _azure_endpoint / _azure_deployment on the copied client, unless the caller overrides base_url in the copy (in which case the old endpoint context is intentionally not carried over). Applied symmetrically to the sync and async clients.

Why minimal

  • Only copy() in each of the two Azure clients changes; two guarded lines each.
  • Public API, credential handling, and the mutually-exclusive base_url / azure_endpoint constructor contract are untouched (the fix deliberately avoids passing azure_endpoint alongside base_url).
  • Deployment endpoints (e.g. /chat/completions) are unaffected — base_url already encodes the deployment and _build_request continues to guard on "/deployments" in base_url.path.

Tests

Added test_copy_preserves_deployment_routing in tests/lib/test_azure.py (sync + async × copy / with_options), asserting that after a copy:

  • /models{endpoint}/openai/models (not nested under /deployments/<name>/), and
  • /chat/completions still keeps the deployment path.

Fails on main (wrong /models URL), passes with this change.

Validation

  • rye run pytest tests/lib/test_azure.py → 63 passed
  • rye run pytest tests/lib/ → all pass except a pre-existing, unrelated failure (test_bedrock_auth_conformance.py::test_retry_signing_fixture, which fails identically on clean main in this environment)
  • ruff check / ruff format clean on both files
  • pyright and mypy clean on src/openai/lib/azure.py

Compatibility

No public API change; behavior only changes for the previously-broken post-copy case. Copies that override base_url keep their current behavior.

Additional context & links

Discovered by auditing sibling copy/state-preservation logic; no existing issue or PR covers this. .with_options() is a common pattern (per-request timeouts/headers), so Azure users combining it with azure_deployment are likely to hit this.

AzureOpenAI.copy()/with_options() reconstructs the client from base_url
via the base OpenAI.copy(), which does not pass azure_endpoint or
azure_deployment. As a result _azure_endpoint and _azure_deployment were
reset to None on the copy, so _prepare_url() no longer bypassed the
deployment path for non-deployment endpoints. A client built with
azure_deployment would then route e.g. /models to
/openai/deployments/<deployment>/models instead of /openai/models,
producing 404s after a copy.

Preserve _azure_endpoint/_azure_deployment on the copied client unless
the caller overrides base_url.
@hsusul
hsusul requested a review from a team as a code owner August 10, 2026 17:18
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.

1 participant