SIGN IN SIGN UP

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

- [x] 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:

```python
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)

```python
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.

---------

Co-authored-by: Marcus Wood <marcuswood@openai.com>
H
Henry Su committed
b3c04f4e701e07726f320295a38928421946e530
Parent: ccc10f5
Committed by GitHub <noreply@github.com> on 9/15/2026, 9:11:34 PM