Files
bifrost/docs/providers/routing-rules.mdx
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

1033 lines
30 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "Routing Rules"
description: "Configure dynamic, expression-based routing decisions using CEL expressions to control how requests are routed across providers."
icon: "chart-diagram"
---
## Overview
Routing Rules provide dynamic, expression-based control over request routing. They execute **before governance provider selection** and can override it, allowing you to make sophisticated routing decisions based on request context, headers, parameters, capacity metrics, and organizational hierarchy.
<Frame>
<img src="/media/ui-routing-tree.png" alt="Routing Rules Tree" />
</Frame>
Unlike governance routing (which uses static provider weights), routing rules use **CEL expressions** (Common Expression Language) to evaluate conditions at runtime and make routing decisions dynamically.
---
## How It Works
### Request Flow
<img src="/media/routing-rules-flow.svg" alt="Routing Rules Request Flow" />
### Scope Hierarchy & Precedence
Routing rules are organized by scope with **first-match-wins** evaluation:
```
VirtualKey Scope (Highest Priority)
Team Scope
Customer Scope
Global Scope (Lowest Priority, applies to all)
```
**How it works:**
1. When a request arrives with a Virtual Key, Bifrost builds a scope chain
2. Rules are evaluated in scope order (highest to lowest)
3. The **first matching rule** wins — no further rules are evaluated in that iteration
4. Within each scope, rules are sorted by **priority** (ascending: 0 evaluates before 10)
5. If the matched rule has `chain_rule: true`, the resolved provider/model becomes the new context and the full scope chain is re-evaluated from the top
6. If no rule matches (or the matched rule is terminal), the current decision is applied
7. If no rule ever matches, the incoming provider/model is used unchanged
**Example:**
```
VirtualKey (vk-123) is attached to Team (team-456),
which belongs to Customer (cust-789)
Evaluation order:
1. Check Virtual Key scope rules (vk-123)
2. Check Team scope rules (team-456)
3. Check Customer scope rules (cust-789)
4. Check Global scope rules
First match → Decision
```
## CEL Expression Guide
### Available Variables
Routing rules evaluate CEL expressions with these available variables:
#### Request Context
```cel
model // Requested model (string)
provider // Current provider (string)
request_type // Request type (chat_completion, embedding, batch, image_generation, moderation, transcription, translation)
```
#### Headers & Parameters
```cel
headers["header-name"] // Request header (case-insensitive key lookup)
params["param-name"] // Query parameter
```
**Header Examples:**
```cel
headers["x-tier"] == "premium"
headers["x-api-version"] == "v2"
headers["user-agent"].contains("mobile")
```
#### Organization Context
```cel
virtual_key_id // VK ID (string, empty if no VK)
virtual_key_name // VK name (string)
team_id // Team ID (string, empty if not in team)
team_name // Team name (string)
customer_id // Customer ID (string)
customer_name // Customer name (string)
```
**Organization Examples:**
```cel
team_name == "ml-research"
customer_id == "acme-corp"
virtual_key_name.startsWith("prod-")
```
#### Capacity Metrics (as percentages: 0-100)
```cel
budget_used // Budget usage percentage for provider/model (0.0 to 100.0)
tokens_used // Token rate limit usage percentage (0.0 to 100.0)
request // Request rate limit usage percentage (0.0 to 100.0)
```
**Capacity Examples:**
```cel
budget_used > 80 // Route to fallback when 80%+ of budget used
tokens_used < 50 // Route to fast provider when below 50% token limit
request > 90 // Switch providers when request limit near max
```
### CEL Operators & Functions
#### Comparison Operators
```cel
== // Equal
!= // Not equal
> // Greater than
< // Less than
>= // Greater or equal
<= // Less or equal
```
#### Logical Operators
```cel
&& // AND
|| // OR
! // NOT
```
#### String Functions
```cel
.startsWith("prefix") // Check string prefix
.endsWith("suffix") // Check string suffix
.contains("substring") // Check substring
.matches("regex") // Regex match
```
#### Collections
```cel
"value" in ["item1", "item2", "item3"] // Check membership
```
### Expression Examples
#### Simple Conditions
```cel
// Route based on header value
headers["x-tier"] == "premium"
// Route based on team
team_name == "research"
// Route based on model
model == "gpt-4o"
// Route based on request type
request_type == "embedding"
// Route to fallback when budget high
budget_used > 80
```
#### Complex Conditions (Multiple Criteria)
```cel
// Premium tier research team
headers["x-tier"] == "premium" && team_name == "research"
// High capacity or premium
budget_used > 90 || headers["x-priority"] == "high"
// Specific team and model
team_name == "ml-ops" && model.startsWith("claude-")
// Region-based with capacity check
headers["x-region"] == "us-east" && tokens_used < 75
// Route embeddings to cheaper provider
request_type == "embedding" && budget_used > 50
```
#### Pattern Matching
```cel
// Match models starting with prefix
model.startsWith("gpt-4")
// Match custom headers
headers["x-environment"] in ["staging", "testing"]
// Email domain matching
headers["x-user-email"].contains("@company.com")
// Regex patterns
headers["x-app-version"].matches("[0-9]+\\.[0-9]+\\.[0-9]+")
```
### Validation & Error Handling
- **Invalid CEL syntax** → Rule logs warning, skipped, evaluation continues
- **Missing header/parameter** → Expression returns false (graceful no-match)
- **Type mismatches** → Logged as warning, rule skipped
- **Empty expression** → Rule always matches (use `true`/`false` for explicit behavior)
---
## Configuration
<Tabs>
<Tab title="Web UI">
Access routing rules from the dashboard:
**Routing Rules Dashboard**
<Frame>
<img src="/media/ui-routing-rules-dashboard.png" alt="Routing Rules Dashboard" />
</Frame>
**Features:**
- List all rules with scope, priority, and enabled status
- Filter by scope or scope_id
- Create/Edit/Delete rules
- View rule expressions and targets
- Enable/disable rules without deletion
- Drag to reorder priority
**Create/Edit Rule Sheet**
<Frame>
<img src="/media/ui-create-routing-rule.png" alt="Create Routing Rule Dialog" />
</Frame>
**Fields:**
- **Name** (required): Unique rule identifier
- **Description** (optional): Internal notes
- **Enabled**: Toggle rule on/off
- **Chain Rule**: When enabled, the routing engine re-evaluates all rules after this one matches, using the resolved provider/model as the new context. See [Rule Chaining](#rule-chaining).
- **CEL Expression**: Visual or manual expression builder
- **Targets** (required): One or more weighted routing targets — each has Provider (optional), Model (optional), API Key (optional, requires Provider to be set), and Weight (%). Weights must sum to 1. When multiple targets are defined, one is selected probabilistically at request time.
- **Fallbacks** (optional): Array of fallback providers
- **Scope**: Where rule applies (global, customer, team, virtual_key)
- **Scope ID**: Required if scope is not global
- **Priority**: Lower = evaluated first (default: 0)
### Visual CEL Builder
The dashboard includes a visual query builder for CEL expressions:
- **Condition Builder**: Select field, operator, value
- **Logical Operators**: Combine conditions with AND/OR
- **Manual Mode**: Switch to edit CEL directly
- **Validation**: Real-time syntax validation
- **Conversion**: Auto-converts visual rules to CEL
</Tab>
<Tab title="API">
### List Routing Rules
```bash
GET /api/governance/routing-rules
# Optional query parameters:
?scope=global&scope_id=<id>&from_memory=true
```
**Response:**
```json
{
"rules": [
{
"id": "rule-uuid-123",
"name": "Premium Tier Route",
"description": "Route premium users to fast provider",
"enabled": true,
"chain_rule": false,
"cel_expression": "headers[\"x-tier\"] == \"premium\"",
"targets": [
{ "provider": "openai", "model": "gpt-4o", "weight": 0.7 },
{ "provider": "azure", "model": "gpt-4o", "weight": 0.3 }
],
"fallbacks": ["groq/gpt-3.5-turbo"],
"scope": "global",
"scope_id": null,
"priority": 10,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
],
"count": 1
}
```
### Get Single Rule
```bash
GET /api/governance/routing-rules/{rule_id}
```
### Create Rule
```bash
POST /api/governance/routing-rules
Content-Type: application/json
```
**Request Body:**
```json
{
"name": "Budget Overflow Route",
"description": "When budget is high, route to cheaper provider",
"enabled": true,
"cel_expression": "budget_used > 85",
"targets": [
{ "provider": "groq", "weight": 1 }
],
"fallbacks": ["openai/gpt-4o"],
"scope": "team",
"scope_id": "team-uuid-456",
"priority": 5
}
```
**Response:** `201 Created`
```json
{
"message": "Routing rule created successfully",
"rule": { /* rule object */ }
}
```
### Update Rule
```bash
PUT /api/governance/routing-rules/{rule_id}
Content-Type: application/json
```
**Request Body (all fields optional):**
```json
{
"name": "Updated Rule Name",
"enabled": false,
"cel_expression": "budget_used > 90",
"priority": 20
}
```
### Delete Rule
```bash
DELETE /api/governance/routing-rules/{rule_id}
```
**Response:** `200 OK`
```json
{
"message": "Routing rule deleted successfully"
}
```
</Tab>
<Tab title="config.json">
Define routing rules in your `config.json` file under the governance configuration:
**Structure:**
```json
{
"governance": {
"routing_rules": [
{
"id": "rule-uuid-123",
"name": "Premium Tier Route",
"description": "Route premium users to fast provider",
"enabled": true,
"cel_expression": "headers[\"x-tier\"] == \"premium\"",
"targets": [
{ "provider": "openai", "model": "gpt-4o", "weight": 0.7 },
{ "provider": "azure", "model": "gpt-4o", "weight": 0.3 }
],
"fallbacks": ["groq/gpt-3.5-turbo"],
"scope": "global",
"scope_id": null,
"priority": 10
},
{
"id": "rule-uuid-456",
"name": "Budget Overflow Route",
"description": "Route to cheaper provider when budget is high",
"enabled": true,
"cel_expression": "budget_used > 85",
"targets": [
{ "provider": "groq", "model": "llama-2-70b", "weight": 1 }
],
"fallbacks": [],
"scope": "team",
"scope_id": "team-ml-ops",
"priority": 5
}
]
}
}
```
**Fields:**
- **id** (string, auto-generated): Unique rule identifier (UUID)
- **name** (string, required): Rule name (must be unique within scope)
- **description** (string, optional): Internal documentation
- **enabled** (boolean): Whether rule is active
- **chain_rule** (boolean, default: `false`): When `true`, re-evaluates the full routing chain after this rule matches, using the resolved provider/model as the new context. See [Rule Chaining](#rule-chaining).
- **cel_expression** (string): CEL expression for rule matching
- **targets** (array, required): One or more routing targets. Each target has:
- `provider` (string, optional): Target provider — omit to use the incoming request provider
- `model` (string, optional): Target model — omit to use the incoming request model
- `key_id` (string, optional): UUID of the API key to pin — requires `provider` to be present; omit for load-balanced key selection
- `weight` (number, required): Probability weight — all weights in a rule must sum to 1 (e.g. 0.7 + 0.3 = 1.0)
- **fallbacks** (array[string]): Fallback providers in "provider/model" format
- **scope** (string): Scope level - "global", "customer", "team", or "virtual_key"
- **scope_id** (string, optional): ID of scoped entity (null for global scope)
- **priority** (number): Rule evaluation order within scope (lower = evaluated first)
**Loading from config.json:**
Routes are automatically loaded on startup from the `config.json` governance section. Changes require application restart.
**Example with Multiple Rules:**
```json
{
"governance": {
"routing_rules": [
{
"id": "tier-based",
"name": "Premium Tier Fast Track",
"enabled": true,
"cel_expression": "headers[\"x-tier\"] == \"premium\"",
"targets": [
{ "provider": "openai", "model": "gpt-4o", "weight": 1 }
],
"fallbacks": ["azure/gpt-4o"],
"scope": "global",
"priority": 0
},
{
"id": "capacity-failover",
"name": "Budget Exhaustion Fallback",
"enabled": true,
"cel_expression": "budget_used > 90",
"targets": [
{ "provider": "groq", "model": "llama-2-70b", "weight": 1 }
],
"fallbacks": [],
"scope": "global",
"priority": 5
},
{
"id": "team-preference",
"name": "ML Team Anthropic Route",
"enabled": true,
"cel_expression": "team_name == \"ml-research\"",
"targets": [
{ "provider": "anthropic", "model": "claude-3-opus-20240229", "weight": 1 }
],
"fallbacks": ["bedrock/claude-3-opus"],
"scope": "team",
"scope_id": "team-ml-research",
"priority": 0
}
]
}
}
```
</Tab>
</Tabs>
---
## Real-World Use Cases
**When to use Routing Rules:**
- Dynamic routing based on request headers or parameters
- Capacity-based routing (route to fallback when budget/rate limit is high)
- Organization-based routing (different rules for different teams/customers)
- A/B testing or canary deployments
- Conditional provider override based on complex logic
### Use Case 1: Tier-Based Routing
Route requests based on customer tier using headers:
```json
{
"name": "Premium Tier Fast Track",
"cel_expression": "headers[\"x-tier\"] == \"premium\"",
"targets": [
{ "provider": "openai", "model": "gpt-4o", "weight": 1 }
],
"fallbacks": ["azure/gpt-4o"],
"scope": "global",
"priority": 10
}
```
### Use Case 2: Capacity-Based Failover
Route to cheaper provider when budget is exhausted:
```json
{
"name": "Budget Exhaustion Fallback",
"cel_expression": "budget_used > 90",
"targets": [
{ "provider": "groq", "model": "llama-2-70b", "weight": 1 }
],
"fallbacks": [],
"scope": "global",
"priority": 5
}
```
### Use Case 3: Team-Specific Routing
Route team-specific requests to their preferred provider:
```json
{
"name": "ML Team Anthropic Preference",
"cel_expression": "team_name == \"ml-research\"",
"targets": [
{ "provider": "anthropic", "model": "claude-3-opus-20240229", "weight": 1 }
],
"fallbacks": ["bedrock/claude-3-opus"],
"scope": "team",
"scope_id": "team-ml-research-uuid",
"priority": 0
}
```
### Use Case 4: Complex Multi-Condition Routing
Combine multiple criteria for sophisticated routing:
```json
{
"name": "Production Premium Route",
"cel_expression": "headers[\"x-environment\"] == \"production\" && headers[\"x-priority\"] == \"high\" && tokens_used < 75",
"targets": [
{ "provider": "openai", "model": "gpt-4o", "weight": 1 }
],
"fallbacks": ["azure/gpt-4o"],
"scope": "global",
"priority": 5
}
```
### Use Case 5: Probabilistic A/B Testing
Split traffic across providers or models by weight for canary deployments or cost optimization:
```json
{
"name": "Split Traffic OpenAI vs Groq",
"cel_expression": "true",
"targets": [
{ "provider": "openai", "model": "gpt-4o", "weight": 0.7 },
{ "provider": "groq", "model": "llama-3.1-70b", "weight": 0.3 }
],
"scope": "global",
"priority": 15
}
```
Each request matching this rule has a 70% chance of going to OpenAI and a 30% chance of going to Groq. Weights must always sum to 1.
### Use Case 6: Regional Routing
Route based on region headers:
```json
{
"name": "EU Data Residency",
"cel_expression": "headers[\"x-region\"] == \"eu\"",
"targets": [
{ "provider": "azure", "model": "gpt-4o", "weight": 1 }
],
"fallbacks": [],
"scope": "global",
"priority": 0
}
```
---
## Rule Chaining
<Info>Rule chaining is available in **Bifrost v1.5.0-prerelease2 and above**.</Info>
Rule chaining allows routing rules to be composed together. When a rule has `chain_rule: true`, the routing engine does not stop after it matches — instead, it updates the request context with the resolved provider/model and re-evaluates the full rule set from the top.
### How Chaining Works
```
Request arrives (provider=openai, model=gpt-4)
Rule 1 matches (chain_rule=true) → resolves model to gpt-4-turbo
Re-evaluate all rules with (provider=openai, model=gpt-4-turbo)
Rule 2 matches (chain_rule=false) → resolves provider to azure
Final decision: azure / gpt-4-turbo
```
### Termination Conditions
The chain stops when any of the following occurs:
| Condition | Description |
|---|---|
| **No match** | Current iteration finds no matching rule |
| **Terminal rule** | Matched rule has `chain_rule: false` (the default) |
| **Convergence** | Provider and model are unchanged after a chain step — continuing would loop forever |
### Decision Accumulation
Each chain step overwrites the previous decision — the last matched rule wins for all fields:
| Field | Strategy |
|---|---|
| Provider | Last matched rule's target |
| Model | Last matched rule's target |
| API Key | Last matched rule's target (empty = use pool) |
| Fallbacks | Last matched rule's fallbacks |
Every chain step is logged in the routing engine audit trail for full observability.
### Configuration Example
```json
{
"governance": {
"routing_rules": [
{
"id": "normalize-alias",
"name": "Normalize gpt-4 Alias",
"enabled": true,
"chain_rule": true,
"cel_expression": "model == \"gpt-4\"",
"targets": [{ "model": "gpt-4-turbo", "weight": 1 }],
"scope": "global",
"priority": 0
},
{
"id": "route-gpt4-turbo",
"name": "Route gpt-4-turbo to Azure",
"enabled": true,
"chain_rule": false,
"cel_expression": "model == \"gpt-4-turbo\"",
"targets": [{ "provider": "azure", "model": "gpt-4-turbo", "weight": 1 }],
"scope": "global",
"priority": 1
}
]
}
}
```
**Result:** A request with `model=gpt-4` is normalized to `gpt-4-turbo` by Rule 1 (chain continues), then routed to Azure by Rule 2 (chain stops).
### Use Cases
- **Model alias normalization**: Rewrite short aliases to canonical model names before routing
- **Tiered policy application**: Apply a team-level override first, then a global key-pinning rule
- **Feature flag injection**: A chain rule sets the target to an experimental model; a downstream rule routes that model to the right provider
- **Budget-aware escalation**: A chain rule downgrades the model when budget is high; the next rule routes the downgraded model appropriately
### Best Practices
- Keep chains short (23 steps) — long chains are harder to reason about
- Ensure the last rule in every intended chain path is terminal (`chain_rule: false`) to prevent unintended continuation
- Use convergence detection as a safety net, not a primary termination strategy — if you rely on it, your rules likely have a logic gap
- Name chain rules clearly to reflect their role: "Normalize X", "Enrich context", etc.
---
## Integration with Governance & Load Balancing
### Interaction with Governance Routing
Routing Rules run **BEFORE** governance provider selection and can override it:
**If a routing rule matches:**
```
1. Routing Rules → CEL expression evaluation (first-match-wins)
2. Rule matches → target selected probabilistically from targets array
3. provider/model/key_id/fallbacks overridden from selected target
4. Governance provider_configs → SKIPPED
5. Load Balancing → selects best key (unless key_id was pinned)
```
**If no routing rule matches:**
```
1. Routing Rules → CEL expression evaluation
2. No match → continue
3. Governance routing → provider/model selection (weighted random)
4. Load Balancing → selects best key
```
**Example:**
- Governance configures: 70% Azure, 30% OpenAI
- Routing rule exists: `budget_used > 85 → groq`
- Request arrives with budget_used = 90%
- **Result**: Groq selected by routing rule, governance provider_configs **ignored**
### Interaction with Load Balancing
Routing rules determine provider BEFORE adaptive load balancing runs:
```
1. Routing Rules evaluate first → determine provider (if matched)
OR
2. Governance selects provider (if no routing rule matched)
3. Load Balancing Level 1 → skipped (provider already determined by routing rules or governance)
4. Load Balancing Level 2 → key selection (performance-based within selected provider)
```
**Key Insight:** Load balancing Level 2 (key selection) always runs regardless of whether the provider was determined by routing rules or governance. This means you get automatic key-level optimization in all cases.
### Fallback Chain
Routing rules can define fallbacks that flow into load balancing:
```json
{
"provider": "openai",
"fallbacks": ["azure/gpt-4o", "groq/gpt-3.5-turbo"]
}
```
If OpenAI fails:
1. Level 2 load balancing evaluates Azure keys
2. If all Azure keys fail, tries Groq
---
## Execution & Performance
### CEL Compilation
- **First evaluation**: CEL expression is compiled into a bytecode program
- **Subsequent evaluations**: Program is cached and reused
- **Performance**: Cached program evaluation is very fast (microseconds)
- **Memory**: Compiled programs cached in memory until Bifrost restart
### Priority & Ordering
Rules within the same scope are evaluated in **ascending priority order**:
```
Priority 0 → Priority 5 → Priority 10 → Priority 100 (first match wins)
```
**Best Practice:** Use priority 0-10 for critical rules, 100+ for fallbacks.
### Optimization Tips
1. **Order rules by likelihood**: Put frequently matching rules first
2. **Use specific scopes**: Avoid global scope when possible (narrower = faster)
3. **Avoid expensive string operations**: Prefer `==` over `.matches()` with regex
4. **Keep expressions simple**: Complex conditions increase evaluation time
5. **Use reasonable priorities**: Gaps in priorities (0, 10, 20) make reordering easy
---
## Best Practices
<AccordionGroup>
<Accordion title="Rule Naming">
✅ **Good names:**
- "Premium Tier Fast Track"
- "Budget Exhaustion Fallback"
- "ML Team Anthropic Route"
- "Production High Priority Route"
❌ **Bad names:**
- "Rule 1"
- "Fix"
- "Temp"
- "TODO"
</Accordion>
<Accordion title="CEL Expression Safety">
✅ **Safe patterns:**
```cel
headers["x-tier"] == "premium" // Exact match
headers["x-region"] in ["us", "eu", "asia"] // Membership
team_name.startsWith("prod-") // Prefix check
budget_used > 80 // Numeric comparison
```
❌ **Risky patterns:**
```cel
headers["x-tier"].matches(".*premium.*") // Complex regex
headers["x-config"].contains("json") // Fragile
model.length() > 5 && ... // Undocumented behavior
```
</Accordion>
<Accordion title="Scope Management">
✅ **Good scope design:**
- Global rules for organization-wide policies
- Customer scope for compliance (EU, data residency)
- Team scope for team preferences
- Virtual Key scope for specific integrations
❌ **Avoid:**
- Too many virtual key-level rules (maintenance nightmare)
- Conflicting rules across scopes
- Rules that duplicate governance routing
</Accordion>
<Accordion title="Testing & Validation">
✅ **Validate before deployment:**
1. Test CEL expression with expected headers
2. Verify provider/model exist in your setup
3. Check fallbacks are valid providers
4. Confirm scope_id matches actual entity
5. Test with `from_memory=true` to verify in-memory state
❌ **Don't:**
- Deploy rules without testing
- Use nonexistent providers
- Create circular fallback chains
</Accordion>
<Accordion title="Monitoring">
✅ **Track rule usage:**
- Log which rules match (logged in Bifrost logs as `[RoutingEngine]`)
- Monitor routing decisions by scope
- Alert on unexpected provider selection patterns
- Review priority order occasionally
❌ **Don't forget:**
- Disabling unused rules (instead of deleting)
- Updating documentation when rules change
- Testing failover chains
</Accordion>
</AccordionGroup>
---
## Troubleshooting
### Rule Not Matching
**Symptom**: Rule expression is correct but doesn't match
**Diagnosis**:
1. Check if rule is **enabled** (`enabled: true`)
2. Verify **scope matches** (check VirtualKey's team/customer hierarchy)
3. Check rule **priority** vs other rules in scope (lower priority evaluates first)
4. Verify **variable values**: Use `from_memory=true` to debug
**Solutions**:
```bash
# Get current routing rules in memory
GET /api/governance/routing-rules?from_memory=true
# Check if your variables are present
# Example: Is team_name actually set?
# Verify headers are lowercase in CEL
```
### Expression Compilation Error
**Symptom**: "Failed to compile rule: invalid CEL syntax"
**Common causes**:
- Unclosed quotes: `headers["x-tier` (missing closing quote)
- Invalid operators: `headers["x"] ??` (not standard CEL)
- String escaping: `headers["x-\type"]` (incorrect escape)
**Solutions**:
1. Use the visual CEL builder to avoid syntax errors
2. Test expressions incrementally
3. Check CEL operator documentation above
4. Wrap complex expressions in parentheses: `(A && B) || (C && D)`
### Wrong Provider Selected
**Symptom**: Request routed to unexpected provider
**Diagnosis**:
1. Multiple rules matching? (first-match-wins means earlier rules take precedence)
2. Governance routing already determined provider? (check scope hierarchy)
3. Load balancing changed key? (rule sets provider, LB sets key)
**Solutions**:
1. Lower priority of matching rules
2. Verify scope precedence (VirtualKey > Team > Customer > Global)
3. Check if another rule has lower priority and matches first
4. Review logs: `[RoutingEngine] Rule matched! Decision: provider=...`
### Header/Parameter Not Found
**Symptom**: "no such key" error in CEL evaluation
**This is normal!** Bifrost treats missing headers as non-matches:
```cel
headers["x-optional"] == "value" # Returns false if header missing
```
If you need to check if header exists:
```cel
headers["x-optional"] != "" # True only if present and non-empty
```
### Debugging with Logs
Enable debug logging to see routing rule evaluation:
```
[RoutingEngine] Starting rule evaluation for provider=openai, model=gpt-4o
[RoutingEngine] Scope chain: [virtual_key(vk-123) team(team-456) customer(cust-789) global]
[RoutingEngine] Evaluating scope=virtual_key, scopeID=vk-123, ruleCount=2
[RoutingEngine] Evaluating rule: id=rule-1, name=Premium Route, expression=headers["x-tier"]=="premium"
[RoutingEngine] Rule rule-1 evaluation result: matched=false
[RoutingEngine] Evaluating rule: id=rule-2, name=Budget Fallback, expression=budget_used>80
[RoutingEngine] Rule rule-2 evaluation result: matched=true
[RoutingEngine] Rule matched! Selected target: provider=groq, model=gpt-3.5-turbo (weight=1), fallbacks=[azure/gpt-4o]
```
---
## API Reference
### Request/Response Examples
#### Create Capacity-Based Rule
```bash
curl -X POST http://localhost:8080/api/governance/routing-rules \
-H "Content-Type: application/json" \
-d '{
"name": "High Budget Fallback",
"description": "Switch to cheaper provider when budget >85%",
"enabled": true,
"cel_expression": "budget_used > 85",
"targets": [
{ "provider": "groq", "model": "llama-2-70b", "weight": 1 }
],
"fallbacks": ["openai/gpt-3.5-turbo"],
"scope": "global",
"priority": 10
}'
```
#### Create Probabilistic Split Rule
```bash
curl -X POST http://localhost:8080/api/governance/routing-rules \
-H "Content-Type: application/json" \
-d '{
"name": "Premium Tier Split",
"cel_expression": "headers[\"x-tier\"] == \"premium\"",
"targets": [
{ "provider": "openai", "model": "gpt-4o", "weight": 0.7 },
{ "provider": "azure", "model": "gpt-4o", "weight": 0.3 }
],
"scope": "global",
"priority": 5
}'
```
#### Create Rule with Pinned API Key
```bash
curl -X POST http://localhost:8080/api/governance/routing-rules \
-H "Content-Type: application/json" \
-d '{
"name": "Pin Production Key for Premium Tier",
"description": "Always use the dedicated production key for premium requests",
"enabled": true,
"cel_expression": "headers[\"x-tier\"] == \"premium\"",
"targets": [
{
"provider": "openai",
"model": "gpt-4o",
"key_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"weight": 1
}
],
"scope": "global",
"priority": 5
}'
```
#### List Rules by Team Scope
```bash
curl http://localhost:8080/api/governance/routing-rules \
-H "Authorization: Bearer your-token" \
-G \
--data-urlencode "scope=team" \
--data-urlencode "scope_id=team-uuid-123"
```
#### Get In-Memory Rules (Debug)
```bash
curl http://localhost:8080/api/governance/routing-rules?from_memory=true \
-H "Authorization: Bearer your-token"
```
---
## Additional Resources
<CardGroup cols={2}>
<Card title="Provider Routing" icon="route" href="/providers/provider-routing">
Understand how routing rules fit into the complete routing pipeline
</Card>
<Card title="Virtual Keys" icon="key" href="/features/governance/virtual-keys">
Configure Virtual Keys that scope routing rules
</Card>
<Card title="Governance" icon="shield-check" href="/features/governance/routing">
Learn about the governance layer (applied after routing rules determine provider selection when no rule matches)
</Card>
<Card title="CEL Language Spec" icon="code" href="https://github.com/google/cel-spec">
Complete CEL expression language documentation
</Card>
</CardGroup>