Gateway Routing#
The AIBrix gateway is built on Envoy Gateway and acts as the single entry point for all LLM inference requests. It handles dynamic model discovery, request routing, rate limiting, and response streaming. For a deep dive into the internal design, see the AIBrix Gateway architecture guide.
This page is the gateway routing reference. Dedicated guides cover the strategies that have their own setup:
Dynamic Routing#
When a model or LoRA adapter is deployed, the respective controller automatically creates an HTTPRoute object. The gateway dynamically discovers these routes to forward incoming requests without any manual configuration.
Find the gateway endpoint#
First, retrieve the external IP and port of the Envoy proxy:
$ kubectl get svc -n envoy-gateway-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
envoy-aibrix-system-aibrix-eg-903790dc LoadBalancer 10.96.239.246 101.18.0.4 80:32079/TCP 10d
envoy-gateway ClusterIP 10.96.166.226 <none> 18000/TCP,18001/TCP,18002/TCP,19001/TCP 10d
In most Kubernetes setups, LoadBalancer is supported by default. Capture the external IP:
LB_IP=$(kubectl get svc/envoy-aibrix-system-aibrix-eg-903790dc -n envoy-gateway-system -o=jsonpath='{.status.loadBalancer.ingress[0].ip}')
ENDPOINT="${LB_IP}:80"
Verify the route#
Verify that the HTTPRoute status is Accepted before sending traffic:
$ kubectl get httproute -A
NAMESPACE NAME HOSTNAMES AGE
aibrix-system aibrix-reserved-router 17m # reserved router
aibrix-system deepseek-r1-distill-llama-8b-router 14m # created for each model deployment
....
$ kubectl describe httproute deepseek-r1-distill-llama-8b-router -n aibrix-system
Name: deepseek-r1-distill-llama-8b-router
Namespace: aibrix-system
Labels: <none>
Annotations: <none>
API Version: gateway.networking.k8s.io/v1
Kind: HTTPRoute
Metadata:
Creation Timestamp: 2025-02-16T17:56:03Z
Generation: 1
Resource Version: 2641
UID: 2f3f9620-bf7c-487a-967e-2436c3809178
Spec:
Parent Refs:
Group: gateway.networking.k8s.io
Kind: Gateway
Name: aibrix-eg
Namespace: aibrix-system
Rules:
Backend Refs:
Group:
Kind: Service
Name: deepseek-r1-distill-llama-8b
Namespace: default
Port: 8000
Weight: 1
Matches:
Headers:
Name: model
Type: Exact
Value: deepseek-r1-distill-llama-8b
Path:
Type: PathPrefix
Value: /
Timeouts:
Request: 120s
Status:
Parents:
Conditions:
Last Transition Time: 2025-02-16T17:56:03Z
Message: Route is accepted
Observed Generation: 1
Reason: Accepted
Status: True
Type: Accepted
Last Transition Time: 2025-02-16T17:56:03Z
Message: Resolved all the Object references for the Route
Observed Generation: 1
Reason: ResolvedRefs
Status: True
Type: ResolvedRefs
Controller Name: gateway.envoyproxy.io/gatewayclass-controller
Parent Ref:
Group: gateway.networking.k8s.io
Kind: Gateway
Name: aibrix-eg
Namespace: aibrix-system
Events: <none>
Send a request#
The model name in the request body must match the model.aibrix.ai/name label in your deployment:
curl -v http://${ENDPOINT}/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-r1-distill-llama-8b",
"messages": [{"role": "user", "content": "Say this is a test!"}],
"temperature": 0.7
}'
Authentication#
Attention
AIBrix exposes a public endpoint to the internet. Enable authentication to secure it.
For vLLM backends, pass the --api-key argument or set the VLLM_API_KEY environment variable to require an API key in the Authorization header.
See vLLM OpenAI-Compatible Server for details.
After enabling authentication, include the Authorization header in every request:
curl -v http://${ENDPOINT}/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer any_key" \
-d '{
"model": "deepseek-r1-distill-llama-8b",
"messages": [{"role": "user", "content": "Say this is a test!"}],
"temperature": 0.7
}'
Expose custom paths#
As of v0.5.0, each model’s HTTPRoute is created with a default set of path prefixes (e.g. /v1/chat/completions, /v1/completions). To expose additional custom paths, add the model.aibrix.ai/model-router-custom-paths annotation to your Deployment, ModelAdapter, or RayClusterFleet manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
model.aibrix.ai/name: deepseek-r1-distill-llama-8b
model.aibrix.ai/port: "8000"
annotations:
model.aibrix.ai/model-router-custom-paths: /version,/score # comma-separated; spaces and empty entries are ignored
name: deepseek-r1-distill-llama-8b
namespace: default
Routing Strategies#
Selecting a strategy#
The routing strategy controls how the gateway selects the target pod for each request. It can be set per request via the routing-strategy header, per model via a config profile, or globally via the ROUTING_ALGORITHM environment variable. When more than one is set, Routing strategy priority decides which one wins.
To override the strategy for a single request, pass the routing-strategy header with any of the strategy names listed below:
curl -v http://${ENDPOINT}/v1/chat/completions \
-H "routing-strategy: least-request" \
-H "Content-Type: application/json" \
-d '{
"model": "your-model-name",
"messages": [{"role": "user", "content": "Say this is a test!"}],
"temperature": 0.7
}'
Some strategies rely on metrics queried from the Prometheus HTTP API (PromQL). See Prometheus API Access for configuration.
General load balancing#
random: routes to a randomly selected pod. Suitable as a baseline or when all pods are equivalent.least-request: routes to the pod with the fewest in-flight requests.least-request-top-k: likeleast-request, but spreads load across the least-loaded pods so that several gateway replicas deciding at the same moment don’t all send their next request to the same pod.Candidates: the ready pods that are both among the
Kleast-loaded and withinepsilonrequests of the least-loaded pod. One of them is picked uniformly at random.Ramp penalty: a newly ready pod gets a temporary penalty that decays over a window, so it isn’t flooded before it warms up.
Tuning:
AIBRIX_ROUTING_LEAST_REQUEST_TOP_K(default5),AIBRIX_ROUTING_LEAST_REQUEST_TOP_K_EPSILON(default4) andAIBRIX_ROUTING_LEAST_REQUEST_TOP_K_RAMP_WINDOW(default300s,0disables the ramp). Seepkg/plugins/gateway/ENV_VARS.md.
least-busy-time: routes to the pod with the least cumulative busy processing time.least-latency: routes to the pod with the lowest average processing latency.least-kv-cache: routes to the pod with the smallest KV cache occupancy (least VRAM used).least-utilization: routes to the pod with the lowest overall utilization score.load-balance: capacity-aware weighted least-request routing. Faster hardware is learned dynamically and receives proportionally more traffic. Seepkg/plugins/gateway/algorithms/load_balance.mdfor details.Each pod is scored, and the pod with the lowest score is selected:
score = (running_requests + λ·queued_requests) / EWMA(output_tokens_per_second) × (1 + α·(1 − kv_free)²)
EWMA(output_tokens_per_second)is the gateway-observed rate at which the pod completes output tokens.kv_freeis the pod’s free KV-cache fraction.λdefaults to0(the running count already includes requests queued in the engine) andαto2.Tuning:
λ,αand the free-KV-cache threshold below are set withAIBRIX_LOAD_BALANCE_QUEUED_WEIGHT,AIBRIX_LOAD_BALANCE_KV_PRESSURE_ALPHAandAIBRIX_LOAD_BALANCE_KV_CRITICAL_FREE, respectively.
Edge cases:
KV guardrail: a pod with less than 10% free KV cache scores
+Infand is skipped. If every pod is below that threshold, the request still routes to the pod with the most KV headroom.Missing KV metrics: a pod whose engine reports no KV-cache usage, or reports
NaN, is treated as fully free (no penalty and no guardrail).No token-rate estimate yet: the pod is scored at the mean estimate of the others (uniform capacity when none has one).
Ties: broken using least combined GPU+CPU KV-cache usage, falling back to a random pick if cache metrics are unavailable for the tied pods.
throughput: routes to the pod that has processed the fewest total weighted tokens, favoring underloaded pods.power-of-two: applies power-of-two-choices — randomly samples two pods and selects the better one.
KV-cache aware#
prefix-cache: routes to a pod that already holds a KV cache matching the request’s prompt prefix, selecting the best prefix-matched pod within a configurable stddev threshold. See Prefix-Cache Aware Routing for the full guide.Load: it is purely about prefix caching and does not itself gate on cluster-wide load imbalance; the gateway applies the load-imbalance gate centrally ahead of it.
Modes: standard (local hash table) or KV sync (real-time distributed index, enabled with
AIBRIX_PREFIX_CACHE_KV_EVENT_SYNC_ENABLED=true).Chat requests: the matched text starts with a canonical rendering of
toolswhen present, because chat templates commonly render tools ahead of the messages. SetAIBRIX_PREFIX_CACHE_INCLUDE_TOOLS=falseto match on messages only. In KV sync mode, chat requests tokenized through the remote chat template carry theirtools, so the template places them.
prefix-cache-preble: routes considering both prefix cache hits and pod load, based on Preble: Efficient Distributed Prompt Scheduling for LLM Serving.
Fairness#
vtc-basic: routes using a hybrid score that balances per-user token fairness and pod utilization. A simplified variant of the Virtual Token Counter (VTC) algorithm. See VTC-artifact for background.
SLO-aware#
slo: routes with awareness of per-request service-level objectives.slo-pack-load: SLO-aware routing that consolidates load onto fewer pods.slo-least-load: SLO-aware routing that spreads load to the least loaded pod.slo-least-load-pulling: variant ofslo-least-loadthat pulls metrics directly instead of using cached snapshots.
Specialized#
pd: prefill-decode disaggregation routing. Splits processing between dedicated prefill pods and decode pods for optimized end-to-end latency. See Prefill-Decode Disaggregation (PD) for the full guide.curl -v http://${ENDPOINT}/v1/chat/completions \ -H "routing-strategy: pd" \ -H "Content-Type: application/json" \ -d '{ "model": "your-model-name", "messages": [{"role": "user", "content": "Say this is a test!"}], "temperature": 0.7 }'
session-affinity: sticky session routing that keeps related requests on the same pod. Two headers can drive it: the gateway-issuedx-session-idand the caller-ownedx-aibrix-session-key. See Multi-turn Conversation & Agentic Routing for the full guide.x-session-idx-aibrix-session-keyIssued by
The gateway, in the response header
The caller
Available from
The second request
The first request
Value
Base64-encoded pod address (
IP:Port)Any stable, opaque identifier
Best for
Sequential multi-turn conversations
Concurrent agentic requests
Sequential multi-turn chat uses
x-session-id: the first response carries the header, and the client sends it back unchanged. Concurrent agentic work usesx-aibrix-session-key: the caller supplies a stable opaque key from the first request. If a request carries both, the gateway usesx-session-idfirst and falls back tox-aibrix-session-keywhen it does not resolve to a ready pod. Neither header is a security token, and keys longer than 256 bytes are treated as unset.external: delegates replica selection to an operator-run HTTP service after AIBrix has resolved the model and filtered unsafe replicas. Experimental. See External Replica Routing for the protocol, policy modes, and configuration.
Load-imbalance gate#
The load-imbalance gate restricts candidates to the least-loaded pods when load is severely skewed. The gateway applies it centrally, ahead of whichever strategy actually routes each request, not just when load-balance itself is selected. See pkg/plugins/gateway/ENV_VARS.md.
Auto-blended capacity awareness#
This auto-blend is enabled by default — no opt-in is required. The gateway silently blends load-balance’s capacity-aware scoring in behind the strategy you select. This keeps any single strategy from steering traffic at an already-hot pod, even outside the load-imbalance gate.
The caller never sees this: ctx.Algorithm, response headers, and Validate() all still reflect exactly the strategy that was requested.
What is blended, by strategy:
Most strategies: every strategy above gets the
load-balancescorer, except the ones listed below.least-request-top-kis blended in too, when the selected strategy doesn’t already route by request count, to keep multi-port/data-parallel pod routing working under the blend.Bare
prefix-cache: uses a dedicated 5:4 (1.25:1) lean toward cache affinity overload-balanceand does not receiveleast-request-top-k, so cache locality wins an exact-tie disagreement.Bare
session-affinity: gets no auto-blend at all. Its scoring is binary (the resolved pod vs. everything else), so anyload-balanceweight small enough to still lose an exact tie could never override the pin either. Blending it in would be dead weight, not a capacity safety net, so it keeps running its ownRoute()/ScoreAll()unblended.Exclusive strategies:
pd,slo/slo-*andexternal, as well as an explicit standaloneload-balanceselection, are not blended.
Both blend weights default to 1 (see pkg/plugins/gateway/ENV_VARS.md). Set AIBRIX_ROUTING_AUTO_BLEND_LOAD_BALANCE_WEIGHT=0 to disable the auto-blend.
Config Profiles#
A config profile lets you embed routing settings directly in a model’s pod annotation, so every request to that model picks up the right configuration automatically. You can define multiple named profiles in one annotation and switch between them per-request with a single header.
Defining profiles#
Add the model.aibrix.ai/config annotation to the pod template of your Deployment, StormService, or RayClusterFleet:
annotations:
model.aibrix.ai/config: |
{
"defaultProfile": "default",
"profiles": {
"default": {
"routingStrategy": "least-request"
},
"large-input": {
"routingStrategy": "pd",
"routingConfig": {
"promptLenBucketMinLength": 8192,
"promptTokensGte": 8192,
"prefillScorePolicy": "prefix_cache",
"decodeScorePolicy": "load_balancing"
}
},
"cache-friendly": {
"routingStrategy": "least-kv-cache"
},
"per-replica-limits": {
"routingStrategy": "least-request",
"requestsPerSecondPerReplica": 5,
"requestsInflight": 16
},
"offline-generation": {
"routingStrategy": "throughput",
"routingConfig": {
"maxTokensGte": 2048
}
}
}
}
Selecting a profile at request time#
Two request headers drive the config at request time:
config-profileselects a named profile; when absent,defaultProfile(or"default") is used.config-profile: autoasks the gateway to select a concrete profile from request-local hints in each profile’sroutingConfig.routing-strategyoverrides the selected profile’sroutingStrategy, unlesslockedRoutingStrategyis set (see Routing strategy priority).
These request-time choices are ignored when authoritativeRoutingPolicy is true (see Authoritative routing policy).
# Use the "batch" profile for this request
curl http://${ENDPOINT}/v1/chat/completions \
-H "config-profile: batch" \
-H "Content-Type: application/json" \
-d '{"model": "my-model", "messages": [{"role": "user", "content": "Summarize..."}]}'
Automatic profile selection#
# Let the gateway resolve a concrete profile from routingConfig hints
curl http://${ENDPOINT}/v1/chat/completions \
-H "config-profile: auto" \
-H "Content-Type: application/json" \
-d '{"model": "my-model", "messages": [{"role": "user", "content": "Summarize..."}], "max_tokens": 4096}'
When config-profile: auto is used, the gateway evaluates the supported request-local hints inside each profile’s routingConfig:
A profile matches only when all of its supported hints match the request.
If no profile matches, the gateway uses
defaultProfile(or"default").promptTokens*uses the gateway’s existing prompt text extraction and local token estimation.maxTokens*readsmax_tokensfirst, thenmax_completion_tokenswhenmax_tokensis absent.
Top-level fields#
Field |
Description |
|---|---|
|
Pins a single routing strategy model-wide. When set, it takes precedence over the |
|
When |
|
Profile name used when no |
|
Map of named profiles, each carrying the per-profile fields below. |
Profile fields#
Field |
Description |
|---|---|
|
The routing algorithm for this profile (e.g. |
|
Per-replica RPS cap for this profile. The gateway multiplies it by the model’s current routable replica count to derive the effective model-wide limit, so the cap scales automatically as replicas are added or removed. Supports fractional values (e.g. |
|
Per-replica concurrency cap: the maximum number of in-flight requests allowed on a single replica. It is enforced per pod, so it needs no replica-count scaling. When every routable replica is at the cap, the request is rejected with HTTP 429 and the |
|
Time-to-first-token threshold in seconds for this profile’s responses, overriding |
|
Algorithm-specific settings as a nested JSON object. It carries:
|
Routing strategy priority#
From highest to lowest:
lockedRoutingStrategypinned model-wide in the config — always wins when set, even over therouting-strategyheader.routing-strategyrequest header.routingStrategyfrom the resolved profile. The resolved profile comes from the concreteconfig-profileheader,config-profile: autoroutingConfig hints, ordefaultProfile.ROUTING_ALGORITHMenvironment variable on the gateway plugin.
Backward compatibility: authoritativeRoutingPolicy defaults to false. If a pod has no model.aibrix.ai/config annotation, the gateway falls back to the routing-strategy request header and then the ROUTING_ALGORITHM env (steps 2 and 4 above). No migration is required for existing deployments.
Locking the routing strategy#
Optionally pin a single routing strategy model-wide so it cannot be overridden at request time (see Routing strategy priority). lockedRoutingStrategy only locks the strategy: the remaining per-profile knobs, such as routingConfig, still follow the profile selected by config-profile:
annotations:
model.aibrix.ai/config: |
{
"lockedRoutingStrategy": "pd",
"defaultProfile": "default",
"profiles": {
"default": {
"routingStrategy": "pd"
},
"batch": {
"routingStrategy": "random"
}
}
}
Per-request routing knobs#
Besides the auto-selection hints above, routingConfig carries routing thresholds that
override the gateway’s process-wide environment variables for this profile’s requests only.
An unset knob keeps the environment default, and a value the matching environment variable
would reject (a negative factor, a percentage outside its range, an unknown GPU name) is
ignored, so a profile can only narrow or sharpen routing behavior. Requests to the same model
can therefore be routed with different thresholds by selecting different profiles.
Prompt-length bucketing#
Field |
Overrides |
Description |
|---|---|---|
|
|
Turn prompt-length bucketing on or off for this profile’s requests. |
|
|
Turn adaptive bucket serving on or off for this profile’s requests. It only takes effect while prompt-length bucketing is on. |
|
|
What the adaptive cut points balance: |
PD timeouts and aborts#
Field |
Overrides |
Description |
|---|---|---|
|
|
Seconds the gateway waits for the decode pod to accept the abort POST after a prefill failure. |
|
|
Seconds between the two abort attempts. |
|
|
Seconds a streaming SGLang PD request waits, after its prefill leg succeeded, for the decode pod’s first token (its first response body chunk) before the gateway fails it. |
|
|
The same bound for a non-streaming request, where the first body chunk is the finished answer. |
|
|
Longest gap, in seconds, between two body chunks of a streaming response before the gateway cuts the stream short. |
|
|
HTTP timeout in seconds for the prefill pod call. |
PD load imbalance and decode scoring#
Field |
Overrides |
Description |
|---|---|---|
|
|
Minimum prefill running-request spread (max minus min) that triggers prefill load-imbalance routing. |
|
|
Minimum decode running-request spread that triggers decode load-imbalance routing. |
|
|
Minimum decode token-throughput spread (tokens/s) that triggers throughput-imbalance routing. |
|
|
Max/min drain-rate score ratio above which the slowest decode pod is excluded. |
|
|
Weight of the running-request term in the decode load-balancing score. |
|
|
Weight of the token-throughput term in the decode load-balancing score. |
PD token load and prefix matching#
Field |
Overrides |
Description |
|---|---|---|
|
|
Weight of resident KV tokens in the token-load charge. |
|
|
Fixed per-request cost, in tokens, of the token-load charge. |
|
|
How long a charge may stay outstanding. |
|
|
How long the last prompt size of a session is kept. |
|
|
How much a full prefix match discounts a pod’s token load in the hybrid-cache-load prefill policy (0 to 1). |
|
|
Prefix-match percentage below which a match is treated as no match (0 to 100). |
Load-balance scoring#
Field |
Overrides |
Description |
|---|---|---|
|
|
Load-imbalance gate multiplier for pools of three or more replicas. |
|
|
Minimum running-request gap required to trigger the load-imbalance gate. |
|
|
Weight of engine-queued requests added to the load-balance score. |
|
|
Strength of the KV-pressure penalty in the load-balance score. |
|
|
Free-KV-cache fraction below which a replica scores |
Prefix cache and Preble#
Field |
Overrides |
Description |
|---|---|---|
|
|
How many standard deviations above the mean replica request count a prefix-match candidate may sit. Also read by the PD prefill candidacy filter. |
|
|
GPU the prefix-cache-preble cost model assumes. Known values: |
|
|
Assumed number of decoding tokens per request in the preble cost model. |
VTC fairness#
Field |
Overrides |
Description |
|---|---|---|
|
|
Running-request count at which the VTC utilization score saturates. |
|
|
Weight of the fairness term in the VTC score. |
|
|
Weight of the utilization term in the VTC score. |
|
|
Weight of a request’s input tokens in the VTC token tracker. Also scopes the tracker: the profile’s requests get one of their own. |
|
|
Weight of a request’s output tokens in that tracker. |
|
|
Sliding window of the profile’s tracker, in |
|
|
Bucket size of that window: |
|
|
Floor the profile’s tracker reports while its window holds little activity. |
|
|
Ceiling the profile’s tracker reports while its window holds little activity. |
Auto-blend weights#
Field |
Overrides |
Description |
|---|---|---|
|
|
Weight of the load-balance scorer blended behind every non-exclusive strategy. |
|
|
Weight of the |
|
|
Prefix-cache weight of the dedicated prefix-cache/load-balance ratio a bare |
|
|
Load-balance weight of that ratio. |
Scoped and environment-only knobs#
Some knobs configure state normally shared by the whole gateway process. A profile that sets one of them does not retune the shared state: the gateway scopes an instance to the resolved values, so profiles that agree share one and a profile that sets none keeps the shared instance exactly as before.
This is how vtc.inputTokenWeight, vtc.outputTokenWeight,
vtc.tokenTrackerWindowSize, vtc.tokenTrackerTimeUnit, vtc.tokenTrackerMinTokens and
vtc.tokenTrackerMaxTokens scope the VTC token tracker (at most 16 trackers per process; a
profile past the bound keeps the shared tracker).
Knobs that bound state shared by every model of a process stay environment-only and have no profile field:
the preble histogram window and eviction loop (
AIBRIX_ROUTER_PREBLE_SLIDING_WINDOW_PERIOD,AIBRIX_ROUTER_PREBLE_EVICTION_LOOP_INTERVAL)the session-affinity local cache capacity (
AIBRIX_SESSION_AFFINITY_MAX_LOCAL_KEYS)the routing string cache bound (
AIBRIX_ROUTER_MAX_CACHED_ALGORITHM_STRINGS)the token-load session table cap (
AIBRIX_TOKEN_LOAD_MAX_SESSIONS)
External Filter#
The external-filter header restricts the candidate pod pool using Kubernetes label selector syntax before the routing strategy makes its final selection. It is evaluated before routing and only takes effect when a routing-strategy is also set.
Supported selector forms:
key=valuekey in (a, b)key!=valuecomma-separated list of selectors
See the Kubernetes label selector reference for the full syntax.
curl -v http://${ENDPOINT}/v1/completions \
-H "Content-Type: application/json" \
-H "routing-strategy: random" \
-H "external-filter: environment=production,tier=frontend" \
-d '{
"model": "deepseek-r1-distill-llama-8b",
"prompt": "San Francisco is a",
"max_tokens": 128,
"temperature": 0
}'
Note
Filtering happens before the routing strategy and does not change which pod the strategy considers optimal.
It further narrows down the pods already selected by
model.aibrix.ai/name.If the filter eliminates all pods, the request fails with
no ready pods for routing.external-filteris optional. When omitted, no extra filtering is applied.
Request Priority Tier#
Some workloads are not latency-sensitive. A nightly batch job, for example, is usually fine with
answering after the interactive traffic of the same model has been served. The gateway can relay
that intent to the backend engine: when AIBRIX_PRIORITY_TIER_ENABLED=true, a request that
declares its tier with the x-aibrix-priority-tier header gets the matching priority value set in
its backend request body.
The gateway relies on the engine’s own scheduler rather than queuing requests itself. On vLLM, that
requires the deployment to run with a priority-aware scheduler such as
--scheduling-policy=priority, which serves smaller priority values first. The mapping is
therefore built to only de-prioritize: a tier never makes a request jump ahead of another one, so
enabling the feature cannot delay interactive traffic on a shared endpoint.
|
|
Notes |
|---|---|---|
|
|
Below the engine default of |
|
|
Yields to both interactive and |
(other or missing) |
(unchanged) |
Unmapped tiers, and requests without the header, are forwarded untouched. |
curl -v http://${ENDPOINT}/v1/chat/completions \
-H "Content-Type: application/json" \
-H "x-aibrix-priority-tier: batch" \
-d '{
"model": "deepseek-r1-distill-llama-8b",
"messages": [{"role": "user", "content": "Say this is a test!"}]
}'
Behavior notes:
The feature is off by default and the header is ignored entirely while it is off.
Requests that already carry a
priorityin their body keep the value the caller set, which makes an explicit per-request choice possible even for a tier the table maps.Tier names are matched case-insensitively.
A request that the gateway does not map is forwarded byte for byte, so callers that send something else in this header see no change in behavior.
Note
The supported tier names are fixed in this release. Per-tier values a deployment can tune, and giving latency-sensitive tiers an explicit head start, are candidates for a follow-up.
Rate Limiting#
The gateway supports per-user rate limiting on requests per minute (RPM) and tokens per minute (TPM). Pass the user header with a unique identifier to enable rate-limit enforcement for that client.
Create the user in the metadata service first:
kubectl -n aibrix-system port-forward svc/aibrix-metadata-service 8090:8090
curl http://localhost:8090/CreateUser \
-H "Content-Type: application/json" \
-d '{"name": "your-user-id", "rpm": 100, "tpm": 1000}'
curl -v http://${ENDPOINT}/v1/chat/completions \
-H "user: your-user-id" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer any_key" \
-d '{
"model": "your-model-name",
"messages": [{"role": "user", "content": "Say this is a test!"}],
"temperature": 0.7
}'
Note
If rate limiting is not needed, the user header can be omitted. To disable user RPM/TPM and model RPS quota enforcement on the gateway, set AIBRIX_DISABLE_RATE_LIMITING=true. See Deploying Gateway.
Prometheus API Access#
Some routing strategies rely on metrics queried from the Prometheus HTTP API (PromQL). Configure the endpoint and optional Basic Auth credentials with the following environment variables.
Environment Variable |
Default |
Description |
|---|---|---|
|
(empty) |
Prometheus HTTP API base URL (e.g. |
|
(empty) |
Kubernetes Secret name containing Basic Auth credentials. When set, takes precedence over the plaintext env vars below. |
|
|
Namespace of the Secret specified by |
|
|
Key in |
|
|
Key in |
|
(empty) |
Basic Auth username. Used only when |
|
(empty) |
Basic Auth password. Used only when |
Example (plaintext env vars)#
export PROMETHEUS_ENDPOINT="http://prometheus-operated.prometheus.svc:9090"
export PROMETHEUS_BASIC_AUTH_USERNAME="prom_user"
export PROMETHEUS_BASIC_AUTH_PASSWORD="prom_pass"
Example (Kubernetes Secret)#
apiVersion: v1
kind: Secret
metadata:
name: prometheus-basic-auth
namespace: aibrix-system
type: Opaque
stringData:
username: prom_user
password: prom_pass
export PROMETHEUS_ENDPOINT="http://prometheus-operated.prometheus.svc:9090"
export PROMETHEUS_BASIC_AUTH_SECRET_NAME="prometheus-basic-auth"
export PROMETHEUS_BASIC_AUTH_SECRET_NAMESPACE="aibrix-system"
Message Size Configuration#
Two independent size limits apply to requests flowing through the gateway. Both default to 4 MiB and must be kept in sync when you raise or lower the limit.
Gateway Plugin gRPC Buffer (AIBRIX_GRPC_MAX_MESSAGE_SIZE_BYTES)#
Envoy communicates with the AIBrix gateway plugin over an external-processing (ext_proc) gRPC connection. This env var controls the maximum size of a single gRPC message on that connection. Requests or responses larger than this value are rejected before they reach routing logic.
Configure it under gatewayPlugin.container.envs in values.yaml:
gatewayPlugin:
container:
envs:
AIBRIX_GRPC_MAX_MESSAGE_SIZE_BYTES: "4194304" # 4 MiB
Envoy Connection Buffer (clientTrafficPolicy.connection.bufferLimit)#
Envoy itself buffers each incoming HTTP connection up to bufferLimit bytes before it begins processing. If a request body exceeds this limit Envoy returns a 413 before the ext_proc filter is even invoked.
Configure it under gateway.clientTrafficPolicy in values.yaml:
gateway:
clientTrafficPolicy:
connection:
bufferLimit: 4194304 # 4 MiB
Keeping the two values in sync#
Because Envoy drops the connection before the gateway plugin sees it when bufferLimit is the binding limit, both values should always be set to the same size. When you need to support larger payloads (e.g. large KV-cache state-sync messages), update both fields together:
gatewayPlugin:
container:
envs:
AIBRIX_GRPC_MAX_MESSAGE_SIZE_BYTES: "16777216" # 16 MiB
gateway:
clientTrafficPolicy:
connection:
bufferLimit: 16777216 # 16 MiB
Observability & Telemetry#
The gateway plugins have built-in support for distributed tracing using OpenTelemetry (OTel). This allows you to trace end-to-end request lifecycles across the external processing pipeline.
Tracing is disabled by default to conserve system resources. To enable it, complete the following two steps.
Step 1: Enable OpenTelemetry in EnvoyProxy#
Enable openTelemetry.enable in your helm chart or add the telemetry configuration directly to your EnvoyProxy resource.
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
name: aibrix-custom-proxy-config
spec:
telemetry:
tracing:
samplingRate: 100
provider:
type: OpenTelemetry
openTelemetry:
# -- The hostname or IP address of the OpenTelemetry Collector/OTLP receiver.
# Do NOT include the protocol scheme (e.g., http://) or port here.
host: "otel-collector.monitoring.svc.cluster.local"
# -- The port of the OTLP gRPC receiver (Envoy strictly uses gRPC for this).
port: 4317
Step 2: Configure the OTLP Exporter Endpoint#
Next, you must explicitly configure an OTLP exporter endpoint in your environment. You can enable it by setting either the global endpoint or the traces-specific endpoint.
For example, if you are running a cluster local OTel Collector, inject the following environment variables:
env:
# Option 1: Global OTLP endpoint (Recommended)
# NOTE: If protocol is HTTP, the SDK automatically appends '/v1/traces' to this URL.
# If protocol is gRPC, it uses the base URL as-is.
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://otel-collector.monitoring.svc.cluster.local:4317"
# --- OR ---
# Option 2: Traces-specific endpoint (Uncomment to use instead of Option 1)
# The SDK uses this URL EXACTLY as-is without appending any paths.
# (Example below uses port 4318 for HTTP/Protobuf exports)
# - name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
# value: "http://otel-collector.monitoring.svc.cluster.local:4318/v1/traces"
# Specifies the transport protocol. Valid options: grpc, http, or http/protobuf
- name: OTEL_EXPORTER_OTLP_PROTOCOL
value: "grpc"
# Keeps TLS active but skips server certificate validation (useful for self-signed certs)
- name: OTEL_EXPORTER_OTLP_INSECURE_SKIP_VERIFY
value: "true"
# Set Headers if your OTel Collector or APM backend (e.g., Datadog, Honeycomb) requires authentication
- name: OTEL_EXPORTER_OTLP_HEADERS
value: "Authorization=Bearer {{ token }}"
Advanced Configuration#
For the full list of supported telemetry settings (including sampling rates, TLS options, and custom headers), see pkg/plugins/gateway/ENV_VARS.md.
Headers Reference#
This section describes the custom headers used in request processing for routing, debugging, and rate limiting.
Target and General Headers#
Header Name |
Direction |
Description |
|---|---|---|
|
Response |
Unique request ID associated with the client request. Useful for correlating logs. |
|
Backend request, Response |
Indicates whether request headers were processed correctly. Used for debugging header parsing issues. |
|
Backend request, Response |
Identifies the destination selected by the routing algorithm: the backend request receives its |
|
Response |
|
|
Request, Backend request, Response |
Selects the routing strategy for the request, forwards the applied strategy to the backend, and reports it in the response. |
|
Request |
Label selector expression used to further filter candidate pods before routing. |
|
Backend request |
Model name injected into the backend request when no explicit routing strategy is selected. |
|
Request |
Selects a model configuration profile for the request. |
|
Response |
Reports the concrete profile selected when |
|
Request, Response |
Session identifier used by |
|
Request |
Caller-owned opaque session key used by |
|
Request, Backend request |
W3C Trace Context propagated through requests initiated by the gateway, including prefill-decode routing. |
|
Response |
Name of the prefill pod selected by |
|
Response |
IP address of the prefill pod selected by |
|
Request |
Declares the priority tier of the request, for example |
Routing and Error Debugging Headers#
Header Name |
Description |
|---|---|
|
Identifies errors caused by incorrect user input. |
|
Indicates a routing failure, such as being unable to select a target pod. |
|
Signals that the response body could not be parsed, typically due to an internal error. |
|
Generic error header when no specific cause is identified. |
|
Marks a request body parsing failure, such as invalid JSON. |
|
Indicates that no model was specified in the request. |
|
Indicates that the requested model exists but has no active backend pods. |
|
Indicates that an unsupported routing strategy was specified. |
|
Marks a gateway-generated error because the prefill leg of a |
|
Marks a gateway-generated error because the decode watchdog fired after a successful prefill. See Prefill-Decode Disaggregation (PD). |
|
The request exceeded the model’s |
|
Error encountered while incrementing the model RPS counter. |
|
Every routable replica is at the profile’s |
|
The multipart/form-data body could not be parsed, typically on image, audio, or video endpoints. See vLLM-Omni Multi-Modal Serving. |
|
The video job ID is unknown, expired, out of scope, or its owning pod is gone. See vLLM-Omni Multi-Modal Serving. |
|
The video job’s owning pod is temporarily unroutable. See vLLM-Omni Multi-Modal Serving. |
Streaming Headers#
Header Name |
Description |
|---|---|
|
Signals an error during response streaming. |
|
Indicates an incorrect |
|
Indicates whether usage statistics were included in the streaming response. |
Rate Limiting Headers#
Header Name |
Description |
|---|---|
|
Indicates the RPM (requests per minute) counter was updated successfully. |
|
Indicates the TPM (tokens per minute) counter was updated successfully. |
|
Signals that the request exceeded the allowed RPM limit. |
|
Signals that the request exceeded the allowed TPM limit. |
|
Error encountered while incrementing the RPM counter. |
|
Error encountered while incrementing the TPM counter. |
Debugging Guidelines#
Identify error headers — inspect
x-error-routing,x-error-user,x-error-response-unmarshal, andx-error-response-unknownto locate the root cause. For request body issues, checkx-error-request-body-processingandx-error-no-model-in-request.Verify routing and model assignment — confirm
target-podis set. Ifx-error-no-model-in-requestorx-error-no-model-backendsappears, verify the request body includes a valid model name and that the model has active backend pods. Ifx-error-invalid-routing-strategyis present, confirm the strategy name is supported.Diagnose streaming issues — check
x-error-streamingandx-error-stream. If usage statistics are missing from a streaming response, inspectx-error-no-stream-options-include-usage.Investigate rate limiting — if a request was rejected, check
x-error-rpm-exceededorx-error-tpm-exceeded. If counters failed to update, look forx-error-incr-rpmorx-error-incr-tpm. Successful updates are indicated byx-update-rpmandx-update-tpm.
Check gateway object status#
Verify that all gateway objects have status.conditions == Accepted:
kubectl describe gatewayclass -n aibrix-system
kubectl describe gateway -n aibrix-system
kubectl describe envoypatchpolicy -n aibrix-system
# check for all objects
kubectl describe envoyextensionpolicy -n aibrix-system
# check for all objects
kubectl describe httproute -n aibrix-system
Collect logs#
Collect logs from the Envoy proxy and the gateway plugin for deeper investigation:
$ kubectl get pods -n envoy-gateway-system
NAME READY STATUS RESTARTS AGE
envoy-aibrix-system-aibrix-eg-903790dc-84ccfcbc6b-hw2lq 2/2 Running 0 13m
envoy-gateway-7c7659ffc9-rvm5s 1/1 Running 0 16m
$ kubectl logs envoy-aibrix-system-aibrix-eg-903790dc-84ccfcbc6b-hw2lq -n envoy-gateway-system
$ kubectl get pods -n aibrix-system
NAME READY STATUS RESTARTS AGE
aibrix-controller-manager-fb4495448-j9k6g 1/1 Running 0 22m
aibrix-gateway-plugins-6bd9fcd5b9-2bwpr 1/1 Running 0 22m
aibrix-gpu-optimizer-df9db96c8-2fctd 1/1 Running 0 22m
aibrix-kuberay-operator-5bf4985d86-7g4tz 1/1 Running 0 22m
aibrix-metadata-service-9d4cd7f77-mq7tr 1/1 Running 0 22m
aibrix-redis-master-7d6b77c794-bcqxc 1/1 Running 0 22m
$ kubectl logs aibrix-gateway-plugins-6bd9fcd5b9-2bwpr -n aibrix-system
See also
- Prefix-Cache Aware Routing
Prefix-cache aware routing and KV event sync.
- Prefill-Decode Disaggregation (PD)
Prefill-decode disaggregation, bucketing, and scoring policies.
- Multi-turn Conversation & Agentic Routing
Session affinity for multi-turn and concurrent agentic requests.
- vLLM Semantic Router
Topic-based routing through the vLLM Semantic Router ext_proc filter.
- vLLM-Omni Multi-Modal Serving
Multi-modal serving and sticky video job routing.
- External Replica Routing
Delegating replica selection to an operator-run decision service.
- AIBrix Gateway
Gateway architecture and how to add a routing algorithm.
- Deploying Gateway
Production replica counts, Redis, timeouts, and traffic policies.