| name | openrouter-routing-rules |
| description | Define custom routing rules for OpenRouter requests based on user tier, task type, cost budget, and availability. Triggers: 'openrouter rules', 'routing rules', 'custom routing openrouter', 'conditional model selection'.
|
| allowed-tools | Read, Write, Edit, Grep, Bash(python3:*) |
| version | 1.20.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
| tags | ["saas","openrouter","routing","rules-engine"] |
| compatibility | Designed for Claude Code |
OpenRouter Routing Rules
Overview
Beyond simple task-based model selection, production systems need configurable routing rules that consider user tier, cost budget, time of day, model availability, and feature requirements. This skill covers building a rules engine for OpenRouter model selection with config-driven rules, dynamic conditions, and override capabilities.
Prerequisites
- An OpenRouter API key (
sk-or-v1-...) exported as OPENROUTER_API_KEY โ see the openrouter-install-auth skill for setup
- Python 3.8+ with the OpenAI SDK โ the rules engine itself is stdlib (
dataclasses, json, random) layered on top
- Per-request metadata available in your app: user tier, task type, remaining budget, tool/vision needs, latency SLA (the
RoutingContext fields)
- Budget tracking wired up (see
openrouter-cost-controls) if you use budget-conditioned rules like low-budget
Instructions
- Model each request's metadata as a
RoutingContext (user tier, task type, budget remaining, tools/vision flags, latency SLA) per Rules Engine.
- Define
RoutingRule entries in priority order โ free-tier first, then budget, capability (tools/vision), task type, latency, and always a priority=99 default catch-all.
- Resolve the winning rule with
evaluate_rules(ctx): first match by ascending priority wins; failing conditions return False instead of raising.
- Execute through
routed_completion() per Routed Completion โ it applies the rule's model, fallback chain (models + route: "fallback"), and max_tokens.
- To make rules hot-reloadable, express them as JSON per Config-Driven Rules and match with
match_config_rule() instead of lambdas.
- Validate any rule change on a slice of traffic with
ab_test_routing() per A/B Testing Rules before full rollout.
Rules Engine
import os, json, time
from dataclasses import dataclass
from typing import Optional, Callable
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
default_headers={"HTTP-Referer": "https://my-app.com", "X-Title": "my-app"},
)
@dataclass
class RoutingContext:
user_tier: str = "free"
task_type: str = "general"
budget_remaining: float = 0.0
prompt_tokens_est: int = 0
needs_tools: bool = False
needs_vision: bool = False
max_latency_ms: int = 30000
@dataclass
class RoutingRule:
name: str
priority: int
condition: Callable[[RoutingContext], ]
model:
fallbacks: [] =
max_tokens: =
() -> :
:
.condition(ctx)
Exception:
RULES = [
RoutingRule(
name=,
priority=,
condition= ctx: ctx.user_tier == ,
model=,
fallbacks=[],
max_tokens=,
),
RoutingRule(
name=,
priority=,
condition= ctx: ctx.budget_remaining < ctx.user_tier != ,
model=,
fallbacks=[],
max_tokens=,
),
RoutingRule(
name=,
priority=,
condition= ctx: ctx.needs_tools,
model=,
fallbacks=[],
),
RoutingRule(
name=,
priority=,
condition= ctx: ctx.needs_vision,
model=,
fallbacks=[, ],
),
RoutingRule(
name=,
priority=,
condition= ctx: ctx.task_type == ,
model=,
fallbacks=[],
),
RoutingRule(
name=,
priority=,
condition= ctx: ctx.max_latency_ms < ,
model=,
fallbacks=[],
),
RoutingRule(
name=,
priority=,
condition= ctx: ctx.user_tier == ,
model=,
fallbacks=[, ],
),
RoutingRule(
name=,
priority=,
condition= ctx: ,
model=,
fallbacks=[],
),
]
() -> RoutingRule:
sorted_rules = (RULES, key= r: r.priority)
rule sorted_rules:
rule.matches(ctx):
rule
sorted_rules[-]
Config-Driven Rules (JSON)
RULES_CONFIG = {
"rules": [
{
"name": "free-tier",
"priority": 1,
"conditions": {"user_tier": "free"},
"model": "google/gemma-2-9b-it:free",
"max_tokens": 512,
},
{
"name": "code-pro",
"priority": 5,
"conditions": {"task_type": "code", "user_tier": ["pro", "enterprise"]},
"model": "anthropic/claude-3.5-sonnet",
"max_tokens": 2048,
},
{
"name": "default",
"priority": 99,
"conditions": {},
"model": "openai/gpt-4o-mini",
},
]
}
def match_config_rule(ctx: RoutingContext, rule_config: dict) -> bool:
"""Match a context against config-driven conditions."""
conditions = rule_config.get("conditions", {})
for key, expected in conditions.items():
actual = getattr(ctx, key, None)
if isinstance(expected, list):
actual expected:
actual != expected:
Routed Completion
def routed_completion(messages: list[dict], ctx: RoutingContext, **kwargs):
"""Execute completion with rule-based routing."""
rule = evaluate_rules(ctx)
extra_body = {}
if rule.fallbacks:
extra_body = {
"models": [rule.model] + rule.fallbacks,
"route": "fallback",
}
response = client.chat.completions.create(
model=rule.model,
messages=messages,
max_tokens=rule.max_tokens,
extra_body=extra_body or None,
**kwargs,
)
return {
"content": response.choices[0].message.content,
"model": response.model,
"rule": rule.name,
"tokens": response.usage.prompt_tokens + response.usage.completion_tokens,
}
ctx = RoutingContext(user_tier="pro", task_type="code", budget_remaining=50.0)
result = routed_completion(
[{"role": "user", "content": "Refactor this function..."}],
ctx=ctx,
)
print(f"Rule: {result['rule']}, Model: {result['model']}")
A/B Testing Rules
import random
def ab_test_routing(ctx: RoutingContext, test_name: str, variant_b_pct: float = 0.10):
"""Route a percentage of traffic to variant B for comparison."""
rule = evaluate_rules(ctx)
if random.random() < variant_b_pct:
return RoutingRule(
name=f"{rule.name}:variant-b",
priority=rule.priority,
condition=rule.condition,
model="openai/gpt-4o",
fallbacks=rule.fallbacks,
max_tokens=rule.max_tokens,
)
return rule
Output
- A resolved
RoutingRule per request โ name, model, fallbacks, max_tokens โ from evaluate_rules()
- A completion result dict from
routed_completion(): {content, model, rule, tokens}; the rule field makes every routing decision auditable
- A JSON rules config (Config-Driven Rules) that can be hot-reloaded without redeployment
- A/B variant assignments (
<rule-name>:variant-b) for a configurable percentage of traffic
Examples
A pro-tier code request falls through the free-tier, budget, tools, and vision rules and matches code-tasks:
ctx = RoutingContext(user_tier="pro", task_type="code", budget_remaining=50.0)
result = routed_completion([{"role": "user", "content": "Refactor this function..."}], ctx=ctx)
print(f"Rule: {result['rule']}, Model: {result['model']}")
The same context with user_tier="free" matches the priority-1 free-tier rule instead, landing on google/gemma-2-9b-it:free capped at 512 tokens. More worked examples: references/examples.md.
Error Handling
| Error | Cause | Fix |
|---|
| No rule matched | Missing default catch-all | Always include a priority=99 default rule |
| Rule condition error | Dynamic check raised exception | Wrap condition in try/catch; return False on error |
| Wrong model selected | Rule priority incorrect | Log matching rule name; review priority ordering |
| Config parse error | Invalid JSON rule definition | Validate config at startup; fail fast |
Enterprise Considerations
- Store rules in a config file or database for hot-reloading without redeployment
- Log every routing decision (rule name, model, context) for analytics and debugging
- Use A/B testing to validate rule changes before full rollout
- Always include a default catch-all rule with a reliable, affordable model
- Version your rule configurations and track changes alongside code deployments
- Combine routing rules with budget enforcement (see openrouter-cost-controls)
References