Quality & Drift Monitoring Across Models

9 min read Module 8 of 10 Lesson 23 of 28

What you'll learn

  • Explain why a gateway needs quality monitoring in addition to the latency/error tracing from Lesson 22
  • Implement an LLM-as-judge scorer that rates a sampled response against a rubric
  • Aggregate judge scores per deployment over time and detect a statistically meaningful drop
  • Explain the specific failure mode drift monitoring catches that error-rate monitoring cannot: a model that still returns 200s but answers worse
Building this at your company? For enterprise and company teams taking this to production: book a 30-minute session with our AI engineers for architecture guidance, code review, and a rollout plan for your use case.
Book a Team Session

Lesson 22 instrumented every request for latency, tokens, and errors. None of that catches a specific, real production failure mode: a deployment that keeps returning perfectly successful 200 responses, at normal latency, while the actual quality of its answers quietly gets worse. This happens for real reasons, a provider updates the model version behind a stable-sounding name, a self-hosted deployment’s configuration drifts, and it is invisible to every signal Lesson 22 tracks. This lesson builds the layer that catches it.

LLM-as-Judge: Scoring Sampled Responses

Human review of every production response doesn’t scale. LLM-as-judge substitutes a separate scoring call, run against a sample of real traffic, using a strong model and an explicit rubric:

import json
from openai import OpenAI

judge_client = OpenAI(api_key="sk-...")  # a strong, stable model used consistently as the judge

JUDGE_PROMPT = """Rate the following AI assistant response on a 1-5 scale for:
- Correctness: is the information accurate?
- Relevance: does it actually address the user's request?
- Completeness: is anything important missing?

User request: {user_request}
Assistant response: {response}

Respond with strict JSON: {{"score": 1-5, "reasoning": "one sentence"}}"""

def judge_response(user_request: str, response_text: str) -> dict:
    result = judge_client.chat.completions.create(
        model="gpt-4o",              # a fixed, stable judge model, kept constant so scores are comparable over time
        messages=[{"role": "user", "content": JUDGE_PROMPT.format(
            user_request=user_request, response=response_text,
        )}],
        temperature=0,
        response_format={"type": "json_object"},
    )
    return json.loads(result.choices[0].message.content)

judge_response(
    user_request="What's the refund window for a defective item?",
    response_text="Defective items can be returned within 30 days for a full refund.",
)
# -> {"score": 5, "reasoning": "Directly and accurately answers the question."}

Keeping the judge model itself fixed and stable is important: if the judge changes, score movements become impossible to attribute to the deployment being evaluated versus a change in how it’s being scored.

Sampling and Aggregating in the Gateway Pipeline

import random

JUDGE_SAMPLE_RATE = 0.05   # score 5% of production traffic, enough for a stable trend without judging every call

def maybe_score_response(deployment: str, user_request: str, response_text: str):
    if random.random() > JUDGE_SAMPLE_RATE:
        return
    result = judge_response(user_request, response_text)
    write_quality_score(deployment=deployment, score=result["score"], timestamp=now())
-- Rolling daily average quality score per deployment
SELECT
    deployment,
    date_trunc('day', timestamp) AS day,
    AVG(score) AS avg_score,
    COUNT(*) AS sample_count
FROM quality_scores
WHERE timestamp >= now() - interval '30 days'
GROUP BY deployment, day
ORDER BY deployment, day;

Detecting Drift

Drift is a sustained, meaningful decline in a deployment’s average score with no corresponding change on the gateway’s own side, exactly the signature that points outward, at something changing about the deployment itself:

def detect_drift(deployment: str, baseline_window_days=14, recent_window_days=3, threshold=0.5) -> bool:
    baseline_avg = get_avg_score(deployment, days_back=baseline_window_days, days_back_end=recent_window_days)
    recent_avg = get_avg_score(deployment, days_back=recent_window_days, days_back_end=0)

    if baseline_avg - recent_avg > threshold:
        alert(f"Quality drift detected for {deployment}: "
              f"baseline {baseline_avg:.2f} -> recent {recent_avg:.2f}")
        return True
    return False
flowchart LR
    Prod["Production traffic"] --> Sample["Sample ~5%\n(JUDGE_SAMPLE_RATE)"]
    Sample --> Judge["LLM-as-judge scoring\n(fixed judge model)"]
    Judge --> Store["Rolling per-deployment\nscore history"]
    Store --> Compare["Compare recent window\nvs baseline window"]
    Compare -->|"meaningful decline"| Alert["Drift alert:\ninvestigate deployment"]
    Compare -->|"stable"| OK["No action"]

    style Prod fill:#EEF0F7,stroke:#6366F1,color:#0F172A,rx:8px,ry:8px
    style Judge fill:#4338CA,stroke:#4338CA,color:#ffffff,rx:8px,ry:8px
    style Alert fill:#F7F8FC,stroke:#0D9488,color:#0F172A,rx:8px,ry:8px
    style OK fill:#F7F8FC,stroke:#0D9488,color:#0F172A,rx:8px,ry:8px

A drift alert on a deployment is information the routing engine (Module 4) should be able to act on, exactly as it would react to a latency or availability signal: temporarily deprioritizing a drifting deployment, or triggering a manual review of whether it still belongs in a fallback chain (Lesson 14) at its previous tier. Lesson 24 closes Module 8 with the complementary practice for a planned change rather than an unplanned drift: safely rolling out a model upgrade using A/B testing and shadow traffic.

Knowledge Check

3 questions to test your understanding

1 Why isn't tracing latency and error rate (Lesson 22) enough to catch every important quality problem with a routed deployment?

2 What is 'LLM-as-judge' as used in this lesson's quality monitoring pipeline?

3 A deployment's average LLM-as-judge score, tracked daily, drops from a stable 4.3/5 to 3.1/5 over three days with no code or config change on the gateway side. What does this pattern most likely indicate, and what should it trigger?

Go further with expert guidance

Ready to build production AI?
Talk to our R&D team.

These courses give you the foundation. Our embedded AI teams take you from prototype to production in 30–90 days, with your team, your codebase, your goals. Book a free strategy call to see how we can accelerate your AI initiative.

30 minutes · No obligation · Expert AI engineers, not sales reps

AI Architecture Review

Audit your current stack and identify high-impact improvements

Project Review

Get expert feedback on your AI implementation and codebase

Team Mentoring

Upskill your engineers with hands-on AI coaching sessions

AI Strategy

Define your AI roadmap, prioritization, and implementation plan