Commit b3c04f4
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>1 parent ccc10f5 commit b3c04f4
2 files changed
Lines changed: 68 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
410 | 410 | | |
411 | 411 | | |
412 | 412 | | |
413 | | - | |
| 413 | + | |
414 | 414 | | |
415 | 415 | | |
416 | 416 | | |
| |||
434 | 434 | | |
435 | 435 | | |
436 | 436 | | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
437 | 445 | | |
438 | 446 | | |
439 | 447 | | |
| |||
762 | 770 | | |
763 | 771 | | |
764 | 772 | | |
765 | | - | |
| 773 | + | |
766 | 774 | | |
767 | 775 | | |
768 | 776 | | |
| |||
786 | 794 | | |
787 | 795 | | |
788 | 796 | | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
789 | 805 | | |
790 | 806 | | |
791 | 807 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
80 | 80 | | |
81 | 81 | | |
82 | 82 | | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
83 | 133 | | |
84 | 134 | | |
85 | 135 | | |
| |||
0 commit comments