惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Secure Thoughts
雷峰网
雷峰网
罗磊的独立博客
T
The Blog of Author Tim Ferriss
阮一峰的网络日志
阮一峰的网络日志
量子位
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
云风的 BLOG
云风的 BLOG
人人都是产品经理
人人都是产品经理
GbyAI
GbyAI
Cisco Talos Blog
Cisco Talos Blog
Engineering at Meta
Engineering at Meta
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
A
About on SuperTechFans
D
Darknet – Hacking Tools, Hacker News & Cyber Security
The Cloudflare Blog
Know Your Adversary
Know Your Adversary
T
Threat Research - Cisco Blogs
Spread Privacy
Spread Privacy
D
DataBreaches.Net
T
The Exploit Database - CXSecurity.com
K
Kaspersky official blog
Cyberwarzone
Cyberwarzone
爱范儿
爱范儿
U
Unit 42
Security Latest
Security Latest
M
MIT News - Artificial intelligence
月光博客
月光博客
Scott Helme
Scott Helme
G
Google Developers Blog
有赞技术团队
有赞技术团队
T
Tor Project blog
宝玉的分享
宝玉的分享
Y
Y Combinator Blog
博客园 - Franky
H
Hackread – Cybersecurity News, Data Breaches, AI and More
aimingoo的专栏
aimingoo的专栏
The GitHub Blog
The GitHub Blog
V
V2EX
B
Blog
Apple Machine Learning Research
Apple Machine Learning Research
S
Securelist
博客园 - 三生石上(FineUI控件)
Blog — PlanetScale
Blog — PlanetScale
TaoSecurity Blog
TaoSecurity Blog
Stack Overflow Blog
Stack Overflow Blog
P
Proofpoint News Feed
腾讯CDC
D
Docker
Google Online Security Blog
Google Online Security Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Your RAG Eval Set Is Probably Wrong. The Test That Catches It.
Gabriel Anha · 2026-04-27 · via DEV Community

Picture a composite scenario drawn from several RAG postmortems: a team ships a legal-research assistant with a Ragas faithfulness score in the mid-0.9s and an answer-relevance score not far behind. Two weeks after launch, customer-success starts forwarding screenshots of the bot citing the wrong jurisdiction. The eval scores never move.

The eval set is a few hundred questions hand-written months earlier. Production users are running tax-court queries with citation patterns that do not exist anywhere in the eval set. The evals are measuring how well the system answered last quarter's questions, not this morning's. The dashboard is green for a system nobody is actually using.

Most teams blame the retriever. It is almost never the retriever. Your eval set is wrong in three ways at once, and only one of them shows up in your metrics. Below: the three failure modes, and a single drift test that catches the worst of them in 40 lines of Python.

The three ways your eval set lies to you

Each of these ships to production regularly. Each one looks like a great eval score on the dashboard.

1. Leakage into training data

If you generated your eval questions with GPT-4 against your corpus, and your corpus is on the public internet, your eval is half-leaked already. The model has seen the documents and probably seen something close to your "synthetic" questions. The PremAI 2026 RAG eval guide says it directly: optimizing for Ragas scores on a leaked set produces systems that look good in CI and break the day a real user asks a real question.

The signal is subtle. Faithfulness is high because the model has memorized the answers. Context recall is high because the retriever finds the chunks the model already knows. The eval is measuring memorization, with a thin retrieval check on top.

To check, ask your model the eval question with no retrieved context. If the closed-book answer score is within 5 points of the RAG answer score, the retriever is not adding value, and the eval is measuring something else.

2. Drift from real production queries

This is the one that bit the legal team above, and it is the one that is most often invisible. Your eval set was hand-written or LLM-generated against a snapshot of intent that existed at one point in time. Your production traffic is a moving target. New segments show up, the vocabulary shifts, novel failure modes appear. The eval set freezes; reality does not.

Atlan's framework comparison makes the same point: knowledge bases that change daily drift faster than the eval sets watching them. The evals stay green while the product gets worse.

This is the failure mode the 40-line test below is designed to catch.

3. Judge bias

Most modern RAG evals (Ragas, DeepEval, TruLens) use an LLM as the grader. That works until the judge has the same blind spot as the system under test. If both are GPT-4o, both share opinions about what "faithful" looks like, both prefer answers that hedge, both penalize the same kind of curt phrasing. You are scoring with a ruler made by the same factory that produced the thing being measured.

The Ragas paper discusses this directly, and the LLM-judge bias literature (Zheng et al., "Judging LLM-as-a-Judge") shows that swapping the judge model can move metric scores meaningfully, often into the high single digits, without any change to the system being evaluated. Run one cross-judge sanity check: grade with gpt-4o, grade with claude-sonnet-4, compare. Without that, your scores have a confidence interval you cannot see.

Drift is the one that hits production hardest because it gets worse over time without any code change. You ship green. Traffic shifts. The eval stays green. Customers churn. The other two are bad on day one. Drift is bad on day 90.

The query-distribution drift test

Here is the test, in 40 lines. It compares the distribution of your eval-set queries against the distribution of your production queries along three axes (embedding-space centroid distance, length distribution, and intent-class distribution) and alerts when any of the three crosses a threshold.

import os
from collections import Counter

import numpy as np
from openai import OpenAI
from scipy.stats import wasserstein_distance

client = OpenAI()
EMBED_MODEL = "text-embedding-3-large"


def embed(texts):
    out = client.embeddings.create(input=texts, model=EMBED_MODEL)
    return np.array([d.embedding for d in out.data])


def centroid_cosine(a, b):
    ca, cb = a.mean(axis=0), b.mean(axis=0)
    return float(
        1 - (ca @ cb) / (np.linalg.norm(ca) * np.linalg.norm(cb))
    )


def length_emd(a, b):
    la = np.array([len(x.split()) for x in a], dtype=float)
    lb = np.array([len(x.split()) for x in b], dtype=float)
    return float(wasserstein_distance(la, lb))


def intent_tv(a_labels, b_labels):
    keys = set(a_labels) | set(b_labels)
    ca, cb = Counter(a_labels), Counter(b_labels)
    pa = np.array([ca[k] / len(a_labels) for k in keys])
    pb = np.array([cb[k] / len(b_labels) for k in keys])
    return float(0.5 * np.abs(pa - pb).sum())  # total variation


def drift_report(eval_q, eval_intents, prod_q, prod_intents):
    ea, eb = embed(eval_q), embed(prod_q)
    return {
        "centroid_cosine": centroid_cosine(ea, eb),
        "length_emd":      length_emd(eval_q, prod_q),
        "intent_tv":       intent_tv(eval_intents, prod_intents),
    }


THRESHOLDS = {
    "centroid_cosine": 0.05,
    "length_emd":      4.0,
    "intent_tv":       0.15,
}


def alert_if_drift(report):
    fired = [
        (k, v) for k, v in report.items()
        if v > THRESHOLDS[k]
    ]
    if fired:
        print("DRIFT", fired)
        return True
    return False

Enter fullscreen mode Exit fullscreen mode

Three signals, one report. Each one catches a different shape of drift.

Centroid cosine distance is the headline number. It says: the average semantic location of your eval queries has moved this far from the average semantic location of your production queries. A sane starting point is around 0.05 in text-embedding-3-large space for short queries, but tune it on a baseline week of your own data. The Evidently embedding drift writeup covers the same idea with reference data versus current data.

The second signal catches a specific failure mode. Length earth-mover distance flags it when your eval set is mostly short, well-formed questions and your production traffic is long, copy-pasted dumps from internal Slack threads. Same intent, very different retrieval shape. The retriever performs differently on a 6-word question and a 90-word one, and a length-blind eval misses that entirely.

The case where vocabulary stays similar but the user task changes is what intent total-variation distance catches. If 30% of your eval set is "definition of X" and 50% of your prod traffic is "compare X and Y," the eval score on definition questions does not predict the system's behavior on comparison questions. Use a small classifier (even a 5-class zero-shot one) and compare label distributions. As a starting threshold, anything past 0.15 total variation is a different product.

Wiring it into a periodic check

The 40 lines above are a function. The piece you actually ship is the cron that runs them and the alert that pages someone.

# Run nightly. Sample 500 prod queries from the last 24h.
import json
import sqlite3

def load_eval():
    with open("eval/queries.jsonl") as f:
        rows = [json.loads(l) for l in f]
    return [r["q"] for r in rows], [r["intent"] for r in rows]


def sample_prod(db_path, n=500):
    conn = sqlite3.connect(db_path)
    cur = conn.execute(
        "SELECT query, intent FROM rag_logs"
        " WHERE ts > datetime('now', '-1 day')"
        " ORDER BY RANDOM() LIMIT ?", (n,))
    rows = cur.fetchall()
    return [r[0] for r in rows], [r[1] for r in rows]


if __name__ == "__main__":
    eq, ei = load_eval()
    pq, pi = sample_prod("rag.db")
    report = drift_report(eq, ei, pq, pi)
    print(report)
    if alert_if_drift(report):
        # post to Slack, page on-call, open a ticket.
        ...

Enter fullscreen mode Exit fullscreen mode

The thresholds above are sane defaults for a corpus of business documents and short user queries. Tune them on your own data: run the script for seven days against a frozen eval set with stable traffic, take the 95th percentile of each signal, set the threshold there.

What the alert should actually do

A drift alert is not a bug ticket. It is a "your evaluation is no longer measuring what your users do" alert. The right response is to refresh the eval set, not to chase a fix in the system.

Three actions to bake into the runbook:

  1. Sample 200 production queries from the drift window for human labeling (a smaller, manually reviewed slice than the 500 the cron uses for the drift signal), label them with the same intent classifier, add them to a prod_addendum slice of the eval set.
  2. Re-run all metrics against the addendum specifically. If your faithfulness drops 10 points on the new slice, the system is failing on the new query shape. That is the bug to fix.
  3. Decay the original eval set. Mark every question with a created_at. After 90 days, drop questions whose intent class no longer appears in production. Eval sets should age out the same way feature flags do.

Run this loop and you have a system you can trust instead of a dashboard you watch. The Galileo data drift docs push the same idea: an eval set is a living artifact, not a fixture file.

The headline

Your eval scores can be high while your product is broken. The likeliest reason is not your retriever or your model. It is that your eval set has drifted away from the queries your users are actually sending. The 40 lines above will not fix the drift. They will tell you it happened, the day it happened, before customer-success forwards the screenshot.

If your dashboard is green and your support queue is red, the test runs in 40 lines. Start there.

If this was useful

The eval and observability chapters of RAG Pocket Guide and LLM Observability Pocket Guide cover the full version of the test above: proper baselines, judge-swap protocols, the slice-by-slice breakdown that catches the failure modes Ragas alone misses, and the on-call runbook for drift alerts. If your evals are green and your users are not, both books are written for that gap.