Skip to content

Commit df58593

Browse files
missBergclaude
andcommitted
docs: add QuotaPolicy capability guide
Document the QuotaPolicy CRD, corrected against the current implementation: per-model token quotas as the primary mechanism, the full CEL cost-expression variable set, Shared-only bucket mode, the valid duration enum (1s/1m/1h/1d), client-selector conditions, shadow mode, prerequisites, and a service-wide quota section noting it is not yet enforced on the data plane. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Erica Hughberg <erica.sundberg.90@gmail.com>
1 parent 2e23b28 commit df58593

4 files changed

Lines changed: 359 additions & 1 deletion

File tree

site/docs/capabilities/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Comprehensive traffic handling and routing capabilities:
3737

3838
- **[Model Virtualization](./traffic/model-virtualization.md)**: Abstract and virtualize AI models
3939
- **[Provider Fallback](./traffic/provider-fallback.md)**: Automatic failover between AI providers
40+
- **[Quota Policy](./traffic/quota-policy.md)**: Token-based quota management for controlling total consumption budgets
4041
- **[Usage-based Rate Limiting](./traffic/usage-based-ratelimiting.md)**: Token-aware rate limiting for AI workloads
4142
- **[Header and Body Mutations](./traffic/header-body-mutations.md)**: Customize HTTP headers and JSON body fields per backend or route
4243

site/docs/capabilities/traffic/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ id: traffic
33
title: Traffic Handling
44
---
55

6-
This section provides information about traffic routing related capabilities in Envoy AI Gateway.
6+
This section provides information about traffic routing related capabilities in Envoy AI Gateway, including [Quota Policy](./quota-policy.md) for controlling total token consumption budgets across time windows.
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
---
2+
id: quota-policy
3+
title: Quota Policy
4+
sidebar_position: 6
5+
---
6+
7+
# Quota Policy
8+
9+
`QuotaPolicy` enables token-based quota management for AI inference services in Envoy AI Gateway.
10+
When a backend's quota is exceeded, requests are rejected with a `429 Too Many Requests` status code.
11+
If the `AIGatewayRoute` rule lists multiple backends, traffic automatically fails over to the next
12+
backend whose quota has not been exhausted.
13+
14+
:::note QuotaPolicy vs. Rate Limiting
15+
QuotaPolicy manages **total consumption budgets** (for example, 100,000 tokens per hour). This is
16+
distinct from [usage-based rate limiting](./usage-based-ratelimiting.md), which controls **request
17+
velocity** (for example, requests per second). Use QuotaPolicy when you need to cap cumulative token
18+
spend across a time window.
19+
:::
20+
21+
## Overview
22+
23+
Key features of QuotaPolicy:
24+
25+
- **Per-model token quotas** — assign token budgets to individual models served by an `AIServiceBackend`.
26+
- **CEL cost expressions** — weight input, output, cached, and reasoning tokens differently when
27+
computing how much a request burns down a quota.
28+
- **Client-selector bucket rules** — carve out per-tenant or per-header quotas using request attributes.
29+
- **Shadow mode** — evaluate quota rules without enforcing them, for safe rollout.
30+
- **Automatic failover** — when a quota is exhausted, route to the next available backend on the rule.
31+
32+
## How It Works
33+
34+
1. A `QuotaPolicy` is attached to one or more `AIServiceBackend` resources via `targetRefs`.
35+
2. For each completed request, the token cost is computed using the configured cost expression
36+
(defaults to `total_tokens`).
37+
3. The cost is charged against the matching quota bucket (the per-model default bucket, or a matching
38+
bucket rule).
39+
4. When a bucket's quota is exceeded, subsequent matching requests receive `429 Too Many Requests`.
40+
5. If the `AIGatewayRoute` rule lists multiple `backendRefs`, traffic fails over to the next backend
41+
whose quota is still available.
42+
43+
:::tip Prerequisites
44+
Quota enforcement uses the same infrastructure as usage-based rate limiting:
45+
46+
1. **Redis Deployment**: A Redis instance for storing quota counters. See the [redis.yaml example](https://github.com/envoyproxy/ai-gateway/blob/main/examples/token_ratelimit/redis.yaml) for a simple deployment.
47+
2. **Envoy Gateway Configuration**: Envoy Gateway must be configured at installation time to enable rate limiting and point to your Redis instance. See the [Envoy Gateway Installation Guide](../../getting-started/prerequisites.md#additional-features-rate-limiting-inferencepool-etc).
48+
49+
See [Usage-based Rate Limiting](./usage-based-ratelimiting.md) for more detail on the rate limit infrastructure that QuotaPolicy builds on.
50+
:::
51+
52+
## Configuration
53+
54+
### Per-Model Quotas
55+
56+
Use `perModelQuotas` to apply a token budget to a specific model served by the targeted backend(s).
57+
58+
:::warning The model name must match the route
59+
A `perModelQuotas` entry only applies when its `modelName` matches the `modelNameOverride` set on the
60+
`AIGatewayRoute` rule's `backendRefs` for the targeted backend. If they do not match, the quota is
61+
silently **not** applied.
62+
:::
63+
64+
Given an `AIGatewayRoute` that routes a model to the backend:
65+
66+
```yaml
67+
apiVersion: aigateway.envoyproxy.io/v1alpha1
68+
kind: AIGatewayRoute
69+
metadata:
70+
name: my-route
71+
spec:
72+
rules:
73+
- backendRefs:
74+
- name: my-backend
75+
modelNameOverride: my-model # <-- the QuotaPolicy modelName must match this
76+
```
77+
78+
attach a `QuotaPolicy` to the backend:
79+
80+
```yaml
81+
apiVersion: aigateway.envoyproxy.io/v1alpha1
82+
kind: QuotaPolicy
83+
metadata:
84+
name: my-quota-policy
85+
spec:
86+
targetRefs:
87+
- group: aigateway.envoyproxy.io
88+
kind: AIServiceBackend
89+
name: my-backend
90+
perModelQuotas:
91+
- modelName: "my-model"
92+
quota:
93+
mode: Shared
94+
defaultBucket:
95+
limit: 10000 # Maximum tokens allowed in the window.
96+
duration: "1h" # Sliding window.
97+
```
98+
99+
You can attach quotas for multiple models, each with its own budget:
100+
101+
```yaml
102+
perModelQuotas:
103+
- modelName: gpt-4
104+
quota:
105+
defaultBucket:
106+
limit: 10000 # Strict limit for the expensive model.
107+
duration: "1h"
108+
- modelName: gpt-3.5-turbo
109+
quota:
110+
defaultBucket:
111+
limit: 100000 # Higher limit for the cost-effective model.
112+
duration: "1h"
113+
```
114+
115+
:::note
116+
When multiple `QuotaPolicy` resources define the same model for the same `AIServiceBackend`, the
117+
policy whose namespace/name sorts alphabetically first takes precedence.
118+
:::
119+
120+
### Custom Cost Expression
121+
122+
By default, a request's cost is its `total_tokens`. You can override this with a
123+
[CEL](https://github.com/google/cel-spec) expression that weights token types differently. The
124+
following variables are available in a `costExpression`:
125+
126+
| Variable | Type | Description |
127+
| ----------------------------- | ------ | ------------------------------------------------ |
128+
| `input_tokens` | uint | Prompt / input tokens. |
129+
| `output_tokens` | uint | Completion / output tokens. |
130+
| `total_tokens` | uint | Total tokens (the default cost). |
131+
| `cached_input_tokens` | uint | Input tokens served from the provider's cache. |
132+
| `cache_creation_input_tokens` | uint | Input tokens charged for writing to the cache. |
133+
| `reasoning_tokens` | uint | Reasoning tokens (for reasoning-capable models). |
134+
| `model` | string | The resolved model name. |
135+
| `backend` | string | The serving backend name. |
136+
| `route_name` | string | The route name. |
137+
138+
```yaml
139+
perModelQuotas:
140+
- modelName: gpt-4
141+
quota:
142+
# Cached input tokens cost 10% of regular input tokens;
143+
# output tokens cost 6x regular input tokens.
144+
costExpression: "input_tokens + cached_input_tokens * 0.1 + output_tokens * 6"
145+
defaultBucket:
146+
limit: 50000
147+
duration: "1h"
148+
```
149+
150+
:::tip
151+
Use a custom cost expression when token types have significantly different costs with your provider —
152+
for example, output tokens are typically more expensive than input tokens. The expression must
153+
evaluate to a non-negative integer.
154+
:::
155+
156+
### Bucket Mode
157+
158+
The `mode` field on a per-model `quota` controls how the `defaultBucket` and matching `bucketRules`
159+
interact when a request matches one or more rules.
160+
161+
Currently only **`Shared`** mode is supported (it is also the default, so the field can be omitted):
162+
163+
- The request is charged to **all** matching `bucketRules` **and** the `defaultBucket`.
164+
- The request is allowed only if quota is available in **every** matching bucket.
165+
166+
```yaml
167+
perModelQuotas:
168+
- modelName: gpt-4
169+
quota:
170+
mode: Shared # Default — may be omitted.
171+
defaultBucket:
172+
limit: 10000
173+
duration: "1h"
174+
```
175+
176+
:::note
177+
An exclusive bucket mode (charging matching rules **or** the default bucket, but not both) is planned
178+
but not yet available. The only accepted value today is `Shared`.
179+
:::
180+
181+
### Client Selectors with Bucket Rules
182+
183+
Bucket rules let you carve out dedicated quotas for specific clients identified by request attributes
184+
such as headers. This is useful for multi-tenant deployments where each tenant needs its own budget.
185+
Because the mode is `Shared`, a request that matches a bucket rule is charged against **both** that
186+
rule's bucket **and** the `defaultBucket`.
187+
188+
```yaml
189+
perModelQuotas:
190+
- modelName: gpt-4
191+
quota:
192+
defaultBucket:
193+
limit: 10000 # Shared budget across all tenants.
194+
duration: "1h"
195+
bucketRules:
196+
# Premium tenant gets a dedicated, higher per-tenant budget.
197+
- clientSelectors:
198+
- headers:
199+
- name: x-tenant-id
200+
type: Exact
201+
value: premium-tenant
202+
quota:
203+
limit: 50000
204+
duration: "1h"
205+
```
206+
207+
Use `type: Distinct` to create a separate bucket for every unique value of a header — for example, a
208+
per-tenant budget keyed on the tenant ID:
209+
210+
```yaml
211+
bucketRules:
212+
- clientSelectors:
213+
- headers:
214+
- name: x-tenant-id
215+
type: Distinct # One bucket per unique tenant ID.
216+
quota:
217+
limit: 5000
218+
duration: "1h"
219+
```
220+
221+
Supported header match types are `Exact`, `Distinct`, and `RegularExpression`.
222+
223+
`clientSelectors` are Envoy Gateway [`RateLimitSelectCondition`](https://gateway.envoyproxy.io/docs/api/extension_types/#ratelimitselectcondition) values. Besides `headers`, a selector can also match on `sourceCIDR` (client IP range), `methods` (HTTP method), `path`, and `queryParams`. Refer to the Envoy Gateway rate limit API reference for the full set of selector options.
224+
225+
### Shadow Mode
226+
227+
Shadow mode lets you test a bucket rule without rejecting traffic. When `shadowMode` is enabled on a
228+
bucket rule, all quota checks are performed (cache lookups, counter updates, telemetry), but the
229+
outcome is never enforced — the request always succeeds even if the quota is exceeded.
230+
231+
```yaml
232+
bucketRules:
233+
- clientSelectors:
234+
- headers:
235+
- name: x-tenant-id
236+
type: Distinct
237+
quota:
238+
limit: 5000
239+
duration: "1h"
240+
shadowMode: true # Evaluate but do not enforce.
241+
```
242+
243+
:::tip
244+
Use shadow mode when rolling out a new quota rule. Monitor the telemetry to confirm the limit is set
245+
correctly, then enable enforcement by removing `shadowMode` (or setting it to `false`).
246+
:::
247+
248+
Shadow mode is configured per bucket rule. It cannot be set on the `defaultBucket`.
249+
250+
## Duration Format
251+
252+
The `duration` field selects the sliding-window size. It must be exactly one of the following values:
253+
254+
| Value | Window |
255+
| ------ | ---------- |
256+
| `"1s"` | One second |
257+
| `"1m"` | One minute |
258+
| `"1h"` | One hour |
259+
| `"1d"` | One day |
260+
261+
The window is fixed-size — arbitrary multiples such as `"30s"` or `"15m"` are **not** valid and will
262+
be rejected by the CRD schema. Choose the `limit` to express your budget within one of these windows.
263+
264+
## Service-Wide Quota
265+
266+
The `serviceQuota` field applies a single budget to **all** models served by the targeted backend,
267+
without naming individual models. Unlike `perModelQuotas`, it accepts only an optional
268+
`costExpression` plus a flat `limit` and `duration` — bucket modes, `bucketRules`, and client
269+
selectors are **not** available at the service-wide level (the underlying type is
270+
`ServiceQuotaDefinition`, versus `QuotaDefinition` for per-model quotas). For per-tenant rules or
271+
client selectors, use `perModelQuotas` instead.
272+
273+
```yaml
274+
apiVersion: aigateway.envoyproxy.io/v1alpha1
275+
kind: QuotaPolicy
276+
metadata:
277+
name: my-quota-policy
278+
spec:
279+
targetRefs:
280+
- group: aigateway.envoyproxy.io
281+
kind: AIServiceBackend
282+
name: my-backend
283+
serviceQuota:
284+
# Optional custom cost expression (defaults to total_tokens).
285+
costExpression: "input_tokens + output_tokens * 3"
286+
quota:
287+
limit: 200000
288+
duration: "1h"
289+
```
290+
291+
:::warning Not yet enforced
292+
In the current release, `serviceQuota` is **not applied by the data plane**. The gateway builds rate
293+
limit configuration for it, but no request-time enforcement is wired up, so on its own it does not
294+
reject or count traffic. Use `perModelQuotas` for enforced token quotas until service-wide
295+
enforcement lands.
296+
:::
297+
298+
## Full Example
299+
300+
The following `QuotaPolicy` combines per-model quotas, custom cost expressions, bucket rules with
301+
client selectors, and shadow mode.
302+
303+
```yaml
304+
apiVersion: aigateway.envoyproxy.io/v1alpha1
305+
kind: QuotaPolicy
306+
metadata:
307+
name: full-quota-policy
308+
spec:
309+
targetRefs:
310+
- group: aigateway.envoyproxy.io
311+
kind: AIServiceBackend
312+
name: my-backend
313+
314+
perModelQuotas:
315+
- modelName: gpt-4
316+
quota:
317+
costExpression: "input_tokens + cached_input_tokens * 0.1 + output_tokens * 6"
318+
mode: Shared
319+
defaultBucket:
320+
limit: 10000
321+
duration: "1h"
322+
bucketRules:
323+
# Premium tenant gets a dedicated, higher quota.
324+
- clientSelectors:
325+
- headers:
326+
- name: x-tenant-id
327+
type: Exact
328+
value: premium-tenant
329+
quota:
330+
limit: 50000
331+
duration: "1h"
332+
# Track per-tenant usage in shadow mode (no enforcement).
333+
- clientSelectors:
334+
- headers:
335+
- name: x-tenant-id
336+
type: Distinct
337+
quota:
338+
limit: 5000
339+
duration: "1h"
340+
shadowMode: true
341+
342+
- modelName: gpt-3.5-turbo
343+
quota:
344+
defaultBucket:
345+
limit: 100000
346+
duration: "1h"
347+
```
348+
349+
## References
350+
351+
- [API Reference](../../api/api.mdx)
352+
- [Usage-Based Rate Limiting](./usage-based-ratelimiting.md)
353+
- [Provider Fallback](./provider-fallback.md)

site/docs/capabilities/traffic/usage-based-ratelimiting.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import TabItem from '@theme/TabItem';
99

1010
This guide focuses on AI Gateway's specific capabilities for token-based rate limiting in LLM requests. For general rate limiting concepts and configurations, refer to [Envoy Gateway's Rate Limiting documentation](https://gateway.envoyproxy.io/docs/tasks/traffic/global-rate-limit/).
1111

12+
:::info Quota Policy vs. Rate Limiting
13+
AI Gateway also provides [Quota Policy](./quota-policy.md) for managing **total consumption budgets** (for example, 100,000 tokens per hour). Use QuotaPolicy when you need to cap cumulative token spend, and usage-based rate limiting (this page) when you need to control **request velocity**.
14+
:::
15+
1216
## Overview
1317

1418
AI Gateway leverages Envoy Gateway's Global Rate Limit API to provide token-based rate limiting for LLM requests. Key features include:

0 commit comments

Comments
 (0)