Module 6: AI-Specific Monitoring
8. Project: AI-Specific Monitoring Layer
Description
This project closes Module 6. You take everything you built — prompt quality tracking, token usage monitoring, model drift detection, hallucination detection, LangSmith integration, and custom metrics with dashboards — and integrate it into a functional AI-Specific Monitoring Layer.
This monitoring layer is what differentiates "we have observability" from "we have observability for AI". Modules 1-5 could apply (with adjustments) to any software system. Module 6 is exclusively AI — without it, your observability stack doesn't understand what your LLM says, whether it says it well, or whether it changed how it says it.
The deliverable is an integrated system you can point at your AI system in production. Detailed token tracking, quality scoring, hallucination detection configured, and everything correlated with OTel.
Connection with the module: Capsules 02-07 built individual components. This project integrates them into a single cohesive system.
Objective
Build a complete AI-Specific Monitoring Layer that monitors the semantic dimension of your AI system.
By the end of this project you will be able to:
- ✅ Have an integrated monitoring layer with all of the module's components
- ✅ Prompt quality tracking with baselines and drift detection
- ✅ Token usage monitoring with drill-down, outliers, and budget enforcement
- ✅ Model drift detection with baseline comparison and alerts
- ✅ Hallucination detection with heuristics and LLM-as-judge
- ✅ Custom AI-specific metrics with a complete dashboard
- ✅ Correlation with OTel to integrate with the existing stack
- ✅ A simulation that validates the behavior of the complete system
What You Will Build
AI-SPECIFIC MONITORING LAYER — Deliverable
├── 1. AIMonitoringLayer (Integrated System)
│ ├── Orchestrates all components
│ ├── Processes each request through all checks
│ ├── Generates consolidated alerts
│ └── Produces a unified dashboard
│
├── 2. Components
│ ├── PromptQualityTracker (capsule 02)
│ ├── TokenUsageMonitor (capsule 03)
│ ├── DriftDetector (capsule 04)
│ ├── HallucinationDetector (capsule 05)
│ └── AIMetricsCollector (capsule 07)
│
├── 3. OTel Integration
│ ├── Span attributes for each component
│ ├── Custom metrics (gauges, counters, histograms)
│ └── Events for alerts
│
└── 4. Complete Simulation
├── Phase 1: Normal traffic → establish baselines
├── Phase 2: Model drift → detect the change
├── Phase 3: Hallucination spike → detect degradation
├── Phase 4: Recovery → confirm stabilization
└── Final dashboard with the system state
Technical Specifications
Stack
python >= 3.10
opentelemetry-api
opentelemetry-sdk
tiktoken (optional, for precise token counting)
Conceptual structure
ai-monitoring-layer/
├── monitoring_layer.py # Integrated AIMonitoringLayer
├── prompt_tracker.py # PromptQualityTracker
├── token_monitor.py # TokenUsageMonitor
├── drift_detector.py # DriftDetector
├── hallucination_detector.py # HallucinationDetector
├── metrics_collector.py # AIMetricsCollector
├── otel_integration.py # OTel spans + metrics
└── simulate.py # Complete simulation
Step by Step
Step 1: Shared data models
import re
import time
import random
import hashlib
import statistics
import json
from dataclasses import dataclass, field
from datetime import datetime, timezone
from collections import deque, defaultdict
from typing import Optional
from enum import Enum
class AlertSeverity(Enum):
INFO = "info"
WARNING = "warning"
CRITICAL = "critical"
class FidelityLevel(Enum):
FAITHFUL = 0
MINOR_IMPRECISION = 1
IRRELEVANT = 2
OUTDATED = 3
DISTORTED = 4
FABRICATED = 5
@dataclass
class AIRequest:
"""A complete request that flows through the whole monitoring layer."""
timestamp: float
endpoint: str
model: str
user_id: str
system_prompt: str
user_prompt: str
output_text: str
prompt_tokens: int
completion_tokens: int
total_tokens: int
cost_usd: float
latency_seconds: float
context: str = ""
success: bool = True
is_retry: bool = False
retry_reason: str = ""
@dataclass
class MonitoringAlert:
"""A unified alert from any component."""
source: str
alert_type: str
severity: AlertSeverity
message: str
value: float
threshold: float
endpoint: str
metadata: dict = field(default_factory=dict)
timestamp: str = ""
def __post_init__(self):
if not self.timestamp:
self.timestamp = datetime.now(timezone.utc).isoformat()
@dataclass
class MonitoringResult:
"""The complete result of processing a request."""
request_id: str
prompt_analysis: dict
token_analysis: dict
hallucination_score: Optional[float]
drift_score: Optional[float]
quality_score: Optional[float]
alerts: list[MonitoringAlert]
otel_attributes: dict
Step 2: Integrated components (compact version)
class PromptTracker:
"""Prompt quality tracking (integrated version)."""
def __init__(self, max_instructions: int = 8):
self.max_instructions = max_instructions
self.history: deque[dict] = deque(maxlen=5000)
self.baselines: dict[str, dict] = {}
self.template_hashes: dict[str, str] = {}
def analyze(self, system_prompt: str, user_prompt: str, endpoint: str) -> tuple[dict, list[MonitoringAlert]]:
full = system_prompt + "\n" + user_prompt
normalized = re.sub(r"\s+", " ", system_prompt.strip().lower())
template_hash = hashlib.sha256(normalized.encode()).hexdigest()[:12]
instruction_count = len(re.findall(
r"(?:must|always|never|respond|include|avoid)\b",
system_prompt, re.IGNORECASE,
))
analysis = {
"prompt_length": len(full),
"system_length": len(system_prompt),
"user_length": len(user_prompt),
"instruction_count": max(instruction_count, 1),
"template_hash": template_hash,
"estimated_tokens": len(full) // 4,
}
self.history.append({**analysis, "endpoint": endpoint})
alerts = []
baseline = self.baselines.get(endpoint)
if baseline:
std = baseline.get("length_std", 1) or 1
z = (analysis["prompt_length"] - baseline["length_mean"]) / std
if abs(z) > 2.0:
direction = "longer" if z > 0 else "shorter"
alerts.append(MonitoringAlert(
source="prompt_tracker", alert_type="length_drift",
severity=AlertSeverity.WARNING,
message=f"Prompt {direction}: {analysis['prompt_length']} chars (baseline: {baseline['length_mean']:.0f})",
value=analysis["prompt_length"], threshold=baseline["length_mean"],
endpoint=endpoint, metadata={"z_score": round(z, 2)},
))
registered = self.template_hashes.get(endpoint)
if registered and template_hash != registered:
alerts.append(MonitoringAlert(
source="prompt_tracker", alert_type="template_change",
severity=AlertSeverity.CRITICAL,
message=f"Template changed in {endpoint}",
value=0, threshold=1, endpoint=endpoint,
metadata={"old_hash": registered, "new_hash": template_hash},
))
if analysis["instruction_count"] > self.max_instructions:
alerts.append(MonitoringAlert(
source="prompt_tracker", alert_type="complexity_creep",
severity=AlertSeverity.WARNING,
message=f"{analysis['instruction_count']} instructions (max: {self.max_instructions})",
value=analysis["instruction_count"], threshold=self.max_instructions,
endpoint=endpoint,
))
return analysis, alerts
def establish_baseline(self, endpoint: str):
ep_data = [h for h in self.history if h["endpoint"] == endpoint]
if len(ep_data) < 50:
return
lengths = [h["prompt_length"] for h in ep_data[-200:]]
hashes = [h["template_hash"] for h in ep_data[-200:]]
most_common = max(set(hashes), key=hashes.count)
self.baselines[endpoint] = {
"length_mean": statistics.mean(lengths),
"length_std": statistics.stdev(lengths) if len(lengths) > 1 else 0,
}
self.template_hashes[endpoint] = most_common
class TokenTracker:
"""Token usage monitoring (integrated version)."""
def __init__(self, outlier_multiplier: float = 10.0, budgets: dict = None):
self.outlier_multiplier = outlier_multiplier
self.budgets = budgets or {}
self.by_endpoint: dict[str, list[int]] = defaultdict(list)
self.by_model: dict[str, int] = defaultdict(int)
self.by_user: dict[str, int] = defaultdict(int)
self.endpoint_totals: dict[str, int] = defaultdict(int)
self.baselines: dict[str, dict] = {}
def record(self, req: AIRequest) -> tuple[dict, list[MonitoringAlert]]:
self.by_endpoint[req.endpoint].append(req.total_tokens)
self.by_model[req.model] += req.total_tokens
self.by_user[req.user_id] += req.total_tokens
self.endpoint_totals[req.endpoint] += req.total_tokens
analysis = {
"total_tokens": req.total_tokens,
"prompt_tokens": req.prompt_tokens,
"completion_tokens": req.completion_tokens,
"cost_usd": req.cost_usd,
"endpoint_total": self.endpoint_totals[req.endpoint],
}
alerts = []
baseline = self.baselines.get(req.endpoint)
if baseline and baseline["mean"] > 0:
if req.total_tokens > baseline["mean"] * self.outlier_multiplier:
alerts.append(MonitoringAlert(
source="token_tracker", alert_type="token_outlier",
severity=AlertSeverity.WARNING,
message=f"Outlier: {req.total_tokens:,} tokens ({req.total_tokens / baseline['mean']:.1f}x avg)",
value=req.total_tokens, threshold=baseline["mean"] * self.outlier_multiplier,
endpoint=req.endpoint,
metadata={"user_id": req.user_id, "model": req.model},
))
budget = self.budgets.get(req.endpoint)
if budget and self.endpoint_totals[req.endpoint] >= budget:
alerts.append(MonitoringAlert(
source="token_tracker", alert_type="budget_exceeded",
severity=AlertSeverity.CRITICAL,
message=f"Token budget exceeded in {req.endpoint}: {self.endpoint_totals[req.endpoint]:,} / {budget:,}",
value=self.endpoint_totals[req.endpoint], threshold=budget,
endpoint=req.endpoint,
))
return analysis, alerts
def establish_baseline(self, endpoint: str):
values = self.by_endpoint.get(endpoint, [])
if len(values) < 50:
return
self.baselines[endpoint] = {
"mean": statistics.mean(values[-200:]),
"std": statistics.stdev(values[-200:]) if len(values) > 1 else 0,
}
class DriftChecker:
"""Drift detection (integrated version)."""
def __init__(self, threshold_sigma: float = 2.0):
self.threshold = threshold_sigma
self.output_lengths: dict[str, deque] = defaultdict(lambda: deque(maxlen=5000))
self.baselines: dict[str, dict] = {}
def record(self, req: AIRequest) -> tuple[dict, list[MonitoringAlert]]:
key = f"{req.endpoint}:{req.model}"
output_len = len(req.output_text)
self.output_lengths[key].append(output_len)
analysis = {"output_length": output_len, "key": key}
alerts = []
baseline = self.baselines.get(key)
if baseline and baseline["std"] > 0:
recent = list(self.output_lengths[key])[-50:]
if len(recent) >= 30:
current_mean = statistics.mean(recent)
z = (current_mean - baseline["mean"]) / baseline["std"]
change_pct = ((current_mean - baseline["mean"]) / baseline["mean"]) * 100
analysis["drift_z_score"] = round(z, 2)
analysis["drift_change_pct"] = round(change_pct, 1)
if abs(z) > self.threshold:
direction = "shorter" if z < 0 else "longer"
alerts.append(MonitoringAlert(
source="drift_checker", alert_type="model_drift",
severity=AlertSeverity.WARNING,
message=f"Drift: {direction} responses ({change_pct:+.0f}%) in {req.model}",
value=current_mean, threshold=baseline["mean"],
endpoint=req.endpoint,
metadata={"model": req.model, "z_score": round(z, 2)},
))
return analysis, alerts
def establish_baseline(self, endpoint: str, model: str):
key = f"{endpoint}:{model}"
values = list(self.output_lengths.get(key, []))
if len(values) < 50:
return
self.baselines[key] = {
"mean": statistics.mean(values[-200:]),
"std": statistics.stdev(values[-200:]) if len(values) > 1 else 0,
}
class HallucinationChecker:
"""Hallucination detection (integrated version)."""
OVERCONFIDENCE = [r"\b(definitely|absolutely|exactly|without a doubt)\b"]
HEDGE = [r"\b(maybe|perhaps|i think|might|not sure)\b"]
SPECIFIC = r"\b\d{2,}\.\d+%\b|\b\d{4,}\b"
def __init__(self, heuristic_threshold: float = 0.4):
self.threshold = heuristic_threshold
self.history: deque[float] = deque(maxlen=10_000)
def evaluate(self, req: AIRequest) -> tuple[dict, list[MonitoringAlert]]:
text = req.output_text
words = max(len(text.split()), 1)
overconf = sum(len(re.findall(p, text, re.I)) for p in self.OVERCONFIDENCE)
hedge = sum(len(re.findall(p, text, re.I)) for p in self.HEDGE)
specific = len(re.findall(self.SPECIFIC, text))
overconf_score = min(overconf / max(words / 50, 1), 1.0)
hedge_ratio = hedge / words
spec_score = min(specific / max(words / 100, 1), 1.0)
heuristic = (
overconf_score * 0.35
+ spec_score * 0.30
+ (1.0 - min(hedge_ratio * 20, 1.0)) * 0.20
)
context_overlap = 0.0
if req.context:
ctx_words = set(req.context.lower().split())
out_words = set(text.lower().split())
context_overlap = len(ctx_words & out_words) / max(len(out_words), 1)
faithfulness = context_overlap * 0.7 + (1 - heuristic) * 0.3
score = 1.0 - faithfulness
else:
score = heuristic * 0.6
score = round(max(0.0, min(1.0, score)), 3)
self.history.append(score)
analysis = {
"hallucination_score": score,
"heuristic_score": round(heuristic, 3),
"overconfidence": round(overconf_score, 3),
"context_overlap": round(context_overlap, 3),
"is_hallucination": score >= 0.5,
}
alerts = []
if len(self.history) >= 50:
recent = list(self.history)[-50:]
halluc_rate = sum(1 for s in recent if s >= 0.5) / len(recent)
if halluc_rate > 0.10:
alerts.append(MonitoringAlert(
source="hallucination_checker", alert_type="hallucination_rate_high",
severity=AlertSeverity.WARNING,
message=f"Hallucination rate: {halluc_rate*100:.1f}% (last 50 requests)",
value=halluc_rate, threshold=0.10,
endpoint=req.endpoint,
))
return analysis, alerts
Step 3: AIMonitoringLayer — Integrated System
class AIMonitoringLayer:
"""AI-specific monitoring layer that integrates all components.
Processes each request through:
1. Prompt quality tracking
2. Token usage monitoring
3. Drift detection
4. Hallucination detection
5. Aggregated metrics
"""
def __init__(
self,
token_budgets: dict = None,
outlier_multiplier: float = 10.0,
drift_sigma: float = 2.0,
hallucination_threshold: float = 0.4,
):
self.prompt_tracker = PromptTracker()
self.token_tracker = TokenTracker(
outlier_multiplier=outlier_multiplier,
budgets=token_budgets or {},
)
self.drift_checker = DriftChecker(threshold_sigma=drift_sigma)
self.hallucination_checker = HallucinationChecker(
heuristic_threshold=hallucination_threshold,
)
self.all_alerts: list[MonitoringAlert] = []
self.all_results: list[MonitoringResult] = []
self.request_count: int = 0
self.quality_scores: deque[float] = deque(maxlen=10_000)
self.cost_by_endpoint: dict[str, float] = defaultdict(float)
self.requests_by_endpoint: dict[str, int] = defaultdict(int)
def process(self, req: AIRequest) -> MonitoringResult:
"""Processes a request through the entire monitoring layer."""
self.request_count += 1
request_id = f"req-{self.request_count:06d}"
all_alerts = []
prompt_analysis, prompt_alerts = self.prompt_tracker.analyze(
req.system_prompt, req.user_prompt, req.endpoint,
)
all_alerts.extend(prompt_alerts)
token_analysis, token_alerts = self.token_tracker.record(req)
all_alerts.extend(token_alerts)
drift_analysis, drift_alerts = self.drift_checker.record(req)
all_alerts.extend(drift_alerts)
halluc_analysis, halluc_alerts = self.hallucination_checker.evaluate(req)
all_alerts.extend(halluc_alerts)
quality_score = None
if req.success and not halluc_analysis["is_hallucination"]:
quality_score = max(0, 1.0 - halluc_analysis["hallucination_score"])
self.quality_scores.append(quality_score)
self.cost_by_endpoint[req.endpoint] += req.cost_usd
self.requests_by_endpoint[req.endpoint] += 1
otel_attributes = {
"ai.prompt.length": prompt_analysis["prompt_length"],
"ai.prompt.template_hash": prompt_analysis["template_hash"],
"ai.prompt.instruction_count": prompt_analysis["instruction_count"],
"ai.tokens.total": req.total_tokens,
"ai.tokens.prompt": req.prompt_tokens,
"ai.tokens.completion": req.completion_tokens,
"ai.hallucination.score": halluc_analysis["hallucination_score"],
"ai.hallucination.is_hallucination": halluc_analysis["is_hallucination"],
"ai.quality.score": quality_score or 0,
"ai.drift.z_score": drift_analysis.get("drift_z_score", 0),
"ai.cost.usd": req.cost_usd,
"ai.model": req.model,
}
result = MonitoringResult(
request_id=request_id,
prompt_analysis=prompt_analysis,
token_analysis=token_analysis,
hallucination_score=halluc_analysis["hallucination_score"],
drift_score=drift_analysis.get("drift_z_score"),
quality_score=quality_score,
alerts=all_alerts,
otel_attributes=otel_attributes,
)
self.all_alerts.extend(all_alerts)
self.all_results.append(result)
return result
def establish_baselines(self, endpoints: list[str], models: list[str]):
"""Establishes baselines for all components."""
for ep in endpoints:
self.prompt_tracker.establish_baseline(ep)
self.token_tracker.establish_baseline(ep)
for model in models:
self.drift_checker.establish_baseline(ep, model)
def dashboard(self) -> dict:
"""Complete dashboard of the monitoring layer state."""
quality_list = list(self.quality_scores)
halluc_scores = list(self.hallucination_checker.history)
halluc_rate = (
sum(1 for s in halluc_scores[-100:] if s >= 0.5) / max(len(halluc_scores[-100:]), 1)
if halluc_scores else 0
)
alert_counts = defaultdict(int)
for a in self.all_alerts:
alert_counts[a.source] += 1
return {
"timestamp": datetime.now(timezone.utc).isoformat(),
"overview": {
"total_requests": self.request_count,
"total_alerts": len(self.all_alerts),
"alerts_by_source": dict(alert_counts),
},
"quality": {
"mean": round(statistics.mean(quality_list), 4) if quality_list else None,
"recent_mean": round(statistics.mean(quality_list[-100:]), 4) if len(quality_list) >= 100 else None,
"samples": len(quality_list),
},
"hallucination": {
"rate": round(halluc_rate, 4),
"mean_score": round(statistics.mean(halluc_scores[-100:]), 4) if halluc_scores else 0,
},
"cost": {
"total_usd": round(sum(self.cost_by_endpoint.values()), 4),
"by_endpoint": {
ep: round(c, 4) for ep, c in self.cost_by_endpoint.items()
},
},
"endpoints": {
ep: {"requests": self.requests_by_endpoint[ep]}
for ep in self.requests_by_endpoint
},
}
Step 4: Complete simulation
def generate_request(
i: int,
base_time: float,
phase: str,
endpoints: list[str],
models: dict[str, str],
) -> AIRequest:
"""Generates a simulated request according to the phase."""
ep = random.choice(endpoints)
model = models[ep]
base_system = "You are a technical assistant. Respond concisely and precisely."
context = "Python was created in 1991 by Guido van Rossum. It is an interpreted language."
if phase == "normal":
user_prompt = f"Technical question #{i}: " + "context " * random.randint(3, 8)
output = "Python is an interpreted language created " + "information " * random.randint(10, 30)
prompt_tok = random.randint(200, 500)
comp_tok = random.randint(100, 300)
elif phase == "drift":
user_prompt = f"Question #{i}: " + "context " * random.randint(3, 8)
output = "Yes. " + "ok " * random.randint(3, 8)
prompt_tok = random.randint(200, 500)
comp_tok = random.randint(20, 80)
elif phase == "hallucination":
user_prompt = f"Question #{i}: " + "context " * random.randint(3, 8)
if random.random() < 0.30:
output = f"Definitely Python was created in {random.randint(1980, 2010)} with exactly {random.random()*100:.1f}% adoption without a doubt"
else:
output = "Python is a language created approximately in 1991 by Guido van Rossum"
prompt_tok = random.randint(200, 500)
comp_tok = random.randint(100, 400)
else:
user_prompt = f"Question #{i}: " + "context " * random.randint(3, 8)
output = "Python is an interpreted language created in 1991 by Guido van Rossum. " + "detail " * random.randint(10, 25)
prompt_tok = random.randint(200, 500)
comp_tok = random.randint(100, 300)
cost_rate = 0.005 if model == "gpt-4o" else 0.0003
cost = (prompt_tok + comp_tok) / 1000 * cost_rate
return AIRequest(
timestamp=base_time + i * 5,
endpoint=ep, model=model, user_id=f"user_{random.randint(1, 20)}",
system_prompt=base_system, user_prompt=user_prompt,
output_text=output, prompt_tokens=prompt_tok,
completion_tokens=comp_tok, total_tokens=prompt_tok + comp_tok,
cost_usd=cost, latency_seconds=max(0.1, random.gauss(1.2, 0.3)),
context=context,
)
def run_simulation():
random.seed(42)
endpoints = ["/chat", "/analyze"]
models = {"/chat": "gpt-4o-mini", "/analyze": "gpt-4o"}
layer = AIMonitoringLayer(
token_budgets={"/analyze": 200_000},
outlier_multiplier=8.0,
drift_sigma=2.0,
hallucination_threshold=0.35,
)
print("=" * 65)
print("PROJECT: AI-Specific Monitoring Layer — Complete Simulation")
print("=" * 65)
# --- PHASE 1: Normal traffic → baselines ---
print("\n─── PHASE 1: Normal traffic (200 requests) → Baselines ───")
for i in range(200):
req = generate_request(i, 0.0, "normal", endpoints, models)
result = layer.process(req)
if result.alerts:
for a in result.alerts:
print(f" [{a.severity.value:8}] {a.source:25} | {a.message[:50]}...")
layer.establish_baselines(endpoints, list(set(models.values())))
print(f"\n Baselines established")
print(f" Requests: {layer.request_count}")
print(f" Phase 1 alerts: {len(layer.all_alerts)}")
d = layer.dashboard()
print(f" Quality: {d['quality']['mean']}")
print(f" Hallucination rate: {d['hallucination']['rate']}")
# --- PHASE 2: Model drift ---
print("\n─── PHASE 2: Model drift (80 requests) → Short responses ───")
alerts_before = len(layer.all_alerts)
for i in range(80):
req = generate_request(i, 1000.0, "drift", endpoints, models)
result = layer.process(req)
if result.alerts:
for a in result.alerts:
print(f" [{a.severity.value:8}] {a.source:25} | {a.message[:50]}...")
new_alerts = len(layer.all_alerts) - alerts_before
print(f"\n New alerts: {new_alerts}")
d = layer.dashboard()
print(f" Quality: {d['quality'].get('recent_mean', 'N/A')}")
# --- PHASE 3: Hallucination spike ---
print("\n─── PHASE 3: Hallucination spike (80 requests) → Fabricated outputs ───")
alerts_before = len(layer.all_alerts)
for i in range(80):
req = generate_request(i, 1400.0, "hallucination", endpoints, models)
result = layer.process(req)
if result.alerts:
for a in result.alerts:
print(f" [{a.severity.value:8}] {a.source:25} | {a.message[:50]}...")
new_alerts = len(layer.all_alerts) - alerts_before
print(f"\n New alerts: {new_alerts}")
d = layer.dashboard()
print(f" Hallucination rate: {d['hallucination']['rate']}")
# --- PHASE 4: Recovery ---
print("\n─── PHASE 4: Recovery (100 requests) → Normal traffic ───")
alerts_before = len(layer.all_alerts)
for i in range(100):
req = generate_request(i, 1800.0, "recovery", endpoints, models)
result = layer.process(req)
if result.alerts:
for a in result.alerts:
print(f" [{a.severity.value:8}] {a.source:25} | {a.message[:50]}...")
new_alerts = len(layer.all_alerts) - alerts_before
print(f"\n New alerts: {new_alerts}")
# --- FINAL DASHBOARD ---
print("\n" + "=" * 65)
print("FINAL DASHBOARD — AI-Specific Monitoring Layer")
print("=" * 65)
final = layer.dashboard()
print(json.dumps(final, indent=2, default=str))
# --- ALERT SUMMARY ---
print("\n" + "=" * 65)
print("ALERT SUMMARY")
print("=" * 65)
by_source = defaultdict(list)
for a in layer.all_alerts:
by_source[a.source].append(a)
for source, alerts in by_source.items():
print(f"\n {source}:")
by_type = defaultdict(int)
for a in alerts:
by_type[a.alert_type] += 1
for atype, count in sorted(by_type.items(), key=lambda x: x[1], reverse=True):
print(f" {atype:30} × {count}")
print(f"\n Total alerts: {len(layer.all_alerts)}")
print(f" Total requests: {layer.request_count}")
print(f" Alert rate: {len(layer.all_alerts) / layer.request_count * 100:.1f}%")
# --- OTel ATTRIBUTES SAMPLE ---
print("\n" + "=" * 65)
print("OTEL ATTRIBUTES — Sample of the last request")
print("=" * 65)
if layer.all_results:
last = layer.all_results[-1]
for key, value in last.otel_attributes.items():
print(f" {key:40} = {value}")
run_simulation()
Completeness Checklist
Integrated System
-
AIMonitoringLayerprocesses requests through all components - Consolidated alerts from all sources
- Unified dashboard with the complete state
- OTel attributes for each request
Prompt Quality
- Length drift detection with baselines
- Template change detection with a hash
- Complexity tracking
Token Usage
- Tracking per endpoint and model
- Outlier detection (anomalous requests)
- Budget enforcement per endpoint
Drift Detection
- Output length baseline per endpoint/model
- Z-score comparison to detect changes
- Alerts when drift exceeds the threshold
Hallucination Detection
- Heuristic checks (overconfidence, specificity)
- Context overlap scoring
- Rate tracking over time
- Alerts when the rate rises
Dashboard and Metrics
- Overview (requests, alerts, cost)
- Quality metrics (mean, trend)
- Hallucination rate
- Cost by endpoint
- Alert summary by source
Simulation
- Normal phase → baselines with no spurious alerts
- Drift phase → drift detected
- Hallucination phase → high rate detected
- Recovery phase → system stabilizes
- Final dashboard shows the correct state
Comparison: Before and After
BEFORE (Without an AI monitoring layer)
─────────────────────────────────────────────
"How do you know your model is still giving good answers?"
→ "Users aren't complaining..."
"Would you detect it if OpenAI updates the model?"
→ "Eventually, when someone notices..."
"What is your hallucination rate?"
→ "We don't measure it..."
AFTER (With an AI monitoring layer)
─────────────────────────────────────────────
"How do you know your model is still giving good answers?"
→ "Quality score at 0.84, stable over the last 4 hours,
hallucination rate at 3%, drift score at 0.15."
"Would you detect it if OpenAI updates the model?"
→ "The drift detector compares the output distribution every 50
requests against the baseline. If output length changes > 2σ,
it alerts in <10 minutes."
"What is your hallucination rate?"
→ "3.2% overall, 1.1% in /chat, 5.8% in /analyze.
Alert threshold at 10%. Heuristic + context overlap."
Next Steps
This project leaves you with:
- An integrated monitoring layer that understands the semantic dimension of your AI system
- Established baselines for prompt quality, token usage, output distribution
- Automated detection of drift, hallucinations, token outliers, prompt degradation
- An AI-specific dashboard with metrics that only exist in AI systems
- OTel attributes ready to integrate with your tracing backend
The connection with the rest of the guide:
What you have (Module 6) Next step
──────────────────────────────────────────────────────
The AI monitoring layer detects → Module 7: Debugging
anomalies: drift, hallucinations, Why did the drift happen?
token outliers, prompt issues. What caused the hallucinations?
Investigation paths and runbooks.
AI-specific alerts configured → Module 7: Detailed runbooks
with thresholds and baselines. for each type of AI anomaly.
An AI dashboard with quality, → Module 8: Integrate everything
hallucination, drift, tokens. OTel + infra dashboards + AI
monitoring in a production-ready stack.
Success Criteria
Your project is complete when:
- ✅ The monitoring layer processes requests and produces analysis of the 4 dimensions (prompts, tokens, drift, hallucinations)
- ✅ The baselines are established automatically with the first N requests
- ✅ The drift simulation generates drift_checker alerts
- ✅ The hallucination simulation generates hallucination_rate_high alerts
- ✅ Normal post-baseline traffic doesn't generate spurious alerts
- ✅ The dashboard shows metrics consistent with what happened in the simulation
- ✅ The OTel attributes are ready to be sent to the tracing backend
- ✅ You can explain each AI-specific metric and what action to take when it changes
Summary
- This project integrates all of Module 6: prompt tracking, token monitoring, drift detection, hallucination detection, custom metrics.
- The
AIMonitoringLayercentralizes the processing: each request passes through all components and produces a unified result. - Baselines are the foundation. Without a normal reference period, there's no anomaly detection.
- The simulation validates 4 phases: normal (baselines), drift (short responses), hallucination (fabricated outputs), recovery (stabilization).
- An AI-specific dashboard with metrics that only exist in AI systems: quality score, hallucination rate, drift score.
- OTel attributes in each request let you integrate with Jaeger/Tempo/Datadog for searching and correlation.
- The difference: you went from "we monitor latency and cost" to "we monitor what our LLM says, how well it says it, and whether it changed how it says it."
Additional Resources
- OpenTelemetry Semantic Conventions for GenAI — Standard conventions for instrumenting LLMs
- LangSmith Documentation — A complementary tool for debugging
- Evidently AI — Open-source ML monitoring framework
- Arize Phoenix — Open-source LLM observability
- Ragas — Evaluation framework for RAG
- Chen et al. — How is ChatGPT's behavior changing over time? — Academic evidence of model drift
- Eugene Yan — LLM Patterns — Production patterns for LLM systems
- Google SRE — Monitoring Distributed Systems — Monitoring principles