How Routing Works
When a request arrives at the Gateway, the routing engine determines which provider and model to use. The process follows this order:
- Model resolution — The requested model name is matched to a configured provider
- Routing rules — CEL-based rules are evaluated in scope precedence order
- Health check — The health tracker adjusts traffic weights based on provider health
- Key selection — A key is selected based on configured weights and model access
- Fallback chain — If the primary provider fails, fallbacks are attempted in order
Provider Priority
The Gateway uses a tiered provider strategy for sovereign deployments:
| Priority | Provider | Use Case |
|---|---|---|
| 1 | Groq | Development and high-throughput chat inference |
| 2 | Azure UAE North | Sovereign embeddings and production chat |
| 3 | Sovereign Cloud | Fallback when primary providers are unavailable |
Requests are routed to the provider that owns the requested model. If a model exists on multiple providers (e.g., gpt-4o on both Azure and sovereign cloud), routing rules and key weights determine the selection.
Model Routing
Models are addressed by their name as configured in the provider's keys[].models array. When multiple providers host the same model name, prefix with the provider key to disambiguate:
# Route to Groq explicitly
curl http://localhost:8080/v1/chat/completions \
-d '{"model": "groq/llama-3.3-70b-versatile", "messages": [...]}'
# Route to sovereign cloud explicitly
curl http://localhost:8080/v1/chat/completions \
-d '{"model": "sovereign/gpt-4o", "messages": [...]}'
# Let the Gateway pick the best provider
curl http://localhost:8080/v1/chat/completions \
-d '{"model": "llama-3.3-70b-versatile", "messages": [...]}'
CEL-Based Routing Rules
The routing engine supports CEL (Common Expression Language) expressions for dynamic routing decisions. Rules are evaluated in scope precedence order with first-match-wins semantics.
Scope Precedence
Rules are evaluated from the most specific scope to the least specific:
- Virtual Key — Rules scoped to a specific API key
- Team — Rules scoped to a team
- Customer — Rules scoped to a customer/organization
- Global — Rules that apply to all requests
The first rule that matches short-circuits evaluation. If no rules match, default routing applies.
Available Variables
CEL expressions have access to the following variables:
| Variable | Type | Description |
|---|---|---|
model | string | Requested model name |
provider | string | Current provider |
request_type | string | chat_completion, embedding, text_completion |
headers | map[string]string | Request headers (lowercase keys) |
params | map[string]string | Query parameters |
virtual_key_id | string | Virtual key identifier |
virtual_key_name | string | Virtual key name |
team_id | string | Team identifier |
team_name | string | Team name |
customer_id | string | Customer identifier |
customer_name | string | Customer name |
budget_used | double | Percentage of budget consumed (0.0-100.0) |
tokens_used | double | Percentage of token rate limit consumed |
request | double | Percentage of request rate limit consumed |
Example Rules
Route embedding requests to Azure:
request_type == "embedding"
With target provider azure and model text-embedding-3-large-uaenorth.
Route Arabic models to Groq:
model == "allam-2-7b" || model.contains("arabic")
Failover when budget exceeds 80%:
budget_used > 80.0 && provider == "azure"
With fallback chain ["groq/llama-3.3-70b-versatile", "sovereign/gpt-4o"].
Route by header (tenant isolation):
headers["x-tenant-id"] == "ministry-of-finance"
Route by team with request type filter:
team_name == "security-team" && request_type == "chat_completion"
Supported CEL Operators
| Operator | Example |
|---|---|
| Equality | model == "gpt-4o" |
| Comparison | budget_used > 80.0 |
| Logical | provider == "azure" && request_type == "embedding" |
| String methods | model.startsWith("llama"), model.contains("70b") |
| Map access | headers["x-tenant-id"], params["region"] |
| Membership | model in ["gpt-4o", "gpt-4.1"] |
Adaptive Load Balancing
The health tracker uses EWMA (Exponentially Weighted Moving Average) to monitor error rates and latency per provider/model pair. It implements a 4-state machine that automatically adjusts traffic distribution.
Health States
| State | Traffic Weight | Description |
|---|---|---|
| Healthy | 100% | Normal operation |
| Degraded | 50% | Elevated errors or latency |
| Failed | 0% | Circuit open, no traffic |
| Recovering | 10% | Probe traffic to verify recovery |
State Transitions
Healthy ──→ Degraded ──→ Failed ──→ Recovering ──→ Healthy
↑ │
└─────────────────────────┘
| From | To | Condition |
|---|---|---|
| Healthy | Degraded | Error rate > 5% OR latency > 2x baseline |
| Healthy | Failed | Error rate > 20% |
| Degraded | Failed | Error rate > 20% OR latency > 5x baseline |
| Degraded | Healthy | Error rate drops below 5% AND latency below 2x baseline |
| Failed | Recovering | 30-second cooldown elapsed |
| Recovering | Healthy | 10 consecutive successful probes |
| Recovering | Failed | Error rate > 5% during recovery |
EWMA Parameters
| Parameter | Value | Description |
|---|---|---|
| Alpha | 0.3 | Recent observations weighted 30% |
| Baseline alpha | 0.05 | Slow-moving average during Healthy state only |
| Counter decay | 0.9 | Applied periodically to prevent stale data from dominating |
| Minimum samples | 5 | Required before any state transition |
Circuit Breaker
The health tracker requires a minimum of 5 samples before triggering any state transition. This prevents premature circuit breaking from isolated errors during low-traffic periods.
Fallback Strategies
Per-Key Retries
Each provider's network_config defines retry behavior for transient failures (timeouts, 5xx responses):
{
"network_config": {
"max_retries": 3,
"retry_backoff_initial_ms": 100,
"retry_backoff_max_ms": 5000
}
}
Retries use exponential backoff capped at retry_backoff_max_ms.
Cross-Provider Fallback
Routing rules can define fallback chains that span multiple providers:
{
"provider": "azure",
"model": "gpt-4o-uaenorth",
"fallbacks": ["groq/llama-3.3-70b-versatile", "sovereign/gpt-4o"]
}
If the primary provider fails after exhausting retries, the Gateway attempts each fallback in order. Fallback providers are subject to the same health tracking — a failed provider is skipped in the fallback chain.
Key Weight Distribution
When a provider has multiple API keys, traffic is distributed by weight:
{
"keys": [
{ "name": "key-a", "value": "env.KEY_A", "models": ["gpt-4o"], "weight": 0.7 },
{ "name": "key-b", "value": "env.KEY_B", "models": ["gpt-4o"], "weight": 0.3 }
]
}
Key A receives approximately 70% of traffic and Key B receives 30%. This is useful for distributing load across multiple API accounts with different rate limits.
Governance Integration
The routing engine integrates with the Gateway governance layer for hierarchical budget and rate limit enforcement:
- Customer-level budgets — Monthly spend caps per organization
- Team-level budgets — Departmental spending limits
- Virtual key budgets — Per-key token and cost limits
- Rate limits — Requests per minute and tokens per minute, per scope
When a budget or rate limit is exceeded, the Gateway returns a 429 Too Many Requests response. Routing rules can use budget_used and tokens_used variables to proactively shift traffic before limits are hit.
Request Flow Diagram
Client Request
│
▼
┌─────────────┐
│ RBAC/OIDC │ ← Authenticate & authorize
└──────┬──────┘
│
┌──────▼──────┐
│ Guardrails │ ← Content safety scan (pre)
└──────┬──────┘
│
┌──────▼──────┐
│ Routing │ ← CEL rules + health weights
└──────┬──────┘
│
┌──────▼──────┐
│ Provider │ ← Upstream API call
└──────┬──────┘
│
┌──────▼──────┐
│ Cost Track │ ← Token + cost recording
└──────┬──────┘
│
┌──────▼──────┐
│ Audit │ ← Event logging
└──────┬──────┘
│
Client Response