zuplo 7.7.8 → 7.7.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,466 @@
1
+ ---
2
+ title: Using Azure AI
3
+ sidebar_label: Azure AI
4
+ description:
5
+ Serve Azure OpenAI and Microsoft Foundry models through one provider
6
+ configuration. Map each deployment to the model it serves so the gateway
7
+ prices usage.
8
+ ---
9
+
10
+ **Azure AI** serves models from your own Azure resource through one provider
11
+ configuration—one resource endpoint and one resource key. Adding it gives your
12
+ [apps](./apps.mdx) the models you have deployed, through the
13
+ [Universal API](./universal-api.mdx), on your Azure subscription and Azure
14
+ billing.
15
+
16
+ One provider type covers both kinds of Azure resource:
17
+
18
+ - An **Azure OpenAI** resource deploys OpenAI models and serves them on an
19
+ OpenAI-compatible API.
20
+ - A **Microsoft Foundry** resource serves that same API for every Foundry model
21
+ family—Grok, DeepSeek, Llama, Mistral, Phi, Kimi and more—_and_ serves Claude
22
+ on the native Anthropic Messages API.
23
+
24
+ Apps reference models as `providerName/model`, where `providerName` is the name
25
+ you give the provider configuration. A provider named `azureai` serves
26
+ `azureai/my-gpt`.
27
+
28
+ ## Azure serves deployments, not model names
29
+
30
+ Of the ways Azure differs from other providers, this is the one that changes
31
+ what you put in the `model` field.
32
+
33
+ Other providers accept a published model ID. Azure resolves the `model` field
34
+ against the **deployments** in your resource, and you choose deployment names
35
+ when you create them. If you deploy `gpt-4.1-mini` under the name `my-gpt`, then
36
+ `my-gpt` is the only name Azure accepts for it.
37
+
38
+ The gateway forwards your deployment name to Azure unchanged, because it's the
39
+ only address Azure answers to. What the gateway needs to know separately is
40
+ which catalog model each deployment serves—that's what prices your usage.
41
+
42
+ :::caution{title="An unmapped deployment is rejected"}
43
+
44
+ The gateway prices a request by looking up the model name in its catalog. A
45
+ deployment named after something outside the catalog, such as `my-gpt`, matches
46
+ nothing—so the gateway returns **400** rather than serving a request it can't
47
+ bill. Azure would have served it, and the usage would have recorded no cost.
48
+
49
+ Map every deployment whose name differs from the model it serves. You do this
50
+ when you add or edit the provider. The error names the deployment and lists the
51
+ names that do work.
52
+
53
+ :::
54
+
55
+ Mapping is optional when it isn't needed. Deploy `gpt-4.1-mini` under the name
56
+ `gpt-4.1-mini`—the Azure portal's own default—and it already matches the
57
+ catalog.
58
+
59
+ ### Deployment name rules
60
+
61
+ Azure accepts 2 to 64 characters of letters, digits, hyphens, underscores and
62
+ periods, and won't accept a name ending in a period. Leading hyphens and
63
+ periods, doubled periods, trailing hyphens and uppercase letters are all fine.
64
+
65
+ Deployment names match case-insensitively, so a deployment named `MyGpt` answers
66
+ to `mygpt`, and one named `my-gpt` answers to `MY-GPT`. Map each deployment once
67
+ using whichever spelling you prefer; requests in any casing resolve to it.
68
+
69
+ ## How the gateway routes Azure AI models
70
+
71
+ A Foundry resource serves two API formats on the same host with the same
72
+ resource key:
73
+
74
+ - An **OpenAI-compatible API** at `/openai/v1`, serving every non-Claude model.
75
+ - The **native Anthropic Messages API** at `/anthropic/v1/messages`, serving the
76
+ Claude models.
77
+
78
+ <Diagram height="h-64">
79
+ <DiagramNode id="app">Your app</DiagramNode>
80
+ <DiagramNode id="gateway" variant="zuplo">
81
+ AI Gateway
82
+ </DiagramNode>
83
+ <DiagramGroup id="azure" label="Your Azure resource">
84
+ <DiagramNode id="openai-surface" variant="blue">
85
+ OpenAI-compatible API
86
+ </DiagramNode>
87
+ <DiagramNode id="messages-surface" variant="green">
88
+ Anthropic Messages API
89
+ </DiagramNode>
90
+ </DiagramGroup>
91
+ <DiagramEdge from="app" to="gateway" label="Universal API" />
92
+ <DiagramEdge
93
+ from="gateway"
94
+ to="openai-surface"
95
+ label="OpenAI-compatible models"
96
+ />
97
+ <DiagramEdge from="gateway" to="messages-surface" label="Claude models" />
98
+ </Diagram>
99
+
100
+ The gateway's model catalog records which API serves each model, and routes
101
+ every request accordingly. Your clients always call your app's URL and never see
102
+ the Azure endpoint.
103
+
104
+ Claude needs a Foundry resource, because an Azure OpenAI resource can't deploy
105
+ Claude at all. Which endpoint host you configure doesn't affect this—a Foundry
106
+ resource serves Claude on either host family the provider accepts.
107
+
108
+ ## Supported endpoints
109
+
110
+ | Endpoint | OpenAI-compatible models | Claude models |
111
+ | ---------------------- | ------------------------ | ------------- |
112
+ | `/v1/chat/completions` | ✅ Forwarded | ✅ Translated |
113
+ | `/v1/embeddings` | ✅ Embedding models | ❌ |
114
+ | `/v1/messages` | ❌ | ✅ Forwarded |
115
+ | `/v1/responses` | ✅ Models that serve it | ❌ |
116
+
117
+ **Forwarded** means the gateway sends your request on in the shape you wrote it.
118
+ **Translated** means it converts between two API shapes, which limits you to the
119
+ parameters listed below.
120
+
121
+ Streaming (`stream: true`) works on chat completions, messages, and responses.
122
+
123
+ The catalog carries three embedding models—`text-embedding-3-small`,
124
+ `text-embedding-3-large` and `text-embedding-ada-002`—so `/v1/embeddings` works
125
+ once you deploy one of them and map your deployment to it.
126
+
127
+ `/v1/responses` serves the OpenAI Responses API, buffered and streaming. Support
128
+ is **per deployed model**, not per resource: Azure serves Responses for
129
+ particular model and version combinations, and a deployment outside that set
130
+ answers `400` with `The requested operation is unsupported`—which is what an
131
+ embedding deployment on this endpoint returns. Check Azure's own Responses API
132
+ model list for your model and version before relying on it.
133
+
134
+ The management operations on a stored response are forwarded too:
135
+
136
+ | Operation | Does |
137
+ | ------------------------------------ | ------------------------------------- |
138
+ | `GET /v1/responses/{id}` | Retrieves the response |
139
+ | `GET /v1/responses/{id}/input_items` | Lists the input items of one response |
140
+ | `DELETE /v1/responses/{id}` | Deletes the response |
141
+
142
+ Two things they need. Send **`store: true`** on the create, since your resource
143
+ serves them only for a response it kept. And because these requests carry no
144
+ body, there is no `model` in them for the gateway to route on—it uses the app's
145
+ first configured completions model instead, so the app needs a
146
+ [Model Filtering](../policies/ai-gateway-model-filtering-v2-inbound.mdx) policy
147
+ whose completions allow list **starts with an OpenAI-compatible Azure
148
+ deployment**. Without one the gateway answers `400`, asking for preselected
149
+ routing, before reaching Azure.
150
+
151
+ A Claude deployment doesn't work in that first slot, even though the allow list
152
+ accepts it—Claude doesn't serve Responses at all, so the request still fails.
153
+
154
+ Azure also takes a moment to make a new response readable. A `GET` immediately
155
+ after a create can return `404` before it settles, so retry rather than treating
156
+ the first `404` as final.
157
+
158
+ Claude deployments don't serve Responses, so a Claude model on this endpoint
159
+ fails with a `400` error—use
160
+ [`/v1/messages`](#call-claude-models-on-the-messages-api) for those.
161
+
162
+ Claude models work on `/v1/chat/completions` through the gateway's translation
163
+ to the Messages API, which supports the core chat parameters: `messages`,
164
+ `max_tokens`, `temperature`, `top_p`, `stop`, and `stream`. For tool use or
165
+ other Anthropic-specific features, call
166
+ [`/v1/messages`](#call-claude-models-on-the-messages-api) instead.
167
+
168
+ This is the gateway's standard handling for Claude, not something specific to
169
+ Azure—[Bedrock Mantle](./bedrock-mantle.mdx#supported-endpoints-by-model-family)
170
+ serves Claude the same way. You get an ordinary `chat.completion` object back,
171
+ with `choices` and `prompt_tokens`/`completion_tokens`. The one visible trace of
172
+ the conversion is the response `id`, which keeps Anthropic's `msg_` prefix.
173
+ Azure's OpenAI-compatible models are forwarded instead of translated, so their
174
+ responses carry Azure's own fields, such as `content_filter_results`.
175
+
176
+ ## Before you begin
177
+
178
+ You need:
179
+
180
+ - An Azure subscription with an Azure OpenAI or Microsoft Foundry resource. Note
181
+ the resource name—it's the first label of the resource's endpoint host.
182
+ - **At least one model deployed in that resource**, and its deployment name. The
183
+ gateway addresses deployments, so a resource with no deployments serves
184
+ nothing. Deploy models in the Azure portal under your resource's **Model
185
+ deployments**.
186
+ - One of the resource's API keys, from the resource's **Keys and Endpoint** page
187
+ in the Azure portal.
188
+ - An AI Gateway project in the Zuplo Portal.
189
+ - An AI Gateway [app](./apps.mdx) to call the models from. The app page shows
190
+ the app's API URL, and its API key lives on the app's **API Key** tab.
191
+
192
+ :::note
193
+
194
+ Use a resource key, not a Microsoft Entra ID token. Azure accepts both on its
195
+ own API, but the gateway stores long-lived resource keys, and the provider
196
+ dialog rejects a pasted Entra token—those expire within hours.
197
+
198
+ :::
199
+
200
+ ### Deploying Claude on Foundry
201
+
202
+ Claude deployments carry extra requirements that Azure applies only to them, and
203
+ the failures are easy to misread:
204
+
205
+ - Azure asks for your **industry, organization name and country** when you
206
+ create an Anthropic-format deployment. Give a plain organization name;
207
+ punctuation in it has been enough to fail the deployment.
208
+ - Azure validates none of it up front. A deployment can report success, spend a
209
+ few minutes provisioning, and then land in a **Failed** state with an
210
+ internal-error message. Check the deployment's provisioning state before you
211
+ configure the provider, and delete a failed deployment before reusing its
212
+ name.
213
+ - If you deploy through the Azure REST API or a template rather than the portal,
214
+ use API version `2025-12-01` or later. Earlier versions silently ignore those
215
+ fields and then reject the request for not providing them.
216
+
217
+ ## Add the provider
218
+
219
+ Adding or editing providers requires the **Edit** permission, granted to Zuplo
220
+ account and project **Admins**—see
221
+ [Managing Providers](./managing-providers.mdx).
222
+
223
+ <Stepper>
224
+
225
+ 1. Open
226
+ [**Settings → AI Providers**](https://portal.zuplo.com/+/account/project/ai/settings/data-models)
227
+ in your AI Gateway project in the Zuplo Portal.
228
+
229
+ 1. Click the **Add Provider** button.
230
+
231
+ 1. In the **AI Provider** list, select **Azure AI** from the Default Providers
232
+ group.
233
+
234
+ 1. Review the **Provider Name**, which fills in as `azureai` (a second
235
+ configuration becomes `azureai-2`). You can replace it with your own name,
236
+ but only now—the name is permanent after creation, and it's the prefix in
237
+ every model reference: a provider named `azureai` serves `azureai/my-gpt`.
238
+
239
+ 1. In **Azure Resource Name**, enter your resource's name, such as
240
+ `my-resource`. Use the host family selector beside the field to pick
241
+ `openai.azure.com` or `services.ai.azure.com`, matching your resource's
242
+ endpoint. The gateway sends this provider's requests to
243
+ `https://<resource>.<host family>`.
244
+
245
+ 1. In **API Key**, paste one of the resource's keys.
246
+
247
+ 1. Select the models to enable, or click **Select All**. The picker shows
248
+ per-token prices, which the gateway uses to track cost per app. You can
249
+ change the selection later.
250
+
251
+ 1. Under **Deployment Names**, click **Add deployment** for each deployment
252
+ whose name differs from the model it serves. Enter the deployment name, then
253
+ pick the model it serves from the list. Skip this for deployments already
254
+ named after their model.
255
+
256
+ 1. Click **Create**.
257
+
258
+ </Stepper>
259
+
260
+ :::note
261
+
262
+ Saving provider settings triggers an automatic production deployment of your
263
+ gateway, because provider credentials are part of the deployed gateway. The
264
+ change is live once the deployment completes.
265
+
266
+ :::
267
+
268
+ When you edit the provider later—see
269
+ [Managing Providers](./managing-providers.mdx)—the **Azure Resource Name**, host
270
+ family and deployment mappings all stay editable, and you can replace the API
271
+ key. The **Provider Name** doesn't change.
272
+
273
+ :::caution{title="Which endpoint form to enter"}
274
+
275
+ Enter the resource name and pick a host family; don't paste a full URL. The
276
+ provider accepts the `openai.azure.com` and `services.ai.azure.com` families
277
+ only.
278
+
279
+ A Foundry resource also has a `cognitiveservices.azure.com` address, and that's
280
+ the one the Azure portal shows as the resource's endpoint. It isn't accepted
281
+ here—use the resource name with one of the two families above instead. The
282
+ resource is the same either way.
283
+
284
+ :::
285
+
286
+ ## Verify the provider
287
+
288
+ Once the deployment completes, send a chat completions request to your app's
289
+ [Universal API](./universal-api.mdx) URL—shown at the top of the
290
+ [app page](./apps.mdx)—with the app's API key as the bearer token and one of
291
+ your deployments:
292
+
293
+ ```bash
294
+ curl https://my-gateway-main-2e18f50.zuplo.app/config_fe0a04972d2848e0a94ae4b8bcd1497e/v1/chat/completions \
295
+ -H "Authorization: Bearer $ZUPLO_APP_API_KEY" \
296
+ -H "Content-Type: application/json" \
297
+ -d '{
298
+ "model": "azureai/my-gpt",
299
+ "messages": [{ "role": "user", "content": "Say hi" }]
300
+ }'
301
+ ```
302
+
303
+ The URL is a sample—replace it with your app's API URL plus
304
+ `/v1/chat/completions`, set `ZUPLO_APP_API_KEY` to the app's API key, and
305
+ substitute your own deployment name for `my-gpt`.
306
+
307
+ A `200` response confirms the provider works. If the request fails immediately
308
+ after you save the provider, the deployment may not have finished—retry before
309
+ debugging further.
310
+
311
+ ## Call OpenAI-compatible models
312
+
313
+ Use any OpenAI client with your app's URL plus `/v1` as the base URL and the
314
+ app's API key:
315
+
316
+ ```ts
317
+ import OpenAI from "openai";
318
+
319
+ const client = new OpenAI({
320
+ apiKey: process.env.ZUPLO_APP_API_KEY,
321
+ baseURL:
322
+ "https://my-gateway-main-2e18f50.zuplo.app/config_fe0a04972d2848e0a94ae4b8bcd1497e/v1",
323
+ });
324
+
325
+ const response = await client.chat.completions.create({
326
+ model: "azureai/my-gpt",
327
+ messages: [{ role: "user", content: "Summarize this ticket." }],
328
+ });
329
+ ```
330
+
331
+ The same call works with a Claude deployment—the gateway translates it to the
332
+ Messages API—within the [translation's parameter subset](#supported-endpoints).
333
+
334
+ ## Call Claude models on the Messages API
335
+
336
+ Claude deployments on a Foundry resource serve the native
337
+ [Anthropic Messages API](./universal-api.mdx#supported-endpoints) at
338
+ `/v1/messages`. With the Anthropic SDK, set `baseURL` to the app's URL _without_
339
+ `/v1`—the SDK appends `/v1/messages` itself—and pass the app's API key as
340
+ `authToken`, not `apiKey`:
341
+
342
+ ```ts
343
+ import Anthropic from "@anthropic-ai/sdk";
344
+
345
+ const client = new Anthropic({
346
+ baseURL:
347
+ "https://my-gateway-main-2e18f50.zuplo.app/config_fe0a04972d2848e0a94ae4b8bcd1497e",
348
+ authToken: process.env.ZUPLO_APP_API_KEY, // apiKey would send x-api-key, which the gateway ignores
349
+ });
350
+
351
+ const message = await client.messages.create({
352
+ model: "azureai/my-claude",
353
+ max_tokens: 1024,
354
+ messages: [{ role: "user", content: "Say hi" }],
355
+ });
356
+ ```
357
+
358
+ The gateway forwards the request body to Azure verbatim, so tool use, system
359
+ prompts and streaming work as they do against Anthropic directly.
360
+
361
+ `anthropic-beta` headers are the exception. The gateway forwards the
362
+ long-context and prompt-caching betas and drops every other value, because Azure
363
+ rejects an unrecognized beta by failing the whole request. A client asking for a
364
+ different beta still gets an answer, without that feature.
365
+
366
+ ## What the model field reports in responses
367
+
368
+ The `model` field in a response doesn't always say what you might expect, and
369
+ what it says depends on the model you called.
370
+
371
+ **Claude models and `/v1/responses` report your deployment name.** Everything
372
+ else reports Azure's own model ID, which usually carries a version date the
373
+ short name doesn't.
374
+
375
+ | Endpoint | Deployment `my-gpt` / `my-embed` / `my-claude` serves | `model` in the response |
376
+ | ---------------------- | ----------------------------------------------------- | ------------------------- |
377
+ | `/v1/chat/completions` | `gpt-4.1-mini` | `gpt-4.1-mini-2025-04-14` |
378
+ | `/v1/responses` | `gpt-4.1-mini` | `my-gpt` |
379
+ | `/v1/embeddings` | `text-embedding-3-small` | `text-embedding-3-small` |
380
+ | `/v1/messages` | `claude-haiku-4-5` | `my-claude` |
381
+ | `/v1/chat/completions` | `claude-haiku-4-5` | `my-claude` |
382
+
383
+ The same deployment reports two different values depending on the endpoint you
384
+ call it on: chat completions answers with Azure's dated ID, Responses with the
385
+ name you gave the deployment. That's Azure's own behavior on each surface, not
386
+ something the gateway changes.
387
+
388
+ A Claude model reports your deployment name on both endpoints—the native
389
+ Messages API and the chat completions translation—but for a different reason:
390
+ there the gateway builds the response itself and echoes back the model you asked
391
+ for.
392
+
393
+ One exception, if you stream Claude: the `message_start` event reports Azure's
394
+ ID (`claude-haiku-4-5-20251001`) rather than your deployment name, because the
395
+ gateway forwards each event as it arrives instead of rebuilding the response. So
396
+ the same request reports two different values depending on whether you streamed
397
+ it.
398
+
399
+ Treat this field as informational either way. If you need to know which
400
+ deployment served a request, use your own request metadata rather than parsing
401
+ this.
402
+
403
+ ## Troubleshooting
404
+
405
+ **A request fails with a deployment-not-found error.** The `model` value after
406
+ the provider prefix must be a deployment name in your Azure resource, not a
407
+ published model ID. Check the deployment list in the Azure portal under your
408
+ resource's **Model deployments**, and confirm the deployment finished
409
+ provisioning.
410
+
411
+ **A request fails saying the deployment isn't mapped.** The deployment exists in
412
+ Azure, but the gateway can't tell which model it serves, so it can't price the
413
+ request. Edit the provider, add the deployment under **Deployment Names**, and
414
+ pick its model. If the deployment is named after its model, selecting that model
415
+ is enough. The error lists the names that currently work.
416
+
417
+ **The dialog rejects your endpoint.** Enter the resource name, not a URL, and
418
+ pick `openai.azure.com` or `services.ai.azure.com`. A Foundry resource's
419
+ `cognitiveservices.azure.com` address isn't accepted—use the resource name with
420
+ one of those two families. Resource names are 2 to 63 characters of lowercase
421
+ letters, digits and hyphens, starting and ending with a letter or digit.
422
+
423
+ **The dialog rejects your API key.** The gateway takes a resource key from the
424
+ resource's **Keys and Endpoint** page. A Microsoft Entra ID access token is
425
+ rejected—it expires within hours, so the connection would break the same day.
426
+
427
+ **A Claude model returns an error on an Azure OpenAI resource.** Only Foundry
428
+ resources deploy Claude. Confirm the deployment exists and is Anthropic-format,
429
+ and see [Deploying Claude on Foundry](#deploying-claude-on-foundry) for the
430
+ requirements Azure applies to those deployments.
431
+
432
+ **A `400` error names `/v1/responses`.** Two different causes, and the message
433
+ tells you which. If it names the provider or the endpoint, the deployment is a
434
+ **Claude** one—Claude doesn't serve Responses, so call
435
+ [`/v1/messages`](#call-claude-models-on-the-messages-api) instead, or
436
+ `/v1/chat/completions` for the translated form. If it says
437
+ `The requested operation is unsupported`, that came from Azure: the deployed
438
+ model doesn't serve Responses. Support is per model and version, so check
439
+ Azure's Responses API model list—an embedding deployment always fails this way.
440
+
441
+ **A `400` asks for preselected routing.** You called `GET` or `DELETE` on a
442
+ response, or listed its input items. Those requests carry no body, so there is
443
+ no `model` for the gateway to route on and it falls back to the app's first
444
+ configured completions model. Add a
445
+ [Model Filtering](../policies/ai-gateway-model-filtering-v2-inbound.mdx) policy
446
+ to the app with an **OpenAI-compatible** Azure deployment first in its
447
+ completions allow list. A Claude deployment in that slot still fails, since
448
+ Claude doesn't serve Responses.
449
+
450
+ **A model in the picker fails at request time.** The picker lists the full Azure
451
+ catalog, but a model only works once you deploy it in your resource, and model
452
+ availability varies by region and resource kind. Deploy the model, then map your
453
+ deployment name to it.
454
+
455
+ ## Next steps
456
+
457
+ - [AI Providers](./providers.mdx)—the capability matrix across every supported
458
+ provider.
459
+ - [Universal API](./universal-api.mdx)—the endpoints every app serves and how
460
+ model references work.
461
+ - [Managing Providers](./managing-providers.mdx)—edit models, keys, and the
462
+ endpoint, and understand when changes deploy.
463
+ - [AI Gateway Apps](./apps.mdx)—create the apps that call your Azure-backed
464
+ models.
465
+ - [Model Filtering policy](../policies/ai-gateway-model-filtering-v2-inbound.mdx)—control
466
+ which models each app can call.
@@ -72,9 +72,13 @@ either way.
72
72
  | ---------------------- | ---------------------------- | ------------- |
73
73
  | `/v1/chat/completions` | ✅ Forwarded | ✅ Translated |
74
74
  | `/v1/responses` | ✅ Models that serve it | ❌ |
75
- | `/v1/messages` | ❌ | ✅ Native |
75
+ | `/v1/messages` | ❌ | ✅ Forwarded |
76
76
  | `/v1/embeddings` | ❌ Mantle has no such models | ❌ |
77
77
 
78
+ **Forwarded** means the gateway sends your request on in the shape you wrote it.
79
+ **Translated** means it converts between two API shapes, which limits you to the
80
+ parameters listed below.
81
+
78
82
  Streaming (`stream: true`) works on all three serving endpoints.
79
83
 
80
84
  On the OpenAI-compatible side, endpoint support is per model, decided by AWS: