zuplo 7.9.18 → 7.9.20

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,522 @@
1
+ Use this policy to spread requests across several deployments of a model to
2
+ combine their rate limits, across providers that serve the same model, or across
3
+ different models. List the models, and every request the policy handles goes to
4
+ one of them.
5
+
6
+ ## How it works
7
+
8
+ For each request, the policy:
9
+
10
+ 1. Takes the list for the request's type: `completions` for chat completions,
11
+ Responses, Messages, and System One, or `embeddings` for embeddings.
12
+ 2. Drops models that can't serve the request's endpoint, such as an OpenAI model
13
+ on `/v1/messages`. The policy decides this by the provider and by whether the
14
+ model is Claude, not by which APIs each model serves. See
15
+ [Endpoints](#endpoints).
16
+ 3. Picks a model with the `strategy`, plus a retry model: the model the strategy
17
+ would pick next.
18
+ 4. Hands both to the AI Gateway. The gateway calls the chosen model, and moves
19
+ to the retry model when that call fails in a way the endpoint retries. See
20
+ [Failures and retries](#failures-and-retries).
21
+
22
+ The gateway sends the policy's choice to the provider in place of the request's
23
+ `model`. Clients still send a `model`, because most SDKs require one, but any
24
+ value works. The request itself keeps the client's `model`, so policies that run
25
+ later, such as Semantic Cache, still see that value. Because the policy picks
26
+ the model for every request it handles, the list also limits which models
27
+ clients can reach.
28
+
29
+ If an earlier policy already chose a model for the request, this policy leaves
30
+ that choice alone. Requests of a type with no list, such as embeddings when you
31
+ set only `completions`, pass through unchanged.
32
+
33
+ ## Choose a strategy
34
+
35
+ | Strategy | How it picks a model | Use it to |
36
+ | ------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
37
+ | `round-robin` | Cycles through the list in order. | Spread load evenly across deployments, accounts, or providers. |
38
+ | `sticky` | Sends every request with the same key to the same model. The key is the caller, the conversation, or a value you select. | Keep each caller or end user on one model, or keep each conversation on the account whose prompt cache already holds it. |
39
+
40
+ Here is how four requests could go with the example configuration, on one
41
+ gateway instance:
42
+
43
+ | Request | Caller | `round-robin` | `sticky` by `user` |
44
+ | ------- | ------- | -------------------- | -------------------- |
45
+ | 1 | `app-a` | `azure-eastus/gpt-5` | `azure-eastus/gpt-5` |
46
+ | 2 | `app-b` | `azure-westus/gpt-5` | `openai/gpt-5` |
47
+ | 3 | `app-a` | `azure-westus/gpt-5` | `azure-eastus/gpt-5` |
48
+ | 4 | `app-a` | `openai/gpt-5` | `azure-eastus/gpt-5` |
49
+
50
+ Round robin moves each app to the next model on each of its requests. Sticky
51
+ keeps `app-a` on `azure-eastus/gpt-5`, whichever gateway instance serves it.
52
+
53
+ ## Examples
54
+
55
+ ### Serve one model from several providers
56
+
57
+ Claude Sonnet is available directly from Anthropic and through Amazon Bedrock.
58
+ Cycle between them to use both quotas. Use a Bedrock Mantle provider for the
59
+ Bedrock entry, because the policy skips models on Bedrock Runtime providers:
60
+
61
+ ```json
62
+ {
63
+ "models": {
64
+ "completions": [
65
+ "anthropic/claude-sonnet-4-6",
66
+ "bedrockmantle/anthropic.claude-sonnet-4-6"
67
+ ]
68
+ }
69
+ }
70
+ ```
71
+
72
+ ### Keep conversations on one deployment
73
+
74
+ Providers cache the prompt prefixes they've already processed, and cached input
75
+ tokens cost less and return faster. Each account or deployment has its own
76
+ cache, so a conversation that moves between deployments loses the benefit:
77
+
78
+ ```json
79
+ {
80
+ "strategy": "sticky",
81
+ "stickyBy": "conversation",
82
+ "models": {
83
+ "completions": [
84
+ "azure-eastus/gpt-5",
85
+ "azure-westus/gpt-5",
86
+ "azure-swedencentral/gpt-5"
87
+ ]
88
+ }
89
+ }
90
+ ```
91
+
92
+ This works for clients that send the whole conversation on every turn, as Chat
93
+ Completions clients do. A Responses API client that continues a stored response
94
+ with `previous_response_id` sends only the new turn, so with this list, its
95
+ requests to read or continue a response fail. Use sticky routing by `user` or
96
+ `expression` for those clients. See [Stored responses](#stored-responses).
97
+
98
+ ### Keep each customer on one model
99
+
100
+ When each of your customers calls through its own app, sticky routing by user
101
+ keeps every customer on one model while customers spread across the list:
102
+
103
+ ```json
104
+ {
105
+ "strategy": "sticky",
106
+ "stickyBy": "user",
107
+ "models": {
108
+ "completions": ["openai/gpt-5", "anthropic/claude-sonnet-4-6"]
109
+ }
110
+ }
111
+ ```
112
+
113
+ ### Balance embeddings
114
+
115
+ ```json
116
+ {
117
+ "models": {
118
+ "embeddings": [
119
+ "openai/text-embedding-3-large",
120
+ "azure-eastus/text-embedding-3-large"
121
+ ]
122
+ }
123
+ }
124
+ ```
125
+
126
+ :::caution Use one embedding model per list
127
+
128
+ Every entry in the `embeddings` list must be the same embedding model. Vectors
129
+ from different models can't be compared, so mixing models breaks similarity
130
+ search.
131
+
132
+ :::
133
+
134
+ ## Round robin
135
+
136
+ Round robin gives every model in the list an equal share of requests. Each
137
+ gateway instance keeps a separate place in the list for each app, starting from
138
+ a random point. Instances never have to coordinate, and a busy app can't skew a
139
+ quiet app's rotation. Across all instances, requests spread evenly, but two
140
+ requests in a row can still go to the same model.
141
+
142
+ ## Sticky routing
143
+
144
+ Sticky routing uses a hash, not a stored mapping, so it gives the same answer on
145
+ every gateway instance. For each request, the policy scores every model by
146
+ hashing the sticky key together with the model name, and picks the model with
147
+ the highest score. The same key always produces the same scores, so it always
148
+ gets the same model.
149
+
150
+ | Model | Score for `app-a` | Result |
151
+ | -------------------- | ----------------- | ----------- |
152
+ | `azure-eastus/gpt-5` | 0.74 | Chosen |
153
+ | `azure-westus/gpt-5` | 0.72 | Retry model |
154
+ | `openai/gpt-5` | 0.25 | |
155
+
156
+ - The retry model is the one with the second-highest score, so it's stable too.
157
+ - Adding a model moves only the keys it now wins. Removing a model moves only
158
+ the keys it was winning. Every other key stays where it was.
159
+
160
+ Requests without a key go round robin:
161
+
162
+ | `stickyBy` | A request has no key when |
163
+ | -------------- | ------------------------------------------------------------------------------- |
164
+ | `user` | It has no authenticated user |
165
+ | `conversation` | It has no user message with text, such as an embeddings or System One request |
166
+ | `expression` | The expression resolves to nothing, to an empty string, or to an unusable value |
167
+
168
+ Stored-response requests don't go round robin when the models that store
169
+ responses have more than one provider name. They fail with `400` unless a `user`
170
+ or `expression` key picks their model, so under `conversation` they fail even
171
+ with a key. See [Stored responses](#stored-responses).
172
+
173
+ ### Sticky by user
174
+
175
+ `"stickyBy": "user"` keys on `request.user.sub`, the identity your
176
+ authentication policy sets. It works like an expression of `request.user.sub`.
177
+
178
+ :::note With AI Gateway API keys, the user is the app
179
+
180
+ AI Gateway Authentication sets `request.user.sub` to the app's name. Sticky
181
+ routing by user then keeps each app on one model, and spreads load only when
182
+ many apps share the policy. To keep each end user on one model, have the app
183
+ send an end-user ID and select it with an [expression](#sticky-by-expression).
184
+ On User App routes, where callers use personal API keys, `request.user.sub` is
185
+ the key owner's user ID, so each person stays on one model.
186
+
187
+ :::
188
+
189
+ ### Sticky by conversation
190
+
191
+ `"stickyBy": "conversation"` keys on the conversation's system prompt and its
192
+ first user message with text. A client that sends the whole conversation on
193
+ every turn, as Chat Completions and Messages clients do, sends the same key on
194
+ every turn. On the Responses API, the system prompt is `instructions` plus any
195
+ system or developer items before the first user message. Keeping a conversation
196
+ on one model keeps it on the account whose prompt cache already holds its
197
+ earlier turns.
198
+
199
+ Only text counts, including plain-text documents. Conversations that open with
200
+ the same system prompt and the same first message share a model, and images and
201
+ other files don't make them different. A conversation that opens with an image
202
+ on its own goes round robin until a turn adds text. If your clients open every
203
+ conversation the same way, send a conversation ID in a header and key on it with
204
+ an [expression](#sticky-by-expression) instead.
205
+
206
+ A Responses API request that continues a stored response with
207
+ `previous_response_id` or `conversation` sends only the new turn, so its key
208
+ changes on every turn. See [Stored responses](#stored-responses).
209
+
210
+ The policy hashes this text in memory. It never stores or logs it.
211
+
212
+ ### Sticky by expression
213
+
214
+ `"stickyBy": "expression"` keys on one value that `expression` selects from the
215
+ request or its context. Expressions use the same syntax as
216
+ [Metering budget expressions](/docs/policies/ai-gateway-metering-inbound#budget-expressions),
217
+ so a value you budget on can also keep requests on one model. For example, to
218
+ keep each end user on one model when your app sends an end-user ID in a header:
219
+
220
+ ```json
221
+ {
222
+ "strategy": "sticky",
223
+ "stickyBy": "expression",
224
+ "expression": "request.headers.get(\"x-end-user-id\")",
225
+ "models": {
226
+ "completions": ["azure-eastus/gpt-5", "azure-westus/gpt-5"]
227
+ }
228
+ }
229
+ ```
230
+
231
+ In JSON, escape the double quotes inside an expression, as the example does.
232
+ Common expressions:
233
+
234
+ | Expression | Selects |
235
+ | ------------------------------- | --------------------------------------------------------- |
236
+ | `request.headers.get("<name>")` | One request header, such as an end-user ID your app sends |
237
+ | `request.user.data.<property>` | A property of the caller's metadata |
238
+ | `request.params.<name>` | A path parameter from the matched route |
239
+ | `context.custom.<property>` | A value your own policy puts on `context.custom` |
240
+
241
+ See
242
+ [Supported expressions](/docs/policies/ai-gateway-metering-inbound#supported-expressions)
243
+ in the Metering docs for the full list. An expression is a selector, not code,
244
+ so it can't compare values, call string methods, or read the request body.
245
+
246
+ - The value follows Metering's value rules. It must be a string or a safe
247
+ integer, and `-0` doesn't count. The gateway converts a string to well-formed
248
+ NFC Unicode, then rejects it when it's longer than 256 UTF-8 bytes or contains
249
+ a control character, U+2028, or U+2029.
250
+ - Values are case-sensitive, so `Acme` and `acme` can land on different models.
251
+ Two strings that normalize to the same NFC text share a model.
252
+ - When the expression resolves to nothing, to an empty string, or to a value
253
+ these rules reject, such as an object, a fractional number, or an overlong
254
+ string, the request goes round robin. A stored-response request fails with
255
+ `400` instead when the models that store responses have more than one provider
256
+ name. The request log says why.
257
+ - Per-request identifiers, such as `context.requestId`, aren't allowed, because
258
+ a key that never repeats can't keep requests together.
259
+ - An expression outside the syntax is an options error, and the error shows
260
+ where parsing stopped.
261
+
262
+ The policy hashes the value in memory. It never stores or logs it.
263
+
264
+ ## Failures and retries
265
+
266
+ Each request gets a retry model, except stored-response requests,
267
+ `/v1/messages/count_tokens` requests, `/v1/systemone` requests, and requests
268
+ that only one model in the list can serve. The AI Gateway treats the retry model
269
+ like any other backup model. When it moves there depends on the endpoint:
270
+
271
+ | When the chosen model | Chat completions and embeddings | Responses and Messages |
272
+ | ------------------------------------------------------------ | ----------------------------------------------- | ----------------------------------------------- |
273
+ | Returns `429` | Retries it twice, then moves to the retry model | Retries it twice, then moves to the retry model |
274
+ | Returns a `5xx`, `408`, or `425` status, or can't be reached | Moves to the retry model | Fails the request |
275
+ | Doesn't start responding within `fallbackTimeoutSeconds` | Moves to the retry model | Keeps waiting |
276
+ | Returns any other error, such as `400` or `401` | Fails the request | Fails the request |
277
+
278
+ Responses and Messages requests use the provider's native API. On those
279
+ endpoints the AI Gateway moves to another model only for rate limits, as it does
280
+ without this policy.
281
+
282
+ - Each request tries at most two models. The gateway retries a rate-limited
283
+ model twice, after 250 ms and then 500 ms. If every attempt returns `429`, the
284
+ request makes six calls, three to each model, before the gateway returns
285
+ `429`.
286
+ - Once a model starts streaming a response, the request stays on that model.
287
+ - A request that doesn't stream gets its response only when the model finishes,
288
+ so on chat completions and embeddings `fallbackTimeoutSeconds` limits the
289
+ whole response. A model that's still working when it runs out moves the
290
+ request to the retry model, which starts over with twice the time, and the
291
+ request fails if that runs out too. Raise `fallbackTimeoutSeconds` for slow
292
+ models, such as reasoning models, or stream the response. When Fallback Model
293
+ runs after this policy with a `fallback`, raise its `fallbackTimeoutSeconds`
294
+ too. Its timeout replaces this policy's on requests that get its `fallback`.
295
+ See [With Fallback Model](#with-fallback-model).
296
+ - When only one model in the list can serve a request, this policy sets no retry
297
+ model and no timeout. On `/v1/responses` and `/v1/messages`, a list that mixes
298
+ providers or vendors often has only one, so list at least two models for each
299
+ API your clients use. Fallback Model can also add a backup. See
300
+ [With Fallback Model](#with-fallback-model).
301
+ - The policy doesn't remember failures between requests. While a model is down,
302
+ every request that picks it tries it first: chat completions and embeddings
303
+ requests then move to the retry model, and Responses and Messages requests
304
+ fail. With `sticky` routing, the same callers pick the failing model every
305
+ time.
306
+
307
+ ## Endpoints
308
+
309
+ | Endpoint | Behavior |
310
+ | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
311
+ | `/v1/chat/completions` | Models the AI Gateway sends Chat Completions requests to are eligible, and the gateway translates the request for each provider. System One models are skipped. Claude on Vertex AI isn't skipped, and requests that pick it fail with `400`. |
312
+ | `/v1/responses` | Only models the AI Gateway sends Responses API requests to are eligible: OpenAI models, Azure AI and OpenRouter models other than Claude, and Bedrock Mantle models other than Claude. Other providers are skipped, even when their own API serves the Responses API. |
313
+ | `/v1/messages`, `/v1/messages/count_tokens` | Only models the AI Gateway sends Anthropic Messages API requests to are eligible: Anthropic models, and Claude on Azure AI, Bedrock Mantle, Vertex AI, and OpenRouter. `count_tokens` also skips providers that don't count tokens, such as OpenRouter. |
314
+ | `/v1/systemone` | Only TypeSafe System One (Jev) models are eligible, so you can balance several Jev models or TypeSafe accounts. Jev through OpenRouter isn't eligible. These requests get no retry model. |
315
+ | `/v1/embeddings` | Uses the `embeddings` list. |
316
+ | `GET` or `DELETE /v1/responses/{id}`, and `GET /v1/responses/{id}/input_items` | Routed to the account that holds the response, when the policy can tell which one it is. See [Stored responses](#stored-responses). |
317
+ | `GET /v1/models` | Passes through unchanged. |
318
+ | Bedrock Runtime (`/model/{modelId}/…`) | Not supported. An app whose policy chain includes this policy rejects Bedrock Runtime requests. A model on a Bedrock Runtime provider can't serve any `/v1` endpoint, so the policy skips it. |
319
+
320
+ If no model in the list can serve the request's endpoint, the policy returns
321
+ `400`. The message names the endpoint, and for an endpoint that uses a
322
+ provider's own API, such as `/v1/messages`, the API it requires.
323
+
324
+ These rules depend on the provider, and on whether the model is Claude. The
325
+ policy doesn't check which APIs each model serves. A request that picks a model
326
+ on an API the model doesn't serve fails, such as a `/v1/chat/completions`
327
+ request that picks a model that serves only the Responses API.
328
+
329
+ On `/v1/responses`:
330
+
331
+ - Bedrock Mantle serves the Responses API for only some of its models other than
332
+ Claude. Requests that pick a Mantle model without Responses support fail. To
333
+ check a model, read the **APIs supported** for the `bedrock-mantle` endpoint
334
+ on its
335
+ [AWS model card](https://docs.aws.amazon.com/bedrock/latest/userguide/model-cards.html).
336
+ The AI Gateway calls that endpoint, and the `bedrock-runtime` endpoint's list
337
+ can differ.
338
+ - OpenRouter stores no responses. When the list also has a model that does,
339
+ OpenRouter gets only creates that set `store` to `false`. See
340
+ [Stored responses](#stored-responses).
341
+
342
+ ### Stored responses
343
+
344
+ A stored response lives on the provider account that created it. Requests that
345
+ read or continue one must reach that account: retrieving a response, listing its
346
+ input items, deleting it, and creating a response with `previous_response_id`, a
347
+ `conversation`, or an `item_reference` input item.
348
+
349
+ | When | Stored-response requests |
350
+ | --------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
351
+ | Every model in the list that stores responses has the same provider name | Go to that provider |
352
+ | `strategy` is `sticky` by `user` or `expression`, and the request has a key | Go to the model the key picks, where that caller's responses were created |
353
+ | Anything else | Fail with `400`, and the request log names the fix |
354
+
355
+ - The provider name is the part before the slash, compared without regard to
356
+ case. `openai/gpt-5` and `openai/gpt-5-mini` share one, but
357
+ `azure-eastus/gpt-5` and `azure-westus/gpt-5` are two.
358
+ - Stored-response requests get no retry model, because no other account holds
359
+ the response. Fallback Model placed after this policy still adds its
360
+ `fallback`, so a continuation that stays rate limited moves to the fallback,
361
+ and fails there if the fallback is on another account.
362
+ - A provider that stores nothing, such as OpenRouter, never gets stored-response
363
+ requests. It gets a create only when the create sets `store` to `false`, or
364
+ when no provider in the list stores responses, because a create stores its
365
+ response unless it opts out.
366
+ - A create that moves to the retry model stores its response on the retry
367
+ model's account. If that's another account, later requests for the response go
368
+ to the chosen model's account and fail.
369
+ - The retry model, a `fallback`, or a `quotaFallback` is on another account when
370
+ its provider name differs from the chosen model's. It's also on another
371
+ account when AI Gateway Authentication passes callers' own API keys through,
372
+ because the chosen model uses the caller's key and the others use your
373
+ configured key.
374
+ - The `400` tells the caller only that this API can't read or continue the
375
+ stored response. The fix is yours to make, so the request log carries it.
376
+ - `conversation` keys can't route them to one of several provider names. A
377
+ retrieve sends no messages, and a request with `previous_response_id` sends
378
+ only the new turn. With one provider name, each continuation is keyed on its
379
+ new turn, so it can run on a different model than the turn before.
380
+ - Changing the list can move a caller to another model. Responses stored before
381
+ the change stay on the old account.
382
+
383
+ ## Policy order
384
+
385
+ Place Model Load Balancing after authentication and before the policies that
386
+ read or change model routing:
387
+
388
+ ```json
389
+ {
390
+ "inboundPolicyChain": [
391
+ { "name": "ai-gateway-auth-inbound" },
392
+ { "name": "ai-gateway-load-balancing-inbound" },
393
+ { "name": "ai-gateway-fallback-model-inbound" },
394
+ { "name": "ai-gateway-metering-inbound" },
395
+ { "name": "ai-gateway-semantic-cache-inbound" }
396
+ ]
397
+ }
398
+ ```
399
+
400
+ This chain shows the order only. Entries without `options` use the options
401
+ declared in `config/policies.json`. See
402
+ [Configure lists per app](#configure-lists-per-app).
403
+
404
+ | Policy | With Model Load Balancing |
405
+ | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
406
+ | [Authentication](/docs/policies/ai-gateway-auth-inbound) | Place it before, so `request.user.sub` is set for sticky routing by user. |
407
+ | [Model Override](/docs/policies/ai-gateway-model-override-inbound) | Not needed, because this policy picks the model for every request it handles. If Model Override runs first, its model wins: always with `force`, and with `default` when a request omits `model`, as stored-response reads do. Placed after, it does nothing for request types this policy has a list for. |
408
+ | [Model Filtering](/docs/policies/ai-gateway-model-filtering-inbound) | Not needed for request types this policy has a list for, because the list already limits the models clients can reach. If Model Filtering runs first, it routes or refuses each request by the model the client asked for, and this policy does nothing. Placed after, it doesn't check this policy's choices. |
409
+ | [Fallback Model](/docs/policies/ai-gateway-fallback-model-inbound) | Place it after, where it changes the retry model, the timeout, and where over-quota requests go. Placed before, it does nothing. See [With Fallback Model](#with-fallback-model). |
410
+ | [Smart Router](/docs/policies/ai-gateway-smart-router-inbound) | When it routes a request, its model wins, whichever runs first. The request loses this policy's retry model, and Smart Router doesn't apply this policy's endpoint or stored-response rules. Fallback Model placed after Smart Router still adds its `fallback` and `quotaFallback`. Placed before Smart Router, they're lost too. |
411
+ | [Metering](/docs/policies/ai-gateway-metering-inbound) | Place it after this policy and Fallback Model, so a request over its quota can switch to Fallback Model's `quotaFallback`. Costs and usage use the model that served the request. |
412
+ | [Semantic Cache](/docs/policies/ai-gateway-semantic-cache-inbound) | Place it after. Its cache key uses the request's own `model`, not this policy's choice, so with a list of different models a cached answer can come from any of them. Placed before, it serves a cache hit without this policy when the request's `model` is a valid model, and skips the hit otherwise. |
413
+
414
+ ### With Fallback Model
415
+
416
+ Fallback Model placed after this policy changes where failed and over-quota
417
+ requests go:
418
+
419
+ | Requests | Fallback Model's `fallback` | Its `quotaFallback`, over quota |
420
+ | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
421
+ | Chat completions and embeddings | Becomes the retry model, and its `fallbackTimeoutSeconds`, 60 when unset, replaces this policy's. | Gets the request. A `quotaFallback` that the AI Gateway refuses on chat completions, such as Claude on Vertex AI, fails the request with `400`. |
422
+ | Responses and Messages creates | Becomes the retry model if it can serve the endpoint. Otherwise the request has no retry model. | Gets the request if it can serve the endpoint. Otherwise the gateway returns `429`. |
423
+ | `/v1/messages/count_tokens` | Never tried. | Gets the request if it can serve the endpoint. Otherwise the gateway returns `429`. |
424
+ | `/v1/systemone`, and requests that retrieve, delete, or list the input items of a stored response | Never tried. | Never used. The gateway returns `429`. |
425
+
426
+ - Requests this policy sends to the `fallback` model itself keep this policy's
427
+ retry model and timeout. Requests it sends to the `quotaFallback` model itself
428
+ get no quota fallback, so when they're over quota, the gateway returns `429`.
429
+ Use a `quotaFallback` that isn't in the list.
430
+ - When a `quotaFallback` call fails in a way the endpoint retries, the request
431
+ moves to the retry model, so an over-quota request can still run on a model in
432
+ the list. To keep over-quota requests off the list, give Fallback Model a
433
+ `fallback` that isn't in the list. To keep failed requests inside the list,
434
+ give Fallback Model only a `quotaFallback`.
435
+ - No model serves both `/v1/responses` and `/v1/messages`, so when your clients
436
+ call both, a `fallback` or a `quotaFallback` covers at most one of them.
437
+ - Over quota, a create that continues a stored response goes to the
438
+ `quotaFallback`, and fails there if the `quotaFallback` is on another account.
439
+ Other creates over quota go there too, so a response they store lives on the
440
+ `quotaFallback`'s account. If that's another account, requests for the
441
+ response fail once they're no longer over quota. See
442
+ [Stored responses](#stored-responses).
443
+ - For embeddings, set `fallback` to the same embedding model as the list. With a
444
+ one-model list, use that model on another provider name, because a `fallback`
445
+ equal to the chosen model is skipped.
446
+ - Unless you've changed them, Fallback Model's declared options set
447
+ `openai/gpt-4o-mini` as the `completions` `fallback`. A Fallback Model entry
448
+ without `options`, like the one in the chain above, makes that model the retry
449
+ model. On a gateway that can't use `openai/gpt-4o-mini`, such as one with no
450
+ provider named `openai`, every request that Fallback Model adds it to fails
451
+ with `500`, even requests that never try a backup. Give the entry its own
452
+ `options`.
453
+
454
+ ## Configure lists per app
455
+
456
+ Declare the policy in `config/policies.json` with a default list. An app can
457
+ supply its own options in its chain entry. Entry options replace the declared
458
+ options entirely:
459
+
460
+ ```json
461
+ {
462
+ "inboundPolicyChain": [
463
+ {
464
+ "name": "ai-gateway-load-balancing-inbound",
465
+ "options": {
466
+ "strategy": "sticky",
467
+ "stickyBy": "conversation",
468
+ "models": {
469
+ "completions": ["azure-eastus/gpt-5", "azure-westus/gpt-5"]
470
+ }
471
+ }
472
+ }
473
+ ]
474
+ }
475
+ ```
476
+
477
+ Unless you've changed them, the declared options are a one-model list,
478
+ `openai/gpt-4o-mini`. A chain entry without `options` then sends every chat
479
+ completions and Responses request to that model, and fails Messages and System
480
+ One requests with `400`, because that model can't serve them.
481
+
482
+ The gateway validates chain options when the policy runs. Invalid options fail
483
+ requests with an error that names the field to fix. So does a model the gateway
484
+ can't use, such as an unknown provider or an inactive model, on every request
485
+ that picks it as the chosen or the retry model. A provider whose API key isn't
486
+ on the gateway yet fails those requests with an error that says the provider
487
+ isn't ready, and the request log names the provider and its environment
488
+ variable.
489
+
490
+ ## Monitoring
491
+
492
+ AI Gateway analytics attribute each request to the model that served it. Logging
493
+ plugins also receive these properties on the request's log entries after the
494
+ policy picks a model:
495
+
496
+ | Property | Value |
497
+ | --------------------------- | ---------------------------------- |
498
+ | `aiLoadBalancingModel` | The model the policy chose |
499
+ | `aiLoadBalancingRetryModel` | The retry model, when there is one |
500
+
501
+ The properties record this policy's picks. They don't change when Fallback Model
502
+ replaces the retry model or Smart Router replaces the choice.
503
+
504
+ ## Limitations
505
+
506
+ - Each list can have up to 32 models, each listed once. The policy compares
507
+ entries without regard to case, so `openai/gpt-5` and `OpenAI/gpt-5` count as
508
+ one model. Provider names work in any case, but write each model ID exactly as
509
+ your provider spells it. A model ID in another case, such as `openai/GPT-5`,
510
+ can fail to route.
511
+ - Each request tries at most two models.
512
+ - An expression can be up to 1024 bytes and 32 property segments.
513
+
514
+ Lists that mix providers or models have limits of their own:
515
+
516
+ | When | What happens |
517
+ | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
518
+ | A list mixes providers or vendors, on `/v1/responses` or `/v1/messages` | Only models the AI Gateway sends that API's requests to are eligible, and that depends on the model, not only its provider. On Azure AI, for example, Claude models are eligible on `/v1/messages`, and GPT models on `/v1/responses`. See [Endpoints](#endpoints). A mixed list often leaves one model and no retry model, so list at least two models for each API your clients use. |
519
+ | A list mixes vendors, on `/v1/chat/completions` | Each pick goes through its provider's translation, which drops what that provider doesn't support. Claude models ignore `response_format` and `seed`, use a `max_tokens` of 1,024 unless the request sets `max_tokens`, read `developer` messages as user messages, and fail requests that carry images, audio, or files. For the same behavior on every pick, list deployments of one model. |
520
+ | The models accept different parameters or context lengths | A request that one model rejects fails whenever the policy picks that model. The retry model isn't tried. |
521
+ | AI Gateway Authentication passes callers' own API keys through (`credentialPassthrough`) | The caller's key goes to whichever model the policy picks, so list only models that accept it. Every model that can be the retry model also needs its own configured key, and requests that move there run on your provider account. |
522
+ | You want different lists for Chat Completions, Responses, and Messages | One `completions` list serves them all. A second Model Load Balancing policy in the chain doesn't run for request types the first one has a list for. |
@@ -0,0 +1,6 @@
1
+ Spreads AI requests across a list of models. The policy cycles through the list,
2
+ or keeps requests that share a key, such as a user or a conversation, on the
3
+ same model. Most requests also get a retry model from the list. A request moves
4
+ there when the chosen model stays rate limited. Chat completions and embeddings
5
+ requests also move there when the model returns a server error, can't be
6
+ reached, or times out.