zuplo 7.7.8 → 7.7.9
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.
- package/docs/ai-gateway/azure-ai.mdx +410 -0
- package/docs/ai-gateway/managing-providers.mdx +10 -6
- package/docs/ai-gateway/overview.mdx +3 -2
- package/docs/ai-gateway/providers.mdx +35 -0
- package/docs/ai-gateway/universal-api.mdx +6 -6
- package/docs/policies/_index.md +1 -1
- package/docs/policies/ai-gateway-auth-v2-inbound/doc.md +65 -3
- package/docs/policies/ai-gateway-auth-v2-inbound/schema.json +29 -4
- package/docs/policies/ai-gateway-configuration-executor-v2-inbound/doc.md +54 -30
- package/docs/policies/ai-gateway-fallback-model-v2-inbound/doc.md +6 -3
- package/docs/policies/ai-gateway-metering-v2-inbound/doc.md +32 -19
- package/docs/policies/ai-gateway-model-filtering-v2-inbound/doc.md +55 -0
- package/package.json +5 -5
|
@@ -0,0 +1,410 @@
|
|
|
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` | ❌ | ✅ Native |
|
|
115
|
+
| `/v1/responses` | ❌ | ❌ |
|
|
116
|
+
|
|
117
|
+
Streaming (`stream: true`) works on chat completions and messages.
|
|
118
|
+
|
|
119
|
+
The catalog carries three embedding models—`text-embedding-3-small`,
|
|
120
|
+
`text-embedding-3-large` and `text-embedding-ada-002`—so `/v1/embeddings` works
|
|
121
|
+
once you deploy one of them and map your deployment to it.
|
|
122
|
+
|
|
123
|
+
`/v1/responses` isn't served for Azure AI, and a Responses request fails with a
|
|
124
|
+
`400` error before any upstream call. Azure does serve a Responses API on its
|
|
125
|
+
own endpoint; the gateway doesn't route to it yet. Use `/v1/chat/completions`
|
|
126
|
+
instead.
|
|
127
|
+
|
|
128
|
+
Claude models work on `/v1/chat/completions` through the gateway's translation
|
|
129
|
+
to the Messages API, which supports the core chat parameters: `messages`,
|
|
130
|
+
`max_tokens`, `temperature`, `top_p`, `stop`, and `stream`. For tool use or
|
|
131
|
+
other Anthropic-specific features, call
|
|
132
|
+
[`/v1/messages`](#call-claude-models-on-the-messages-api) instead.
|
|
133
|
+
|
|
134
|
+
This is the gateway's standard handling for Claude, not something specific to
|
|
135
|
+
Azure—[Bedrock Mantle](./bedrock-mantle.mdx#supported-endpoints-by-model-family)
|
|
136
|
+
serves Claude the same way. You get an ordinary `chat.completion` object back,
|
|
137
|
+
with `choices` and `prompt_tokens`/`completion_tokens`. The one visible trace of
|
|
138
|
+
the conversion is the response `id`, which keeps Anthropic's `msg_` prefix.
|
|
139
|
+
Azure's OpenAI-compatible models are forwarded instead of translated, so their
|
|
140
|
+
responses carry Azure's own fields, such as `content_filter_results`.
|
|
141
|
+
|
|
142
|
+
## Before you begin
|
|
143
|
+
|
|
144
|
+
You need:
|
|
145
|
+
|
|
146
|
+
- An Azure subscription with an Azure OpenAI or Microsoft Foundry resource. Note
|
|
147
|
+
the resource name—it's the first label of the resource's endpoint host.
|
|
148
|
+
- **At least one model deployed in that resource**, and its deployment name. The
|
|
149
|
+
gateway addresses deployments, so a resource with no deployments serves
|
|
150
|
+
nothing. Deploy models in the Azure portal under your resource's **Model
|
|
151
|
+
deployments**.
|
|
152
|
+
- One of the resource's API keys, from the resource's **Keys and Endpoint** page
|
|
153
|
+
in the Azure portal.
|
|
154
|
+
- An AI Gateway project in the Zuplo Portal.
|
|
155
|
+
- An AI Gateway [app](./apps.mdx) to call the models from. The app page shows
|
|
156
|
+
the app's API URL, and its API key lives on the app's **API Key** tab.
|
|
157
|
+
|
|
158
|
+
:::note
|
|
159
|
+
|
|
160
|
+
Use a resource key, not a Microsoft Entra ID token. Azure accepts both on its
|
|
161
|
+
own API, but the gateway stores long-lived resource keys, and the provider
|
|
162
|
+
dialog rejects a pasted Entra token—those expire within hours.
|
|
163
|
+
|
|
164
|
+
:::
|
|
165
|
+
|
|
166
|
+
### Deploying Claude on Foundry
|
|
167
|
+
|
|
168
|
+
Claude deployments carry extra requirements that Azure applies only to them, and
|
|
169
|
+
the failures are easy to misread:
|
|
170
|
+
|
|
171
|
+
- Azure asks for your **industry, organization name and country** when you
|
|
172
|
+
create an Anthropic-format deployment. Give a plain organization name;
|
|
173
|
+
punctuation in it has been enough to fail the deployment.
|
|
174
|
+
- Azure validates none of it up front. A deployment can report success, spend a
|
|
175
|
+
few minutes provisioning, and then land in a **Failed** state with an
|
|
176
|
+
internal-error message. Check the deployment's provisioning state before you
|
|
177
|
+
configure the provider, and delete a failed deployment before reusing its
|
|
178
|
+
name.
|
|
179
|
+
- If you deploy through the Azure REST API or a template rather than the portal,
|
|
180
|
+
use API version `2025-12-01` or later. Earlier versions silently ignore those
|
|
181
|
+
fields and then reject the request for not providing them.
|
|
182
|
+
|
|
183
|
+
## Add the provider
|
|
184
|
+
|
|
185
|
+
Adding or editing providers requires the **Edit** permission, granted to Zuplo
|
|
186
|
+
account and project **Admins**—see
|
|
187
|
+
[Managing Providers](./managing-providers.mdx).
|
|
188
|
+
|
|
189
|
+
<Stepper>
|
|
190
|
+
|
|
191
|
+
1. Open
|
|
192
|
+
[**Settings → AI Providers**](https://portal.zuplo.com/+/account/project/ai/settings/data-models)
|
|
193
|
+
in your AI Gateway project in the Zuplo Portal.
|
|
194
|
+
|
|
195
|
+
1. Click the **Add Provider** button.
|
|
196
|
+
|
|
197
|
+
1. In the **AI Provider** list, select **Azure AI** from the Default Providers
|
|
198
|
+
group.
|
|
199
|
+
|
|
200
|
+
1. Review the **Provider Name**, which fills in as `azureai` (a second
|
|
201
|
+
configuration becomes `azureai-2`). You can replace it with your own name,
|
|
202
|
+
but only now—the name is permanent after creation, and it's the prefix in
|
|
203
|
+
every model reference: a provider named `azureai` serves `azureai/my-gpt`.
|
|
204
|
+
|
|
205
|
+
1. In **Azure Resource Name**, enter your resource's name, such as
|
|
206
|
+
`my-resource`. Use the host family selector beside the field to pick
|
|
207
|
+
`openai.azure.com` or `services.ai.azure.com`, matching your resource's
|
|
208
|
+
endpoint. The gateway sends this provider's requests to
|
|
209
|
+
`https://<resource>.<host family>`.
|
|
210
|
+
|
|
211
|
+
1. In **API Key**, paste one of the resource's keys.
|
|
212
|
+
|
|
213
|
+
1. Select the models to enable, or click **Select All**. The picker shows
|
|
214
|
+
per-token prices, which the gateway uses to track cost per app. You can
|
|
215
|
+
change the selection later.
|
|
216
|
+
|
|
217
|
+
1. Under **Deployment Names**, click **Add deployment** for each deployment
|
|
218
|
+
whose name differs from the model it serves. Enter the deployment name, then
|
|
219
|
+
pick the model it serves from the list. Skip this for deployments already
|
|
220
|
+
named after their model.
|
|
221
|
+
|
|
222
|
+
1. Click **Create**.
|
|
223
|
+
|
|
224
|
+
</Stepper>
|
|
225
|
+
|
|
226
|
+
:::note
|
|
227
|
+
|
|
228
|
+
Saving provider settings triggers an automatic production deployment of your
|
|
229
|
+
gateway, because provider credentials are part of the deployed gateway. The
|
|
230
|
+
change is live once the deployment completes.
|
|
231
|
+
|
|
232
|
+
:::
|
|
233
|
+
|
|
234
|
+
When you edit the provider later—see
|
|
235
|
+
[Managing Providers](./managing-providers.mdx)—the **Azure Resource Name**, host
|
|
236
|
+
family and deployment mappings all stay editable, and you can replace the API
|
|
237
|
+
key. The **Provider Name** doesn't change.
|
|
238
|
+
|
|
239
|
+
:::caution{title="Which endpoint form to enter"}
|
|
240
|
+
|
|
241
|
+
Enter the resource name and pick a host family; don't paste a full URL. The
|
|
242
|
+
provider accepts the `openai.azure.com` and `services.ai.azure.com` families
|
|
243
|
+
only.
|
|
244
|
+
|
|
245
|
+
A Foundry resource also has a `cognitiveservices.azure.com` address, and that's
|
|
246
|
+
the one the Azure portal shows as the resource's endpoint. It isn't accepted
|
|
247
|
+
here—use the resource name with one of the two families above instead. The
|
|
248
|
+
resource is the same either way.
|
|
249
|
+
|
|
250
|
+
:::
|
|
251
|
+
|
|
252
|
+
## Verify the provider
|
|
253
|
+
|
|
254
|
+
Once the deployment completes, send a chat completions request to your app's
|
|
255
|
+
[Universal API](./universal-api.mdx) URL—shown at the top of the
|
|
256
|
+
[app page](./apps.mdx)—with the app's API key as the bearer token and one of
|
|
257
|
+
your deployments:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
curl https://my-gateway-main-2e18f50.zuplo.app/config_fe0a04972d2848e0a94ae4b8bcd1497e/v1/chat/completions \
|
|
261
|
+
-H "Authorization: Bearer $ZUPLO_APP_API_KEY" \
|
|
262
|
+
-H "Content-Type: application/json" \
|
|
263
|
+
-d '{
|
|
264
|
+
"model": "azureai/my-gpt",
|
|
265
|
+
"messages": [{ "role": "user", "content": "Say hi" }]
|
|
266
|
+
}'
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
The URL is a sample—replace it with your app's API URL plus
|
|
270
|
+
`/v1/chat/completions`, set `ZUPLO_APP_API_KEY` to the app's API key, and
|
|
271
|
+
substitute your own deployment name for `my-gpt`.
|
|
272
|
+
|
|
273
|
+
A `200` response confirms the provider works. If the request fails immediately
|
|
274
|
+
after you save the provider, the deployment may not have finished—retry before
|
|
275
|
+
debugging further.
|
|
276
|
+
|
|
277
|
+
## Call OpenAI-compatible models
|
|
278
|
+
|
|
279
|
+
Use any OpenAI client with your app's URL plus `/v1` as the base URL and the
|
|
280
|
+
app's API key:
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
import OpenAI from "openai";
|
|
284
|
+
|
|
285
|
+
const client = new OpenAI({
|
|
286
|
+
apiKey: process.env.ZUPLO_APP_API_KEY,
|
|
287
|
+
baseURL:
|
|
288
|
+
"https://my-gateway-main-2e18f50.zuplo.app/config_fe0a04972d2848e0a94ae4b8bcd1497e/v1",
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const response = await client.chat.completions.create({
|
|
292
|
+
model: "azureai/my-gpt",
|
|
293
|
+
messages: [{ role: "user", content: "Summarize this ticket." }],
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
The same call works with a Claude deployment—the gateway translates it to the
|
|
298
|
+
Messages API—within the [translation's parameter subset](#supported-endpoints).
|
|
299
|
+
|
|
300
|
+
## Call Claude models on the Messages API
|
|
301
|
+
|
|
302
|
+
Claude deployments on a Foundry resource serve the native
|
|
303
|
+
[Anthropic Messages API](./universal-api.mdx#supported-endpoints) at
|
|
304
|
+
`/v1/messages`. With the Anthropic SDK, set `baseURL` to the app's URL _without_
|
|
305
|
+
`/v1`—the SDK appends `/v1/messages` itself—and pass the app's API key as
|
|
306
|
+
`authToken`, not `apiKey`:
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
310
|
+
|
|
311
|
+
const client = new Anthropic({
|
|
312
|
+
baseURL:
|
|
313
|
+
"https://my-gateway-main-2e18f50.zuplo.app/config_fe0a04972d2848e0a94ae4b8bcd1497e",
|
|
314
|
+
authToken: process.env.ZUPLO_APP_API_KEY, // apiKey would send x-api-key, which the gateway ignores
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const message = await client.messages.create({
|
|
318
|
+
model: "azureai/my-claude",
|
|
319
|
+
max_tokens: 1024,
|
|
320
|
+
messages: [{ role: "user", content: "Say hi" }],
|
|
321
|
+
});
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
The gateway forwards the request body to Azure verbatim, so tool use, system
|
|
325
|
+
prompts and streaming work as they do against Anthropic directly.
|
|
326
|
+
|
|
327
|
+
`anthropic-beta` headers are the exception. The gateway forwards the
|
|
328
|
+
long-context and prompt-caching betas and drops every other value, because Azure
|
|
329
|
+
rejects an unrecognized beta by failing the whole request. A client asking for a
|
|
330
|
+
different beta still gets an answer, without that feature.
|
|
331
|
+
|
|
332
|
+
## What the model field reports in responses
|
|
333
|
+
|
|
334
|
+
The `model` field in a response doesn't always say what you might expect, and
|
|
335
|
+
what it says depends on the model you called.
|
|
336
|
+
|
|
337
|
+
**Claude responses report your deployment name.** Every other response reports
|
|
338
|
+
Azure's own model ID, which usually carries a version date the short name
|
|
339
|
+
doesn't.
|
|
340
|
+
|
|
341
|
+
| Endpoint | Deployment `my-gpt` / `my-embed` / `my-claude` serves | `model` in the response |
|
|
342
|
+
| ---------------------- | ----------------------------------------------------- | ------------------------- |
|
|
343
|
+
| `/v1/chat/completions` | `gpt-4.1-mini` | `gpt-4.1-mini-2025-04-14` |
|
|
344
|
+
| `/v1/embeddings` | `text-embedding-3-small` | `text-embedding-3-small` |
|
|
345
|
+
| `/v1/messages` | `claude-haiku-4-5` | `my-claude` |
|
|
346
|
+
| `/v1/chat/completions` | `claude-haiku-4-5` | `my-claude` |
|
|
347
|
+
|
|
348
|
+
A Claude model reports your deployment name on both endpoints—the native
|
|
349
|
+
Messages API and the chat completions translation—because the gateway builds
|
|
350
|
+
those responses itself and echoes back the model you asked for.
|
|
351
|
+
|
|
352
|
+
One exception, if you stream Claude: the `message_start` event reports Azure's
|
|
353
|
+
ID (`claude-haiku-4-5-20251001`) rather than your deployment name, because the
|
|
354
|
+
gateway forwards each event as it arrives instead of rebuilding the response. So
|
|
355
|
+
the same request reports two different values depending on whether you streamed
|
|
356
|
+
it.
|
|
357
|
+
|
|
358
|
+
Treat this field as informational either way. If you need to know which
|
|
359
|
+
deployment served a request, use your own request metadata rather than parsing
|
|
360
|
+
this.
|
|
361
|
+
|
|
362
|
+
## Troubleshooting
|
|
363
|
+
|
|
364
|
+
**A request fails with a deployment-not-found error.** The `model` value after
|
|
365
|
+
the provider prefix must be a deployment name in your Azure resource, not a
|
|
366
|
+
published model ID. Check the deployment list in the Azure portal under your
|
|
367
|
+
resource's **Model deployments**, and confirm the deployment finished
|
|
368
|
+
provisioning.
|
|
369
|
+
|
|
370
|
+
**A request fails saying the deployment isn't mapped.** The deployment exists in
|
|
371
|
+
Azure, but the gateway can't tell which model it serves, so it can't price the
|
|
372
|
+
request. Edit the provider, add the deployment under **Deployment Names**, and
|
|
373
|
+
pick its model. If the deployment is named after its model, selecting that model
|
|
374
|
+
is enough. The error lists the names that currently work.
|
|
375
|
+
|
|
376
|
+
**The dialog rejects your endpoint.** Enter the resource name, not a URL, and
|
|
377
|
+
pick `openai.azure.com` or `services.ai.azure.com`. A Foundry resource's
|
|
378
|
+
`cognitiveservices.azure.com` address isn't accepted—use the resource name with
|
|
379
|
+
one of those two families. Resource names are 2 to 63 characters of lowercase
|
|
380
|
+
letters, digits and hyphens, starting and ending with a letter or digit.
|
|
381
|
+
|
|
382
|
+
**The dialog rejects your API key.** The gateway takes a resource key from the
|
|
383
|
+
resource's **Keys and Endpoint** page. A Microsoft Entra ID access token is
|
|
384
|
+
rejected—it expires within hours, so the connection would break the same day.
|
|
385
|
+
|
|
386
|
+
**A Claude model returns an error on an Azure OpenAI resource.** Only Foundry
|
|
387
|
+
resources deploy Claude. Confirm the deployment exists and is Anthropic-format,
|
|
388
|
+
and see [Deploying Claude on Foundry](#deploying-claude-on-foundry) for the
|
|
389
|
+
requirements Azure applies to those deployments.
|
|
390
|
+
|
|
391
|
+
**A `400` error names `/v1/responses`.** The gateway doesn't serve the Responses
|
|
392
|
+
API for Azure AI. Use `/v1/chat/completions`.
|
|
393
|
+
|
|
394
|
+
**A model in the picker fails at request time.** The picker lists the full Azure
|
|
395
|
+
catalog, but a model only works once you deploy it in your resource, and model
|
|
396
|
+
availability varies by region and resource kind. Deploy the model, then map your
|
|
397
|
+
deployment name to it.
|
|
398
|
+
|
|
399
|
+
## Next steps
|
|
400
|
+
|
|
401
|
+
- [AI Providers](./providers.mdx)—the capability matrix across every supported
|
|
402
|
+
provider.
|
|
403
|
+
- [Universal API](./universal-api.mdx)—the endpoints every app serves and how
|
|
404
|
+
model references work.
|
|
405
|
+
- [Managing Providers](./managing-providers.mdx)—edit models, keys, and the
|
|
406
|
+
endpoint, and understand when changes deploy.
|
|
407
|
+
- [AI Gateway Apps](./apps.mdx)—create the apps that call your Azure-backed
|
|
408
|
+
models.
|
|
409
|
+
- [Model Filtering policy](../policies/ai-gateway-model-filtering-v2-inbound.mdx)—control
|
|
410
|
+
which models each app can call.
|
|
@@ -53,9 +53,12 @@ To add a new AI provider to your Zuplo AI Gateway, follow these steps:
|
|
|
53
53
|
[Zuplo Demo provider](./providers.mdx#zuplo-demo) asks for no API key—your
|
|
54
54
|
gateway authenticates to the demo service itself,
|
|
55
55
|
[Bedrock Mantle](./bedrock-mantle.mdx) additionally asks for an **AWS
|
|
56
|
-
Region** and accepts only long-term Bedrock API keys,
|
|
56
|
+
Region** and accepts only long-term Bedrock API keys,
|
|
57
57
|
[Vertex AI](./vertex-ai.mdx) asks for a **Location** and a **Google Cloud
|
|
58
|
-
Project ID** and takes a service account JSON key file instead of an API key
|
|
58
|
+
Project ID** and takes a service account JSON key file instead of an API key,
|
|
59
|
+
and [Azure AI](./azure-ai.mdx) asks for an **Azure Resource Name** and a host
|
|
60
|
+
family, plus a mapping for any deployment named differently from the model it
|
|
61
|
+
serves.
|
|
59
62
|
|
|
60
63
|
1. Select the model or models you want to use with this provider. The available
|
|
61
64
|
models will depend on the selected provider. This can be changed later.
|
|
@@ -85,10 +88,11 @@ To modify an existing provider, open
|
|
|
85
88
|
and click the **Edit** button next to the provider you want to modify.
|
|
86
89
|
|
|
87
90
|
You can modify the API key and selected models for the provider—and, for
|
|
88
|
-
[Bedrock Mantle](./bedrock-mantle.mdx), the AWS Region,
|
|
89
|
-
[Vertex AI](./vertex-ai.mdx), the Location and Google Cloud Project ID
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
[Bedrock Mantle](./bedrock-mantle.mdx), the AWS Region, for
|
|
92
|
+
[Vertex AI](./vertex-ai.mdx), the Location and Google Cloud Project ID, or for
|
|
93
|
+
[Azure AI](./azure-ai.mdx), the Azure Resource Name, host family, and deployment
|
|
94
|
+
mappings. The **Provider Name** isn't editable: it's the routing address in
|
|
95
|
+
every `providerName/model` reference, so renaming it would orphan each stored
|
|
92
96
|
reference to this provider. After making your changes, click **Save** to apply
|
|
93
97
|
them.
|
|
94
98
|
|
|
@@ -75,8 +75,9 @@ usage limits still apply.
|
|
|
75
75
|
### Multi-Provider Support
|
|
76
76
|
|
|
77
77
|
Configure multiple LLM providers within a single AI Gateway project. Supported
|
|
78
|
-
providers include OpenAI, Anthropic, Google, Mistral, xAI,
|
|
79
|
-
(through [
|
|
78
|
+
providers include OpenAI, Anthropic, Google, Mistral, xAI, Microsoft Azure
|
|
79
|
+
(through [Azure AI](./azure-ai.mdx)), Amazon Bedrock (through
|
|
80
|
+
[Bedrock Mantle](./bedrock-mantle.mdx)), Google Cloud
|
|
80
81
|
([Vertex AI](./vertex-ai.mdx)), and OpenAI-compatible custom providers. See
|
|
81
82
|
[AI Providers](./providers.mdx) for the full list of providers and supported
|
|
82
83
|
capabilities. Apps reference models as `providerName/model`—for example
|
|
@@ -19,6 +19,8 @@ Zuplo currently supports the following AI providers:
|
|
|
19
19
|
- Google
|
|
20
20
|
- Mistral
|
|
21
21
|
- xAI (Grok)
|
|
22
|
+
- [Azure AI](./azure-ai.mdx)—Azure OpenAI and Microsoft Foundry resources,
|
|
23
|
+
serving Claude and every Foundry model family from your own Azure subscription
|
|
22
24
|
- [Bedrock Mantle](./bedrock-mantle.mdx)—Amazon Bedrock's compatible-APIs
|
|
23
25
|
endpoint, serving Claude models and models from many other vendors
|
|
24
26
|
- [Vertex AI](./vertex-ai.mdx)—Google Cloud's managed model platform, serving
|
|
@@ -36,6 +38,7 @@ The following capabilities are supported across providers:
|
|
|
36
38
|
| Google | ✅ | ✅ | ❌ | ❌ |
|
|
37
39
|
| Mistral | ✅ | ✅ | ❌ | ❌ |
|
|
38
40
|
| xAI | ✅ | ✅ | ❌ | ❌ |
|
|
41
|
+
| Azure AI | ✅ | ✅ | ❌ | ✅ |
|
|
39
42
|
| Bedrock Mantle | ✅ | ❌ | ✅ | ✅ |
|
|
40
43
|
| Vertex AI | ✅ | ✅ | ❌ | ✅ |
|
|
41
44
|
| Zuplo Demo | ✅ | ❌ | ❌ | ❌ |
|
|
@@ -51,6 +54,12 @@ Messages (plus chat completions through translation), while its other models
|
|
|
51
54
|
serve chat completions and—per model—Responses. See
|
|
52
55
|
[Using Bedrock Mantle](./bedrock-mantle.mdx#supported-endpoints-by-model-family).
|
|
53
56
|
|
|
57
|
+
Azure AI's Messages support needs a Microsoft Foundry resource, since Claude
|
|
58
|
+
can't be deployed on an Azure OpenAI resource. Azure also addresses models by
|
|
59
|
+
**deployment name** rather than by published model ID, so a deployment named
|
|
60
|
+
differently from the model it serves needs mapping for the gateway to price it.
|
|
61
|
+
See [Using Azure AI](./azure-ai.mdx#azure-serves-deployments-not-model-names).
|
|
62
|
+
|
|
54
63
|
A custom provider must serve chat completions and embeddings under a `/v1` path
|
|
55
64
|
segment on its API URL, and you enter that URL as an origin root—without the
|
|
56
65
|
`/v1` suffix vendors usually publish. See
|
|
@@ -61,6 +70,32 @@ Apps reference a provider's models as `providerName/model`—for example
|
|
|
61
70
|
name you give the provider configuration. See the
|
|
62
71
|
[Universal API](./universal-api.mdx).
|
|
63
72
|
|
|
73
|
+
## Azure AI
|
|
74
|
+
|
|
75
|
+
**Azure AI** serves models from your own Azure resource. One provider
|
|
76
|
+
configuration covers both kinds of resource, and which capabilities apply
|
|
77
|
+
depends on the resource and the model:
|
|
78
|
+
|
|
79
|
+
- An **Azure OpenAI** resource deploys OpenAI models and serves chat completions
|
|
80
|
+
and embeddings.
|
|
81
|
+
- A **Microsoft Foundry** resource serves those plus every Foundry model
|
|
82
|
+
family—Grok, DeepSeek, Llama, Mistral, Phi, Kimi and more—and serves **Claude
|
|
83
|
+
models** on the native Anthropic Messages API, with chat completions through
|
|
84
|
+
the gateway's translation.
|
|
85
|
+
|
|
86
|
+
Two things make Azure different from every other provider:
|
|
87
|
+
|
|
88
|
+
- **It addresses deployments, not published model IDs.** You name deployments
|
|
89
|
+
when you create them in Azure, and that name is what goes in the `model`
|
|
90
|
+
field. A deployment whose name differs from the model it serves needs a
|
|
91
|
+
mapping, or the gateway can't price its usage.
|
|
92
|
+
- **Its endpoint host embeds your resource name.** The provider dialog asks for
|
|
93
|
+
an **Azure Resource Name** and a host family rather than a full URL, and it
|
|
94
|
+
takes a resource key—not a Microsoft Entra ID token.
|
|
95
|
+
|
|
96
|
+
For prerequisites, setup steps, code examples, and troubleshooting, see
|
|
97
|
+
[Using Azure AI](./azure-ai.mdx).
|
|
98
|
+
|
|
64
99
|
## Bedrock Mantle
|
|
65
100
|
|
|
66
101
|
**Bedrock Mantle** is Amazon Bedrock's compatible-APIs endpoint. One regional
|
|
@@ -63,9 +63,9 @@ list—so clients that can't set a model still work.
|
|
|
63
63
|
|
|
64
64
|
## Supported endpoints
|
|
65
65
|
|
|
66
|
-
| Endpoint | Notes
|
|
67
|
-
| ---------------------- |
|
|
68
|
-
| `/v1/chat/completions` | Chat completions for every provider
|
|
69
|
-
| `/v1/embeddings` | Embeddings for
|
|
70
|
-
| `/v1/responses` | OpenAI Responses API: OpenAI, and [Bedrock Mantle](./bedrock-mantle.mdx) OpenAI-compatible models that serve it
|
|
71
|
-
| `/v1/messages` | Anthropic Messages API: Anthropic, and the Claude models of [Bedrock Mantle](./bedrock-mantle.mdx) and [Vertex AI](./vertex-ai.mdx) |
|
|
66
|
+
| Endpoint | Notes |
|
|
67
|
+
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
68
|
+
| `/v1/chat/completions` | Chat completions for every provider |
|
|
69
|
+
| `/v1/embeddings` | Embeddings for the providers marked in the [capability matrix](./providers.mdx#supported-providers) |
|
|
70
|
+
| `/v1/responses` | OpenAI Responses API: OpenAI, and [Bedrock Mantle](./bedrock-mantle.mdx) OpenAI-compatible models that serve it |
|
|
71
|
+
| `/v1/messages` | Anthropic Messages API: Anthropic, and the Claude models of [Azure AI](./azure-ai.mdx), [Bedrock Mantle](./bedrock-mantle.mdx) and [Vertex AI](./vertex-ai.mdx) |
|
package/docs/policies/_index.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
| --- | --- | --- | --- |
|
|
5
5
|
| set-query-params-inbound | Add or Set Query Parameters | Adds or sets query parameters on the incoming request. | api-gateway |
|
|
6
6
|
| set-headers-inbound | Add or Set Request Headers | Adds or sets headers on the incoming request. | api-gateway |
|
|
7
|
-
| ai-gateway-auth-v2-inbound | AI Gateway Authentication | Authenticates requests to an AI Gateway endpoint with application API keys. Add this policy to an application's `inboundPolicyChain` to require a key for that app only, or place it on the route before the configuration executor to require a key for every application on the route. The policies that follow can read the authenticated application from `request.user` (`sub` is the application name, `data` its metadata), and the application's AI Gateway configuration takes effect for the request. Use `authHeader` and `authScheme` when clients send their app key somewhere other than the default `Authorization: Bearer` header. When the matched route captures an `app_id` path parameter (platform catch-all `/:app_id/(.*)`), this policy also requires `configuration.id === request.params.app_id` and returns 403 on mismatch. | ai-gateway |
|
|
7
|
+
| ai-gateway-auth-v2-inbound | AI Gateway Authentication | Authenticates requests to an AI Gateway endpoint with application API keys. Add this policy to an application's `inboundPolicyChain` to require a key for that app only, or place it on the route before the configuration executor to require a key for every application on the route. The policies that follow can read the authenticated application from `request.user` (`sub` is the application name, `data` its metadata), and the application's AI Gateway configuration takes effect for the request. Use `authHeader` and `authScheme` when clients send their app key somewhere other than the default `Authorization: Bearer` header. Enable `credentialPassthrough`, with the application key in a separate header, to forward the caller's `Authorization` header to the primary provider as its credential. When the matched route captures an `app_id` path parameter (platform catch-all `/:app_id/(.*)`), this policy also requires `configuration.id === request.params.app_id` and returns 403 on mismatch. | ai-gateway |
|
|
8
8
|
| ai-gateway-configuration-executor-v2-inbound | AI Gateway Configuration Executor | Loads the app configuration for the request (when auth or the configuration loader has not already), runs the inbound policy chain from that configuration, and enforces limits inherited from parent teams or the gateway root. Place this policy on AI Gateway routes after optional authentication and optional `ai-gateway-configuration-loader-v2-inbound`. When either of those already populated the app-configuration channel, this policy reuses it. Otherwise it loads the configuration with the route's `app_id` path parameter. Applications select from policies pre-declared by the gateway. Applications without a `inboundPolicyChain`, or with an empty chain, run no application-selected policies. Entry options replace the declaration's options as a complete object; omit them to inherit the declaration, including environment-backed credentials. Each occurrence receives a private deep copy of its entry options, so a policy mutating its options cannot corrupt the cached app configuration. | ai-gateway |
|
|
9
9
|
| ai-gateway-configuration-loader-v2-inbound | AI Gateway Configuration Loader | Loads the AI Gateway app configuration for the request into the request-scoped channel and does nothing else. Place this policy on AI Gateway routes before `ai-gateway-configuration-executor-v2-inbound` when you want configuration loading separated from chain execution. When `ai-gateway-auth-v2-inbound` already populated the channel, this policy reuses it. Otherwise it loads the configuration with the route's `app_id` path parameter. If this policy is omitted, the configuration executor still loads configuration itself before running the application chain. | ai-gateway |
|
|
10
10
|
| ai-gateway-fallback-model-v2-inbound | AI Gateway Fallback Model | Adds failure and quota fallbacks to an existing AI Gateway model selection. Place this policy after AI Gateway Model Filtering. It never creates a model selection, so a misplaced policy cannot bypass filtering. | ai-gateway |
|
|
@@ -147,6 +147,64 @@ curl https://gateway.example.com/v1/chat/completions \
|
|
|
147
147
|
}'
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
+
## Pass through a caller's provider credential
|
|
151
|
+
|
|
152
|
+
Set `credentialPassthrough` to `true` and configure a separate header for the
|
|
153
|
+
Zuplo application key. For a bare key in `zp-gateway-api-key`, use these policy
|
|
154
|
+
options:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"credentialPassthrough": true,
|
|
159
|
+
"authHeader": "zp-gateway-api-key",
|
|
160
|
+
"authScheme": ""
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Send the provider credential in `Authorization`. `authHeader` and `authScheme`
|
|
165
|
+
remain authoritative; sending a special header does not enable passthrough or
|
|
166
|
+
override your configuration. The defaults remain `Authorization` and `Bearer`,
|
|
167
|
+
so enabling passthrough without configuring a separate header is a configuration
|
|
168
|
+
error.
|
|
169
|
+
|
|
170
|
+
For example:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
curl https://gateway.example.com/v1/chat/completions \
|
|
174
|
+
--header "zp-gateway-api-key: YOUR_ZUPLO_APP_KEY" \
|
|
175
|
+
--header "Authorization: Bearer YOUR_PROVIDER_CREDENTIAL" \
|
|
176
|
+
--header "Content-Type: application/json" \
|
|
177
|
+
--data '{
|
|
178
|
+
"model": "provider/model",
|
|
179
|
+
"messages": [{ "role": "user", "content": "Hello" }]
|
|
180
|
+
}'
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
After the Zuplo application key succeeds, the policy moves the provider
|
|
184
|
+
credential into private request-scoped state and removes both authentication
|
|
185
|
+
headers from the mutable inbound request. The primary provider receives the
|
|
186
|
+
original `Authorization` value exactly as supplied, instead of its configured
|
|
187
|
+
credential. Retries of the primary use that same caller credential. Error and
|
|
188
|
+
quota fallbacks use their own configured credentials, never the caller's token.
|
|
189
|
+
Fallbacks therefore require configured credentials and can incur charges on the
|
|
190
|
+
gateway owner's provider account. On native create requests at `/v1/messages`
|
|
191
|
+
and `/v1/responses`, a compatible, explicitly configured backup runs only after
|
|
192
|
+
the primary exhausts upstream 429 retries. It uses the backup's configured
|
|
193
|
+
credential while preserving the native response and stream format; these
|
|
194
|
+
endpoints do not use timeout, transport-error, or 5xx fallback. Semantic-cache
|
|
195
|
+
policies skip passthrough requests so a cache hit cannot bypass upstream
|
|
196
|
+
authentication. ZuploDemo is excluded because it must continue to use the
|
|
197
|
+
gateway's deployment credential.
|
|
198
|
+
|
|
199
|
+
A missing, empty, or invalid dedicated key fails authentication; the gateway
|
|
200
|
+
never retries with `Authorization` as the Zuplo key. A missing or empty
|
|
201
|
+
`Authorization` also returns 401 in this mode.
|
|
202
|
+
|
|
203
|
+
With `credentialPassthrough` omitted or `false`, existing authentication
|
|
204
|
+
applies: `authHeader` and `authScheme` select the Zuplo key, and providers use
|
|
205
|
+
their configured credentials. Moving the gateway key to an arbitrary custom
|
|
206
|
+
header alone does not enable passthrough.
|
|
207
|
+
|
|
150
208
|
## Choose a cache duration
|
|
151
209
|
|
|
152
210
|
`cacheTtlSeconds` controls how long an authentication result can be reused. The
|
|
@@ -163,6 +221,10 @@ A revoked key can continue to work until its cached result expires.
|
|
|
163
221
|
- `cacheTtlSeconds`: Number of seconds to cache an authentication result.
|
|
164
222
|
Defaults to `10` and must be at least `10`.
|
|
165
223
|
- `authHeader`: Header containing the application key. Defaults to
|
|
166
|
-
`Authorization`.
|
|
167
|
-
-
|
|
168
|
-
|
|
224
|
+
`Authorization`. With `credentialPassthrough`, use a different header, such as
|
|
225
|
+
`zp-gateway-api-key`.
|
|
226
|
+
- `authScheme`: Scheme before the key. Defaults to `Bearer`. Use `""` when the
|
|
227
|
+
header contains only the key.
|
|
228
|
+
- `credentialPassthrough`: Forward the caller's `Authorization` header to the
|
|
229
|
+
primary provider as its credential while Zuplo reads the application key from
|
|
230
|
+
`authHeader`. Defaults to `false`.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"requiresAI": true,
|
|
13
13
|
"policyType": "ai-gateway-auth-v2",
|
|
14
14
|
"products": ["ai-gateway"],
|
|
15
|
-
"description": "Authenticates requests to an AI Gateway endpoint with application API keys.\n\nAdd this policy to an application's `inboundPolicyChain` to require a key for that app only, or place it on the route before the configuration executor to require a key for every application on the route. The policies that follow can read the authenticated application from `request.user` (`sub` is the application name, `data` its metadata), and the application's AI Gateway configuration takes effect for the request. Use `authHeader` and `authScheme` when clients send their app key somewhere other than the default `Authorization: Bearer` header.\n\nWhen the matched route captures an `app_id` path parameter (platform catch-all `/:app_id/(.*)`), this policy also requires `configuration.id === request.params.app_id` and returns 403 on mismatch.",
|
|
15
|
+
"description": "Authenticates requests to an AI Gateway endpoint with application API keys.\n\nAdd this policy to an application's `inboundPolicyChain` to require a key for that app only, or place it on the route before the configuration executor to require a key for every application on the route. The policies that follow can read the authenticated application from `request.user` (`sub` is the application name, `data` its metadata), and the application's AI Gateway configuration takes effect for the request. Use `authHeader` and `authScheme` when clients send their app key somewhere other than the default `Authorization: Bearer` header. Enable `credentialPassthrough`, with the application key in a separate header, to forward the caller's `Authorization` header to the primary provider as its credential.\n\nWhen the matched route captures an `app_id` path parameter (platform catch-all `/:app_id/(.*)`), this policy also requires `configuration.id === request.params.app_id` and returns 403 on mismatch.",
|
|
16
16
|
"deprecatedMessage": "",
|
|
17
17
|
"required": ["handler"],
|
|
18
18
|
"properties": {
|
|
@@ -36,6 +36,25 @@
|
|
|
36
36
|
"description": "The options for this policy.",
|
|
37
37
|
"additionalProperties": false,
|
|
38
38
|
"required": [],
|
|
39
|
+
"if": {
|
|
40
|
+
"required": ["credentialPassthrough"],
|
|
41
|
+
"properties": {
|
|
42
|
+
"credentialPassthrough": {
|
|
43
|
+
"const": true
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"then": {
|
|
48
|
+
"required": ["authHeader"],
|
|
49
|
+
"properties": {
|
|
50
|
+
"authHeader": {
|
|
51
|
+
"minLength": 1,
|
|
52
|
+
"not": {
|
|
53
|
+
"pattern": "^\\s*[Aa][Uu][Tt][Hh][Oo][Rr][Ii][Zz][Aa][Tt][Ii][Oo][Nn]\\s*$"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
39
58
|
"properties": {
|
|
40
59
|
"cacheTtlSeconds": {
|
|
41
60
|
"type": "number",
|
|
@@ -48,14 +67,19 @@
|
|
|
48
67
|
"default": "Authorization",
|
|
49
68
|
"x-show-example": false,
|
|
50
69
|
"x-advanced": true,
|
|
51
|
-
"description": "The
|
|
70
|
+
"description": "The request header that carries the Zuplo application key. When credentialPassthrough is enabled, use a header other than Authorization, such as zp-gateway-api-key."
|
|
52
71
|
},
|
|
53
72
|
"authScheme": {
|
|
54
73
|
"type": "string",
|
|
55
74
|
"default": "Bearer",
|
|
56
75
|
"x-show-example": false,
|
|
57
76
|
"x-advanced": true,
|
|
58
|
-
"description": "The scheme
|
|
77
|
+
"description": "The scheme that prefixes the key in the authHeader header, for example Bearer. Set to an empty string when the header contains only the key."
|
|
78
|
+
},
|
|
79
|
+
"credentialPassthrough": {
|
|
80
|
+
"type": "boolean",
|
|
81
|
+
"default": false,
|
|
82
|
+
"description": "Forward the caller's Authorization header to the primary provider as its credential. Zuplo reads the application key from authHeader, which must be a header other than Authorization."
|
|
59
83
|
}
|
|
60
84
|
}
|
|
61
85
|
}
|
|
@@ -65,7 +89,8 @@
|
|
|
65
89
|
"export": "AIGatewayAuthV2InboundPolicy",
|
|
66
90
|
"module": "$import(@zuplo/runtime)",
|
|
67
91
|
"options": {
|
|
68
|
-
"cacheTtlSeconds": 10
|
|
92
|
+
"cacheTtlSeconds": 10,
|
|
93
|
+
"credentialPassthrough": false
|
|
69
94
|
}
|
|
70
95
|
}
|
|
71
96
|
]
|
|
@@ -27,13 +27,38 @@ The gateway owner remains in control:
|
|
|
27
27
|
| `inboundPolicyChain` contains entries | Entries run in the listed order |
|
|
28
28
|
| An entry has `enabled: false` | That entry is skipped |
|
|
29
29
|
|
|
30
|
-
If a policy returns a response,
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
If a policy returns a response, later entries do not run. Successful 2xx
|
|
31
|
+
responses still receive the budget check described below before they are sent.
|
|
32
|
+
Other responses, including authentication failures, remain unchanged. If a chain
|
|
33
|
+
is invalid, the request fails closed with an error that identifies the entry to
|
|
34
|
+
fix.
|
|
33
35
|
|
|
34
36
|
This inbound executor does not run `outboundPolicyChain`. That field is reserved
|
|
35
37
|
for an outbound configuration executor on the response pipeline.
|
|
36
38
|
|
|
39
|
+
## Automatic budget enforcement
|
|
40
|
+
|
|
41
|
+
The executor enforces budgets configured on the application, its parent teams,
|
|
42
|
+
and the gateway. This does not require a Metering policy and applies even when
|
|
43
|
+
the application's policy chain is empty. An application cannot override a shared
|
|
44
|
+
budget with a higher limit of its own.
|
|
45
|
+
|
|
46
|
+
Request logs indicate when automatic budget checks run, including when the
|
|
47
|
+
application has no Metering policy. Warnings identify exceeded budgets, blocked
|
|
48
|
+
requests, fallback decisions, and checks that could not be completed.
|
|
49
|
+
|
|
50
|
+
When a budget blocks a provider request, the gateway uses the configured quota
|
|
51
|
+
fallback model or returns `429 Too Many Requests`. Successful responses from
|
|
52
|
+
policies, including Semantic Cache hits, are also subject to budgets. A blocked
|
|
53
|
+
response returns `429 Too Many Requests` without using a fallback model.
|
|
54
|
+
Warning-only budgets do not block responses. Authentication failures and other
|
|
55
|
+
non-2xx policy responses are preserved.
|
|
56
|
+
|
|
57
|
+
Place Metering before Semantic Cache to count cache hits toward request limits.
|
|
58
|
+
Budget changes can take time to take effect, and concurrent requests can exceed
|
|
59
|
+
a budget before further requests are blocked. If a budget check is unavailable,
|
|
60
|
+
requests can proceed unless a budget has already been determined to block them.
|
|
61
|
+
|
|
37
62
|
## Build an AI Gateway from scratch
|
|
38
63
|
|
|
39
64
|
### 1. Declare the policies
|
|
@@ -171,8 +196,7 @@ chain:
|
|
|
171
196
|
}
|
|
172
197
|
```
|
|
173
198
|
|
|
174
|
-
|
|
175
|
-
configuration when the channel is empty.
|
|
199
|
+
If you omit the loader, the executor loads the application configuration.
|
|
176
200
|
|
|
177
201
|
Authentication is optional and placement controls its scope:
|
|
178
202
|
|
|
@@ -205,14 +229,19 @@ An application can inherit the options from `policies.json`. Include
|
|
|
205
229
|
{
|
|
206
230
|
"name": "ai-gateway-metering-v2-inbound",
|
|
207
231
|
"options": {
|
|
208
|
-
"
|
|
209
|
-
|
|
210
|
-
"
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
232
|
+
"budgetRules": [
|
|
233
|
+
{
|
|
234
|
+
"budgetBy": "app",
|
|
235
|
+
"meters": [
|
|
236
|
+
{
|
|
237
|
+
"meter": "requests",
|
|
238
|
+
"period": "daily",
|
|
239
|
+
"value": 1000,
|
|
240
|
+
"action": "block"
|
|
241
|
+
}
|
|
242
|
+
]
|
|
214
243
|
}
|
|
215
|
-
|
|
244
|
+
]
|
|
216
245
|
}
|
|
217
246
|
},
|
|
218
247
|
{
|
|
@@ -347,10 +376,10 @@ canonical forms when writing application configuration:
|
|
|
347
376
|
| A key requiring brackets | `request.user.data["team-id"]` |
|
|
348
377
|
|
|
349
378
|
The `expression` field contains the complete expression. Do not wrap it in an
|
|
350
|
-
interpolation marker such as `${...}`.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
redeploying the gateway.
|
|
379
|
+
interpolation marker such as `${...}`. Expressions use values from the request
|
|
380
|
+
and context, including values set by earlier policies. Place policies that set
|
|
381
|
+
these values before Semantic Cache. You can change an expression in the
|
|
382
|
+
application configuration without rebuilding or redeploying the gateway.
|
|
354
383
|
|
|
355
384
|
JSON encoding and expression syntax are separate. When writing raw JSON, escape
|
|
356
385
|
the double quotes required by a bracket segment:
|
|
@@ -393,10 +422,8 @@ The selectable data model is:
|
|
|
393
422
|
| `request` | `url`, `method`, `headers`, `user.sub`, `user.data`, `query`, `searchParams`, `params`, and the scalar Fetch request metadata `bodyUsed`, `cache`, `credentials`, `destination`, `integrity`, `keepalive`, `mode`, `redirect`, `referrer`, and `referrerPolicy` |
|
|
394
423
|
| `context` | `contextId`, `requestId`, `custom`, `route`, and `incomingRequestProperties` |
|
|
395
424
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
plain-data snapshot of the selected root. It never receives the live request or
|
|
399
|
-
context object.
|
|
425
|
+
Expressions cannot read the request body, call logging or policy methods, or
|
|
426
|
+
access properties outside this table.
|
|
400
427
|
|
|
401
428
|
Expressions have these limits:
|
|
402
429
|
|
|
@@ -413,9 +440,8 @@ Expressions have these limits:
|
|
|
413
440
|
It does not traverse arrays, class instances, inherited properties, or the
|
|
414
441
|
property names `__proto__`, `constructor`, and `prototype`.
|
|
415
442
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
earlier in the chain can derive a value and place it in a request header:
|
|
443
|
+
An authentication policy can populate `request.user`. A custom policy earlier in
|
|
444
|
+
the chain can derive a value and place it in a request header:
|
|
419
445
|
|
|
420
446
|
```ts
|
|
421
447
|
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
|
|
@@ -446,14 +472,12 @@ value to well-formed NFC Unicode and ignores it when it exceeds 256 UTF-8 bytes
|
|
|
446
472
|
or contains control characters, U+2028, or U+2029. A missing or invalid value
|
|
447
473
|
does not contribute to the rule for that request.
|
|
448
474
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
same value.
|
|
475
|
+
Use consistent spelling for each expression. Different spellings, such as single
|
|
476
|
+
quotes and double quotes, identify different budget rules even when they select
|
|
477
|
+
the same value.
|
|
453
478
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
other valid rules continue to run.
|
|
479
|
+
Invalid budget rules and unsupported expressions are logged and skipped. Other
|
|
480
|
+
valid rules continue to apply.
|
|
457
481
|
|
|
458
482
|
## Write a custom policy for the chain
|
|
459
483
|
|
|
@@ -70,9 +70,12 @@ not override. A configured `fallback` replaces an existing backup and timeout; a
|
|
|
70
70
|
configured `quotaFallback` replaces an existing quota fallback.
|
|
71
71
|
|
|
72
72
|
Retry and timeout fallback is available for translated Chat Completions and
|
|
73
|
-
Embeddings requests. Native `/v1/messages` and
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
Embeddings requests. Native create requests at `/v1/messages` and
|
|
74
|
+
`/v1/responses` use a compatible completions backup only after the primary
|
|
75
|
+
exhausts its upstream 429 retries; they do not inherit `fallbackTimeoutSeconds`
|
|
76
|
+
or transport/5xx fallback. Responses management operations do not retry a backup
|
|
77
|
+
because stored response IDs belong to the provider that created them. Quota
|
|
78
|
+
fallback remains a separate, metering-driven path.
|
|
76
79
|
|
|
77
80
|
## Write your own fallback policy
|
|
78
81
|
|
|
@@ -5,13 +5,19 @@ budgets before the provider request runs. It meters spend, tokens, and requests.
|
|
|
5
5
|
Budget rules can cover the whole application or each distinct value of an
|
|
6
6
|
expression.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
returns `429 Too Many Requests`.
|
|
8
|
+
Application, team, and gateway budgets are enforced even when this policy is not
|
|
9
|
+
included in the application's policy chain.
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
Request logs indicate when automatic budget checks run and when a budget blocks
|
|
12
|
+
a request or selects a fallback model.
|
|
13
|
+
|
|
14
|
+
When a budget blocks a provider request, the gateway uses the configured quota
|
|
15
|
+
fallback model or returns `429 Too Many Requests`. Cached responses are also
|
|
16
|
+
subject to budgets: a blocked cache hit returns `429 Too Many Requests` without
|
|
17
|
+
using a fallback model.
|
|
18
|
+
|
|
19
|
+
Place Metering after Model Filtering and Fallback Model. Place it before
|
|
20
|
+
Semantic Cache so cache hits count toward request limits.
|
|
15
21
|
|
|
16
22
|
## Example
|
|
17
23
|
|
|
@@ -62,19 +68,26 @@ value of its expression. An action of `"warn"` notifies without blocking. An
|
|
|
62
68
|
action of `"block"` activates the configured quota fallback or returns
|
|
63
69
|
`429 Too Many Requests` when usage reaches the value.
|
|
64
70
|
|
|
65
|
-
> **Budgets fail open by default.** When `throwOnFailure` is `false`, a metering
|
|
66
|
-
> service failure lets the request proceed unmetered and no limit is checked.
|
|
67
|
-
> Set it to `true` to reject the request instead.
|
|
68
|
-
|
|
69
71
|
## Team limits
|
|
70
72
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
Application budgets apply only to that application. Parent team and gateway
|
|
74
|
+
budgets apply across their applications, even when an application does not
|
|
75
|
+
include Metering. An application cannot override a shared budget with a higher
|
|
76
|
+
limit of its own.
|
|
77
|
+
|
|
78
|
+
For example, two applications that spend $40 and $10 exhaust their team's $50
|
|
79
|
+
monthly budget. An application outside that team does not share its budget.
|
|
80
|
+
|
|
81
|
+
Team policy templates provide defaults for application policies. To set a shared
|
|
82
|
+
team budget, configure the budget on the team itself.
|
|
83
|
+
|
|
84
|
+
## Budget availability
|
|
85
|
+
|
|
86
|
+
Budget changes can take time to take effect. Usage is recorded asynchronously,
|
|
87
|
+
so concurrent requests can exceed a budget before further requests are blocked.
|
|
76
88
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
policy
|
|
80
|
-
|
|
89
|
+
If a budget check is unavailable, requests can proceed unless a budget has
|
|
90
|
+
already been determined to block them. The `throwOnFailure` option controls
|
|
91
|
+
whether this policy rejects requests when its metering operations fail; it does
|
|
92
|
+
not change how application, team, or gateway `budgetRules` handle unavailable
|
|
93
|
+
checks.
|
|
@@ -253,3 +253,58 @@ Policy order determines precedence:
|
|
|
253
253
|
|
|
254
254
|
Prefer one policy as the primary selector so the route's intent is easy to
|
|
255
255
|
understand.
|
|
256
|
+
|
|
257
|
+
## Model discovery
|
|
258
|
+
|
|
259
|
+
Authenticated `GET /v1/models` and `GET /v1/models/{providerName/model}` use the
|
|
260
|
+
same app URL as inference. Select the inference endpoint with the optional
|
|
261
|
+
`x-zuplo-models-endpoint` request header:
|
|
262
|
+
|
|
263
|
+
| Value | Eligible models | Response format |
|
|
264
|
+
| ------------------ | ----------------------------------------------------------------------------------------------- | --------------- |
|
|
265
|
+
| `chat-completions` | Active completion models supported by Chat Completions adapters, including existing translation | OpenAI |
|
|
266
|
+
| `messages` | Active completion models supported by native Messages passthrough | Anthropic |
|
|
267
|
+
| `responses` | Active completion models supported by the resolved Responses adapter | OpenAI |
|
|
268
|
+
| `embeddings` | Active embedding models supported by the resolved embedding adapter | OpenAI |
|
|
269
|
+
|
|
270
|
+
An explicit header wins. Otherwise, the presence of `anthropic-version` selects
|
|
271
|
+
`messages`; other requests default to `chat-completions`. Empty, unknown, and
|
|
272
|
+
multiple selector values return an actionable 400. The selector affects
|
|
273
|
+
discovery only. Embedding discovery is opt-in. Messages never translates to Chat
|
|
274
|
+
Completions.
|
|
275
|
+
|
|
276
|
+
Discovery applies the first Model Filtering policy's parsed rules, matching
|
|
277
|
+
inference's selection precedence. A capability omitted from that policy lists no
|
|
278
|
+
models. Without Model Filtering, discovery lists the eligible catalog. Inactive
|
|
279
|
+
models, unavailable adapters, and assignments without locally resolvable
|
|
280
|
+
credentials are excluded. List and retrieve use identical eligibility rules;
|
|
281
|
+
unknown, filtered-out, and wrong-endpoint IDs return the same 404.
|
|
282
|
+
|
|
283
|
+
IDs retain the exact model-name casing; only the provider assignment label is
|
|
284
|
+
normalized. OpenAI entries contain `id`, `object`, `created`, and `owned_by`.
|
|
285
|
+
`owned_by` means the provider assignment namespace, not the model's developer.
|
|
286
|
+
Anthropic entries contain `id`, `type`, `display_name`, and `created_at`, with
|
|
287
|
+
unknown required nullable metadata set to `null`. Unknown creation dates use the
|
|
288
|
+
epoch; optional unknown metadata is omitted.
|
|
289
|
+
|
|
290
|
+
OpenAI lists return every eligible model and ignore pagination parameters.
|
|
291
|
+
Anthropic lists default to 20 entries, accept `limit` from 1–1000, and accept
|
|
292
|
+
either `after_id` or `before_id` from a previous page. Follow `has_more` and
|
|
293
|
+
`last_id` for forward pagination. Invalid cursors return 400; restart without a
|
|
294
|
+
cursor.
|
|
295
|
+
|
|
296
|
+
Discovery uses the existing 60-second catalog cache. An uncached lookup,
|
|
297
|
+
including legacy fallback, has a two-second budget; failure returns a
|
|
298
|
+
sanitized 503. A successfully loaded catalog with no eligible models returns an
|
|
299
|
+
empty 200. Responses use `Cache-Control: private, no-store` and vary on both
|
|
300
|
+
selector headers. Only GET is supported. POST, PUT, PATCH, DELETE, and HEAD
|
|
301
|
+
return 405 with `Allow: GET`. OPTIONS is handled by the gateway’s existing CORS
|
|
302
|
+
preflight handler before app routing.
|
|
303
|
+
|
|
304
|
+
Authentication and general abuse protections still apply. Discovery skips prompt
|
|
305
|
+
inspection, semantic caching, and inference-budget blocking, and records one
|
|
306
|
+
baseline request. Eligibility describes the catalog, adapters, and declared
|
|
307
|
+
filtering rules; it does not guarantee upstream availability, provider
|
|
308
|
+
acceptance of credentials, or acceptance by prompt-dependent custom policies.
|
|
309
|
+
Existing Chat Completions translation does not promise full feature parity. Keep
|
|
310
|
+
a qualified model configured when a client's picker does not consume discovery.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zuplo",
|
|
3
|
-
"version": "7.7.
|
|
3
|
+
"version": "7.7.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The official Zuplo CLI for local development and platform management",
|
|
6
6
|
"homepage": "https://zuplo.com/docs/cli/overview",
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"zuplo": "zuplo.js"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@zuplo/cli": "7.7.
|
|
36
|
-
"@zuplo/core": "7.7.
|
|
37
|
-
"@zuplo/runtime": "7.7.
|
|
38
|
-
"@zuplo/test": "7.7.
|
|
35
|
+
"@zuplo/cli": "7.7.9",
|
|
36
|
+
"@zuplo/core": "7.7.9",
|
|
37
|
+
"@zuplo/runtime": "7.7.9",
|
|
38
|
+
"@zuplo/test": "7.7.9"
|
|
39
39
|
}
|
|
40
40
|
}
|