AI Monitoring and Alerting: Latency, Token Usage, Error Rates, Drift Detection
Introduction
AI applications introduce new monitoring dimensions beyond traditional infrastructure metrics. LLM responses can be slow, expensive, incorrect, or suddenly change behavior when providers update models. This article covers the metrics, tools, and alerting strategies for production AI monitoring, including drift detection that catches quality degradation before users complain.
Core Metrics
Every AI application should track these foundational metrics:
from prometheus_client import Counter, Histogram, Gauge
import time
class AIMetrics:
def __init__(self):
self.request_count = Counter(
"llm_requests_total", "Total LLM requests",
["provider", "model", "status"],
)
self.latency = Histogram(
"llm_latency_ms", "LLM response latency",
["provider", "model"],
buckets=[100, 250, 500, 1000, 2000, 5000, 10000],
)
self.token_usage = Counter(
"llm_tokens_total", "Token usage",
["provider", "model", "token_type"], # token_type: input/output
)
self.cost_total = Counter(
"llm_cost_usd", "Total cost in USD",
["provider", "model"],
)
self.cache_hit_ratio = Gauge(
"llm_cache_hit_ratio", "Cache hit ratio",
["cache_level"], # cache_level: exact/semantic
)
def record_request(self, provider: str, model: str, duration_ms: float, status: str = "success"):
self.request_count.labels(provider=provider, model=model, status=status).inc()
self.latency.labels(provider=provider, model=model).observe(duration_ms)
def record_tokens(self, provider: str, model: str, input_tokens: int, output_tokens: int, cost: float):
self.token_usage.labels(provider=provider, model=model, token_type="input").inc(input_tokens)
self.token_usage.labels(provider=provider, model=model, token_type="output").inc(output_tokens)
self.cost_total.labels(provider=provider, model=model).inc(cost)
Token Usage Tracking
Monitor token consumption per user, feature, and time period:
class TokenUsageTracker:
def __init__(self, db):
self.db = db
async def log_usage(self, user_id: str, feature: str, provider: str, model: str,
input_tokens: int, output_tokens: int, latency_ms: float):
await self.db.execute("""
INSERT INTO token_usage
(user_id, feature, provider, model, input_tokens, output_tokens,
latency_ms, cost, timestamp)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW())
""", user_id, feature, provider, model, input_tokens, output_tokens,
latency_ms, self._calculate_cost(input_tokens, output_tokens, provider, model))
async def get_daily_usage(self, date: str) -> dict:
row = await self.db.fetchrow("""
SELECT SUM(input_tokens) as input, SUM(output_tokens) as output,
SUM(cost) as cost, COUNT(*) as requests
FROM token_usage WHERE DATE(timestamp) = $1
""", date)
return dict(row) if row else {"input": 0, "output": 0, "cost": 0, "requests": 0}
async def check_budget(self, user_id: str, daily_budget: float) -> bool:
row = await self.db.fetchrow("""
SELECT SUM(cost) as today_cost
FROM token_usage
WHERE user_id = $1 AND DATE(timestamp) = CURRENT_DATE
""", user_id)
return (row["today_cost"] or 0) < daily_budget
Error Rate Monitoring
LLM applications experience distinctive error types:
from enum import Enum
class LLMErrorType(Enum):
RATE_LIMIT = "rate_limit"
CONTEXT_WINDOW = "context_window_exceeded"
CONTENT_FILTER = "content_filter_blocked"
TIMEOUT = "timeout"
INVALID_RESPONSE = "invalid_response"
PROVIDER_DOWN = "provider_down"
class ErrorMonitor:
def __init__(self, alert_threshold: float = 0.05):
self.alert_threshold = alert_threshold
self.error_counts: dict[str, int] = {}
self.total_requests: int = 0
def record_error(self, error_type: LLMErrorType, provider: str):
key = f"{error_type.value}:{provider}"
self.error_counts[key] = self.error_counts.get(key, 0) + 1
self.total_requests += 1
def check_alerts(self) -> list[str]:
alerts = []
for key, count in self.error_counts.items():
rate = count / max(self.total_requests, 1)
if rate > self.alert_threshold:
alerts.append(f"Error rate {rate:.1%} for {key} exceeds threshold {self.alert_threshold:.1%}")
return alerts
Drift Detection
The most critical AI-specific monitoring: detect when model behavior changes:
import numpy as np
from scipy import stats
class ResponseDriftDetector:
def __init__(self, reference_embeddings: list, drift_threshold: float = 0.1):
self.reference = np.mean(reference_embeddings, axis=0)
self.threshold = drift_threshold
self.recent_embeddings: list = []
def analyze_response(self, query: str, response: str) -> dict:
emb = self._embed(response)
self.recent_embeddings.append(emb)
if len(self.recent_embeddings) >= 100:
drift_score = self._compute_drift()
self.recent_embeddings = []
return {"drift_detected": drift_score > self.threshold, "drift_score": drift_score}
return {"drift_detected": False, "drift_score": 0.0}
def _embed(self, text: str) -> np.ndarray:
return embedding_model.encode(text)
def _compute_drift(self) -> float:
recent_mean = np.mean(self.recent_embeddings, axis=0)
return float(np.linalg.norm(recent_mean - self.reference))
def detect_refusal_rate_change(self, recent_responses: list[str], baseline_rate: float) -> dict:
refusal_patterns = ["I cannot", "I'm unable", "I apologize", "cannot assist"]
current_rate = sum(
1 for r in recent_responses
if any(p in r.lower() for p in refusal_patterns)
) / len(recent_responses)
change = abs(current_rate - baseline_rate)
return {"changed": change > 0.05, "baseline": baseline_rate, "current": current_rate, "change": change}
Alerting Configuration
class AIAlertManager:
def __init__(self, pagerduty_key: str, slack_webhook: str):
self.pagerduty = pagerduty_key
self.slack = slack_webhook
def check_and_alert(self, metrics: dict):
alerts = []
# Latency alerts
if metrics.get("p99_latency_ms", 0) > 10000:
alerts.append(Alert(severity="critical", title="High P99 latency",
message=f"P99 latency is {metrics['p99_latency_ms']}ms"))
# Error rate alerts
if metrics.get("error_rate", 0) > 0.05:
alerts.append(Alert(severity="critical", title="Elevated error rate",
message=f"Error rate is {metrics['error_rate']:.1%}"))
# Cost anomaly alerts
if metrics.get("daily_cost", 0) > metrics.get("daily_budget", float("inf")) * 1.2:
alerts.append(Alert(severity="warning", title="Cost anomaly detected",
message=f"Cost ${metrics['daily_cost']:.2f} exceeds 120% of budget"))
# Drift alerts
if metrics.get("drift_detected", False):
alerts.append(Alert(severity="warning", title="Response drift detected",
message=f"Drift score: {metrics.get('drift_score', 0):.3f}"))
return alerts
Conclusion
AI monitoring extends traditional observability with LLM-specific metrics. Track latency percentiles (P50, P95, P99) to detect slowdowns. Monitor token usage and cost per user and feature to control spending. Categorize errors by type (rate limit, context window, content filter) to identify provider issues. Most importantly, implement drift detection to catch subtle quality changes when models are updated or system prompts are modified. Alert on all four dimensions and investigate any metric that deviates more than 20% from its baseline.