Stage 7 · Master
LLM Cost, Latency & Observability
Model Routing
Route requests across small, large, and local models using task complexity, latency SLOs, and fallback rules.
Why Route Requests?
Not every request needs the most expensive model. A simple classification can use GPT-3.5 Turbo at 1/60th the cost of GPT-4. Model routing selects the cheapest model that can handle the task well enough.
Routing Strategies
| Strategy | How It Works | Best For |
|---|---|---|
| Task-based | Route by task type | Well-defined task categories |
| Complexity-based | Route by query complexity | Varied query difficulty |
| Latency-based | Route by latency requirement | Mixed latency needs |
| Cost-based | Route by cost budget | Cost-sensitive applications |
Complexity Classification
class ComplexityRouter:
def __init__(self):
self.model_tiers = {
"simple": {"model": "gpt-3.5-turbo", "max_tokens": 500},
"moderate": {"model": "gpt-4-turbo", "max_tokens": 2000},
"complex": {"model": "gpt-4", "max_tokens": 4000},
}
def classify_complexity(self, query, task_type):
"""Classify the complexity of a query."""
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
temperature=0.0,
messages=[
{
"role": "system",
"content": """Classify this query as simple, moderate, or complex.
Simple: Single factual question, classification, simple lookup
Moderate: Multi-step reasoning, comparison, analysis
Complex: Deep analysis, multi-source correlation, creative generation
Return only: simple, moderate, or complex"""
},
{"role": "user", "content": query}
]
)
complexity = response["choices"][0]["message"]["content"].strip()
return self.model_tiers.get(complexity, self.model_tiers["moderate"])
def route(self, query, task_type):
"""Route a query to the appropriate model."""
tier = self.classify_complexity(query, task_type)
return tierA lightweight classifier determines complexity, then routes to the cheapest model that can handle it.
Latency-Based Routing
class LatencyRouter:
def __init__(self):
self.model_latencies = {
"gpt-3.5-turbo": {"p50": 500, "p99": 2000},
"gpt-4-turbo": {"p50": 1500, "p99": 5000},
"gpt-4": {"p50": 3000, "p99": 10000},
}
def route_by_latency(self, query, latency_slo_ms):
"""Route to a model that meets the latency SLO."""
for model_name, latency in sorted(
self.model_latencies.items(),
key=lambda x: x[1]["p99"]
):
if latency["p99"] <= latency_slo_ms:
return {
"model": model_name,
"expected_latency": latency["p50"],
"meets_slo": True
}
# No model meets the SLO, use the fastest
fastest = min(self.model_latencies.items(), key=lambda x: x[1]["p99"])
return {
"model": fastest[0],
"expected_latency": fastest[1]["p50"],
"meets_slo": False
}Route based on the latency requirement. Interactive features need fast models. Background tasks can use slower, cheaper models.
Fallback Rules
class ModelRouter:
def __init__(self):
self.routes = {
"alert_classification": {
"primary": "gpt-3.5-turbo",
"fallback": "gpt-4-turbo",
"local": "llama-3-8b",
},
"runbook_lookup": {
"primary": "gpt-4-turbo",
"fallback": "gpt-4",
"local": "llama-3-70b",
},
"rca_analysis": {
"primary": "gpt-4",
"fallback": "gpt-4-turbo",
"local": None,
},
}
def route_with_fallback(self, task_type, query):
"""Route with automatic fallback on failure."""
config = self.routes.get(task_type, self.routes["alert_classification"])
for model_key in ["primary", "fallback", "local"]:
model = config[model_key]
if model is None:
continue
try:
result = call_model(model, query)
return {
"model": model,
"result": result,
"attempt": model_key
}
except Exception as e:
log_fallback(task_type, model, str(e))
continue
return {"error": "All models failed"}The fallback chain tries the primary model, falls back to a secondary, and finally tries a local model if available.
The simplest routing strategy is task-based: classification goes to GPT-3.5, analysis goes to GPT-4, and code generation goes to Codex. Add complexity-based routing later.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.