1. Why Evaluation Matters
Large language models produce high-dimensional, stochastic outputs. A single prompt can yield many acceptable answers, and many subtly wrong ones. Without systematic evaluation, teams ship regressions discovered only through user complaints, support tickets, or viral social posts.
Evaluation is the feedback loop that connects model capability to product value. It answers: Is this model or system good enough for our users, on our tasks, under our constraints?
What goes wrong without eval
- Silent regressions: A prompt tweak improves tone but breaks JSON schema compliance on 8% of invoices.
- Leaderboard theater: A model scores well on MMLU but fails your retrieval-grounded support workflow.
- Cost surprises: A "smarter" model doubles tokens per task without improving task success.
- Safety gaps: Jailbreak resistance drops after a fine-tune nobody regression-tested.
- Slice blindness: Average accuracy hides catastrophic failure on low-resource languages or edge-case document formats.
Evaluation spans the full stack
| Layer | What you measure | When |
|---|---|---|
| Base model | Knowledge, reasoning, coding, multilingual ability | Model selection, pre-deployment |
| Prompt / system design | Instruction following, format compliance, tone | Every prompt change |
| RAG pipeline | Retrieval recall, faithfulness, citation accuracy | Index or chunking changes |
| Agent / tools | Task completion, tool selection, error recovery | Tool schema or planner changes |
| Production system | Latency, cost, thumbs-down rate, escalation rate | Continuous monitoring |
Core principles
- Task-specific signal beats generic benchmarks for product decisions.
- Measure what users experience, not only what is easy to automate.
- Version everything: model ID, prompt hash, retrieval index, tool definitions.
- Stratify results by category, locale, and user segment.
- Pair automatic metrics with periodic human review.
Refer: Holistic Evaluation of Language Models (HELM) · AlpacaEval / length-controlled win rates
2. Intrinsic vs Extrinsic Evaluation
Understanding this distinction prevents optimizing the wrong objective.
Intrinsic evaluation
Measures properties of the model in isolation, typically on standardized prompts with reference answers or scoring rubrics. Examples:
- Perplexity on a held-out text corpus
- Accuracy on MMLU, GSM8K, HumanEval
- Win rate on MT-Bench or Arena-style comparisons
- Refusal rate on a red-team prompt set
Intrinsic eval is fast, reproducible, and comparable across models. It is essential for model selection and research, but it only approximates real product performance.
Extrinsic evaluation
Measures performance on the actual downstream task or product the model powers:
- Does the support bot resolve tickets without human escalation?
- Does the coding assistant produce mergeable PRs?
- Does the legal summarizer preserve obligation clauses correctly?
- Does the sales email drafter match brand voice and factual constraints?
Extrinsic eval is slower, noisier, and often requires proprietary data. It is the metric that ultimately matters for business outcomes.
| Dimension | Intrinsic | Extrinsic |
|---|---|---|
| Data | Public or synthetic benchmarks | Real user queries, logs (redacted), task outcomes |
| Cost | Low to moderate (API spend) | High (labeling, integrations, wait for outcomes) |
| Generalizability | Cross-model comparison | Specific to your product |
| Leakage risk | High (benchmark contamination) | Lower if you own the eval set |
| Actionability | Coarse go/no-go on capability | Direct ship/rollback decisions |
Bridging the gap
Build a custom golden set derived from production tasks. It acts as a proxy extrinsic eval that you can run cheaply in CI. Calibrate it quarterly against true extrinsic outcomes (ticket resolution, revenue, human ratings) to ensure the proxy still correlates.
3. Evaluation Lifecycle
Evaluation is not a one-time gate before launch. It is a continuous lifecycle tied to every change in the ML and product stack.
Phase 1: Define success criteria
- What does "good" mean for users? (correctness, helpfulness, safety, speed, cost)
- What are hard constraints? (JSON schema, regulatory language, PII handling)
- What slices matter disproportionately? (enterprise tier, medical queries, non-English)
Phase 2: Build eval assets
- Curate golden prompts from logs (anonymized, consented)
- Label expected behaviors or reference answers
- Define automatic scorers and human rubrics
- Establish baselines on current production config
Phase 3: Pre-deployment validation
- Run full eval suite on candidate model, prompt, or pipeline
- Compare against baseline with statistical tests where sample size allows
- Review failure cases manually before launch
Phase 4: Controlled rollout
- Canary or A/B test with online metrics
- Monitor safety, latency, cost, and user feedback in real time
- Automatic rollback on threshold breach
Phase 5: Continuous improvement
- Feed production failures into eval set (failure-driven dataset growth)
- Deprecate stale prompts that no longer reflect the product
- Re-baseline after major feature changes
# Eval lifecycle as versioned artifacts
eval_suite/
golden_v3.2.jsonl # prompts + labels + metadata
rubrics/
support_helpfulness.yaml
json_schema_invoice.yaml
baselines/
2025-06-01_gpt-4o-mini_prompt-f4e8.json
results/
2025-07-10_candidate-lora_run-0042.json
4. Benchmark Catalog
Public benchmarks provide coarse capability maps. Use them for model shopping, not as sole ship criteria.
Knowledge and reasoning
| Benchmark | Measures | Notes |
|---|---|---|
| MMLU | 57-subject multiple-choice knowledge | Industry standard; contamination concerns |
| MMLU-Pro | Harder multi-step MCQ | Reduces guessing; fewer items |
| GPQA | Graduate-level science QA | Very hard; small set |
| BBH | 23 challenging reasoning tasks | Chain-of-thought sensitive |
| ARC-Challenge | Science exam questions | Classic; largely saturated by top models |
| HellaSwag | Commonsense continuation | Mostly saturated |
| TruthfulQA | Resistance to popular misconceptions | Measures factuality under traps |
Mathematics
| Benchmark | Measures | Notes |
|---|---|---|
| GSM8K | Grade-school word problems | Verifiable answers; good for reasoning eval |
| MATH | Competition mathematics | Hard; needs step-by-step grading |
| MathVista / MMMU | Multimodal math | Requires vision capability |
Code
| Benchmark | Measures | Notes |
|---|---|---|
| HumanEval | Python function synthesis | pass@k with unit tests |
| MBPP | Basic Python programming | Broader than HumanEval |
| SWE-bench | Real GitHub issue resolution | Agentic; expensive to run |
| LiveCodeBench | Fresh competitive programming | Reduces training leakage via recency |
Instruction following and chat
| Benchmark | Measures | Notes |
|---|---|---|
| MT-Bench | Multi-turn chat quality | LLM-as-judge; GPT-4 judge common |
| AlpacaEval 2 | Win rate vs reference | Length-controlled variants reduce verbosity bias |
| IFEval | Verifiable instruction constraints | Count words, JSON format, etc. |
| Arena-Hard | Hard user-style prompts | Closer to real chat distribution |
RAG and long-context
| Benchmark | Measures | Notes |
|---|---|---|
| RAGAS (framework) | Faithfulness, relevancy, recall | Run on your corpus, not a single leaderboard |
| HotpotQA | Multi-hop QA | Retrieval + reasoning |
| Needle-in-a-haystack | Long-context recall | Synthetic; tests context window use |
| LongBench | Long document tasks | Summarization, QA, few-shot |
Safety and robustness
| Benchmark | Measures | Notes |
|---|---|---|
| ToxiGen | Toxic generation | Demographic sensitivity |
| HarmBench | Red-team refusal | Standardized attack prompts |
| AdvBench | Jailbreak success rate | Adversarial suffix attacks |
| WinoBias / BBQ | Social bias in completions | Slice by demographic axis |
5. Golden Sets and Custom Eval Data
A golden set (eval set, regression suite) is your highest-signal asset: real or realistic prompts with labeled expectations, curated to represent production traffic.
Sourcing strategies
- Production logs: Sample queries (PII-redacted, consent-checked). Highest fidelity.
- Support tickets: Escalated cases become permanent regression items.
- Synthetic augmentation: Paraphrase and perturb real prompts to increase coverage.
- Expert-authored: Domain specialists write edge cases engineers miss.
- Adversarial mining: Red-team failures, jailbreaks, and user-reported bugs.
Golden set schema
{
"id": "support-0042",
"prompt": "My order #8821 shows delivered but I never received it.",
"context": ["chunk-991", "chunk-442"], // optional RAG refs
"expected": {
"must_mention": ["refund", "investigation"],
"must_not_mention": ["guaranteed refund without verification"],
"json_schema": null,
"reference_answer": "..." // optional
},
"metadata": {
"category": "shipping",
"locale": "en-US",
"difficulty": "medium",
"source": "prod_log_2025-06",
"human_labeler": "alice@corp.com"
},
"grading": {
"type": "llm_judge",
"rubric_id": "support_helpfulness_v2"
}
}
Size and composition guidelines
| Suite type | Typical size | Purpose |
|---|---|---|
| Smoke | 20-50 items | Fast PR gate (<5 min) |
| Core regression | 200-1,000 items | Nightly or pre-release |
| Full golden | 1,000-10,000+ items | Major model migrations |
| Slice-critical | 50-200 per slice | Legal, medical, non-English |
Quality practices
- Balance categories to match production mix (or overweight high-risk slices).
- Version and changelog every addition or label edit.
- Hold out a secret set that engineers cannot overfit prompts against.
- Review disagreements between raters; fix ambiguous rubrics.
- Expire stale items when product behavior intentionally changes.
6. Contamination and Data Leakage
Contamination occurs when benchmark test data (or close paraphrases) appears in pretraining, fine-tuning, or eval-set construction, inflating scores without improving real capability.
Types of leakage
- Pretraining leakage: Benchmark Q&A memorized from the web crawl.
- Fine-tuning leakage: Eval prompts accidentally included in SFT or DPO data.
- Prompt leakage: Engineers iterate prompts against the test set (implicit overfitting).
- Judge leakage: The judge model saw benchmark solutions during training.
- Pipeline leakage: Retrieval index built from documents that contain benchmark answers.
Detection methods
| Method | How it works |
|---|---|
| N-gram overlap | Compare benchmark text to training corpus shards |
| Min-hash / dedup | Near-duplicate detection at scale |
| Canary strings | Insert unique tokens; check if model reproduces them |
| Held-out fresh benchmarks | LiveCodeBench, private unreleased sets |
| Perturbation tests | Rephrase questions; large score drops suggest memorization |
Mitigation playbook
- Filter training data with decontamination pipelines (e.g., n-gram blocklists from test sets).
- Keep a private holdout never shown to anyone tuning prompts.
- Separate dev (for iteration) and test (for reporting) splits.
- Prefer dynamic benchmarks that rotate or use fresh content.
- Report contamination audits alongside leaderboard numbers.
# Simple n-gram overlap check (illustrative)
from collections import Counter
def ngrams(text, n=13):
tokens = text.lower().split()
return set(tuple(tokens[i:i+n]) for i in range(len(tokens) - n + 1))
bench_ng = ngrams(benchmark_question)
train_ng = ngrams(training_shard)
overlap = len(bench_ng & train_ng) / max(len(bench_ng), 1)
# High overlap -> investigate for contamination
7. Automated Metrics
Automatic metrics scale evaluation beyond what human labelers can afford. Each metric captures a narrow facet of quality; combine several rather than relying on one number.
Classification-style metrics
| Metric | Best for | Limitation |
|---|---|---|
| Exact match (EM) | Short-form QA, IDs, dates | Ignores paraphrases |
| F1 token overlap | Extractive QA | Penalizes valid rephrasing |
| Accuracy | Multiple choice, classification | Hides per-class failure |
| pass@k | Code generation | Needs executable tests |
Text overlap metrics
BLEU and ROUGE measure n-gram overlap against reference summaries. They are cheap and interpretable but correlate weakly with human judgment on open-ended generation. Use them as sanity checks, not primary quality signals.
$$ \text{BLEU} = \text{BP} \cdot \exp\left(\sum_{n=1}^{N} w_n \log p_n\right) $$ROUGE-L measures longest common subsequence overlap. ROUGE-1/2 use unigram/bigram recall.
Semantic similarity metrics
- Embedding cosine similarity between output and reference.
- BERTScore: Token-level contextual similarity.
- BLEURT: Learned metric trained on human ratings.
These capture paraphrase better than n-gram metrics but can reward vague, generic answers that embed similarly to references.
Structured output metrics
- JSON schema validity rate
- Field-level accuracy (per-key exact match)
- SQL execution accuracy (run generated SQL, compare result sets)
- API call validity (correct tool name and argument types)
RAG-specific automatic metrics
- Context precision / recall (are retrieved chunks relevant?)
- Faithfulness / groundedness (is the answer supported by context?)
- Answer relevancy (does the answer address the query?)
- Citation accuracy (do cited spans support claims?)
Operational metrics (always report alongside quality)
- Latency p50 / p95 / p99 per stage
- Tokens in / out per task
- Cost per successful task
- Error rate (timeouts, 5xx, parse failures)
8. LLM-as-Judge
LLM-as-judge uses a strong model to score, rank, or critique outputs against a rubric. It scales subjective evaluation when human labeling is too slow or expensive.
Common judge patterns
| Pattern | Description | Use case |
|---|---|---|
| Pointwise scoring | Rate one output 1-10 on criteria | Regression tracking |
| Pairwise preference | Pick A or B (or tie) | Model comparison, RLHF data |
| Binary checklist | Pass/fail per rubric item | CI gates |
| Chain-of-thought judge | Reason then score | Complex reasoning tasks |
| Reference-guided | Compare to gold answer | QA with references |
Known biases and mitigations
| Bias | Symptom | Mitigation |
|---|---|---|
| Position bias | Favors first option in pairwise | Swap order, aggregate both |
| Verbosity bias | Rewards longer answers | Length-controlled prompts, AlpacaEval LC |
| Self-preference | Judge favors own style/model family | Use different judge model family |
| Anchoring | Overweights reference answer quirks | Blind references, multiple refs |
| Sycophancy | Agrees with flawed reasoning | Require evidence citations from context |
import json
from openai import OpenAI
client = OpenAI()
JUDGE_PROMPT = """You are an expert evaluator.
Score the assistant response on correctness (0-5) and helpfulness (0-5).
Return JSON only: {"correctness": int, "helpfulness": int, "rationale": str}
User query: {query}
Assistant response: {response}
Reference (optional): {reference}
"""
def judge(query, response, reference=""):
msg = JUDGE_PROMPT.format(query=query, response=response, reference=reference)
out = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": msg}],
temperature=0,
response_format={"type": "json_object"},
)
return json.loads(out.choices[0].message.content)
# Calibrate monthly against human labels
# target: Pearson r > 0.7 on core rubric dimensions
Calibration protocol
- Sample 200+ items with dual human ratings.
- Run judge on same items; compute correlation and confusion matrix.
- Adjust rubric wording where judge disagrees systematically.
- Track judge-model version; re-calibrate when judge upgrades.
9. RAG Evaluation
Retrieval-augmented generation fails in two independent places: retrieval (wrong context) and generation (misuse of context). Evaluate both, plus end-to-end answer quality.
Retrieval metrics
| Metric | Formula / definition | Requires |
|---|---|---|
| Recall@k | Fraction of queries where a relevant doc is in top-k | Relevance labels |
| Precision@k | Fraction of top-k docs that are relevant | Relevance labels |
| MRR | Mean reciprocal rank of first relevant doc | Relevance labels |
| nDCG@k | Discounted cumulative gain with position penalty | Graded relevance |
| Hit rate | Any relevant doc retrieved (binary) | Relevance labels |
where $\text{rank}_q$ is the position of the first relevant document for query $q$, or 0 if none found.
Generation metrics
- Faithfulness: Every claim in the answer is entailed by retrieved context.
- Answer relevancy: The answer addresses the user question.
- Hallucination under empty context: Does the model abstain or invent when retrieval returns nothing?
- Citation precision: Linked spans actually support the claim.
End-to-end RAG eval with RAGAS
from datasets import Dataset
from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
context_precision,
context_recall,
)
# Each row: question, answer, contexts (list[str]), ground_truth (optional)
eval_data = {
"question": [
"What is the refund window for enterprise plans?",
"Who approves SOC2 exceptions?",
],
"answer": [
"Enterprise refunds are available within 60 days of purchase.",
"The security team lead approves SOC2 exceptions.",
],
"contexts": [
["Enterprise plans include a 60-day refund policy..."],
["SOC2 exception requests route to the security team lead..."],
],
"ground_truth": [
"60-day refund window for enterprise.",
"Security team lead approves SOC2 exceptions.",
],
}
dataset = Dataset.from_dict(eval_data)
result = evaluate(
dataset=dataset,
metrics=[faithfulness, answer_relevancy, context_precision, context_recall],
)
print(result.to_pandas())
# Investigate rows where faithfulness < 0.8 manually
Custom faithfulness checker (LLM-free heuristic + judge)
import re
def extract_claims(answer: str) -> list[str]:
# Split into sentences as crude claims
return [s.strip() for s in re.split(r'[.!?]+', answer) if s.strip()]
def claim_supported(claim: str, contexts: list[str]) -> bool:
# Production: use NLI model or LLM judge with context-only prompt
ctx = "\n".join(contexts).lower()
# Heuristic demo: keyword overlap (replace in prod)
tokens = set(claim.lower().split())
return len(tokens & set(ctx.split())) >= max(2, len(tokens) // 3)
def faithfulness_score(answer: str, contexts: list[str]) -> float:
claims = extract_claims(answer)
if not claims:
return 1.0
supported = sum(claim_supported(c, contexts) for c in claims)
return supported / len(claims)
RAG eval workflow
- Build labeled (query, relevant_chunk_ids) set from click logs or human annotators.
- Run retrieval-only eval when changing embeddings, chunk size, or hybrid weights.
- Run generation eval when changing model, prompt, or reranker.
- Run full pipeline eval before release; stratify by document type and query length.
10. Agent Evaluation
Agents loop: plan, call tools, observe results, repeat. Evaluation must cover trajectory quality, not only final text.
What to measure
| Dimension | Metric examples |
|---|---|
| Task success | Goal achieved (binary), partial credit rubric |
| Tool selection | Correct tool rate, unnecessary tool calls |
| Argument correctness | Valid JSON args, schema compliance |
| Step efficiency | Steps to success, token cost per success |
| Recovery | Recovers from API error within N steps |
| Safety | No destructive actions without confirmation |
Eval environments
- Sandbox APIs: Mock Stripe, GitHub, CRM with recorded responses.
- Replay buffers: Deterministic tool outputs for regression tests.
- Simulated users: LLM user simulator sends follow-up messages.
- Real staging: Highest fidelity; expensive and flaky.
from dataclasses import dataclass, field
@dataclass
class AgentTrace:
task_id: str
steps: list[dict] = field(default_factory=list)
final_answer: str = ""
success: bool = False
def score_trace(trace: AgentTrace, gold) -> dict:
tool_names = [s["tool"] for s in trace.steps]
expected_tools = set(gold.get("required_tools", []))
forbidden = set(gold.get("forbidden_tools", []))
return {
"task_success": trace.success,
"tool_recall": len(expected_tools & set(tool_names)) / max(len(expected_tools), 1),
"forbidden_tool_violation": bool(forbidden & set(tool_names)),
"step_count": len(trace.steps),
"within_step_budget": len(trace.steps) <= gold.get("max_steps", 10),
}
# SWE-bench style: apply patch, run tests, measure resolve rate
# WebArena / OSWorld: UI automation success in simulated environments
Agent benchmark landscape
| Benchmark | Domain | Success criterion |
|---|---|---|
| SWE-bench | Software engineering | Unit tests pass after patch |
| WebArena | Web navigation | Goal state in simulated sites |
| GAIA | General assistant tasks | Exact answer match |
| τ-bench | Tool-use with user sim | Multi-turn task completion |
| AgentBench | Multi-environment | Environment-specific success |
11. Safety Evaluation
Safety eval measures whether the system refuses harmful requests, avoids toxic outputs, resists jailbreaks, and behaves fairly across demographic slices. It is a release blocker for consumer and enterprise products.
Safety dimensions
| Dimension | Examples | Measurement |
|---|---|---|
| Harmful content generation | Violence, self-harm instructions | Refusal rate on red-team set |
| Jailbreak resistance | Roleplay bypass, base64 attacks | Attack success rate (ASR) |
| PII leakage | Training data extraction | Canary extraction tests |
| Bias / fairness | Stereotyping by demographic | Slice disparity metrics |
| Over-refusal | Refusing benign medical/legal info | False refusal rate on benign set |
| Prompt injection | Hidden instructions in RAG docs | Instruction override success rate |
Red-team prompt categories
- Direct harmful requests (weapons, malware, harassment)
- Indirect elicitation (hypothetical framing, fictional context)
- Multi-turn escalation (benign start, harmful pivot)
- Tool abuse (SQL injection via agent, unauthorized API calls)
- Multilingual and encoded attacks (ROT13, leetspeak, mixed scripts)
# Safety eval result schema
{
"prompt_id": "jailbreak-017",
"category": "indirect_harm",
"model_response": "I can't help with that...",
"verdict": "pass", // pass = safe refusal or safe answer
"judge": "gpt-4o-safety-rubric-v3",
"severity": "high"
}
# Report: ASR = attacks_that_succeeded / total_attacks
# Also report over-refusal rate on benign_control_set
Guardrails in the eval loop
Evaluate the full stack, not only the base model:
- Input classifier (block or route before LLM)
- System prompt and policy layer
- Output filter (regex, moderation model)
- Human review queue for borderline cases
Refer: HarmBench · Safety learning notes
12. Human Evaluation
Humans remain the gold standard for subjective quality, nuanced correctness, and safety edge cases. Automatic metrics should be calibrated against human judgment, not replace it for high-stakes decisions.
When human eval is required
- Major model or prompt launches
- Disputes when automatic metrics and user sentiment diverge
- Rubric design and judge calibration
- Low-incidence, high-severity slices (legal, medical)
- Creative or brand-sensitive outputs
Rating designs
| Design | Description | Pros / cons |
|---|---|---|
| Likert scale | 1-5 on helpfulness, clarity, tone | Simple; rater scale drift |
| Side-by-side (SBS) | Pick A, B, or tie | High signal for comparisons; needs both outputs |
| Binary rubric | Pass/fail per criterion | Good for CI calibration |
| Holistic ranking | Rank N outputs | Efficient per dollar; harder analysis |
| Edit distance | How much rater edits output | Implicit quality signal |
Inter-rater agreement
Track agreement to ensure rubric clarity:
- Cohen's kappa for two raters, categorical labels.
- Fleiss' kappa for multiple raters.
- Krippendorff's alpha for missing data and varied scales.
Target $\kappa > 0.6$ for core dimensions. Low agreement means fix the rubric, not blame raters.
Operational best practices
- Write concrete rubrics with pass/fail examples (anchor items).
- Use 2+ raters per item for high-stakes labels.
- Blind model identity to prevent brand bias.
- Rotate raters to detect drift and fatigue.
- Close the loop: thumbs-down in prod feeds labeling queues.
- Pay fairly and train: qualification batches before production labeling.
13. Online vs Offline Evaluation
Offline eval runs on fixed datasets in a controlled harness. Online eval measures real user interactions in production. Mature teams use both in complementary roles.
Offline evaluation
| Strength | Weakness |
|---|---|
| Reproducible, version-controlled | Dataset staleness |
| Fast iteration in CI | Distribution shift from live traffic |
| Cheap relative to live experiments | Cannot capture long-term user behavior |
| Safe for destructive agent tests | May overfit to golden set |
Online evaluation
| Signal | How to collect |
|---|---|
| Explicit feedback | Thumbs up/down, star ratings, NPS |
| Implicit feedback | Copy, dwell time, regenerate, abandon |
| Task outcome | Ticket resolved, purchase completed, code merged |
| Human escalation | Handoff rate to support agent |
| Latency / cost | Real infrastructure measurements |
Bridging offline and online
- Correlate offline golden scores with online thumbs-down rate weekly.
- When correlation drops, refresh golden set from recent failures.
- Use shadow mode: run candidate model on live inputs, show baseline to user, log both outputs for offline comparison.
- Use interleaving in ranking contexts (less common for generative LLMs than for search).
# Shadow evaluation (no user impact)
async def handle_request(req):
baseline = await generate(req, model="prod-gpt-4o-mini")
shadow = await generate(req, model="candidate-lora") # async, discard from UI
log_shadow_pair(req.id, baseline, shadow)
return baseline # user always sees production
14. CI Gates and Regression Testing
CI gates block merges or deployments when eval scores drop below thresholds. They turn evaluation from a report into an enforcement mechanism.
Typical gate tiers
| Tier | Trigger | Suite | Latency budget |
|---|---|---|---|
| PR smoke | Every pull request | 20-50 golden items | 3-8 minutes |
| Nightly | Cron schedule | 500+ items, all slices | 1-3 hours |
| Pre-release | Release branch | Full golden + safety red-team | 4-12 hours |
| Post-deploy | Canary start | Online guardrail metrics | Real time |
Gate criteria examples
- Overall task success $\geq$ baseline - 1% (absolute)
- No slice drops more than 3% (legal, JSON schema, non-English)
- Safety ASR $\leq$ baseline + 0.5%
- p95 latency $\leq$ baseline + 10%
- Cost per task $\leq$ baseline + 5%
# GitHub Actions pseudo-workflow
# eval-gate.yml
on: [pull_request]
jobs:
llm-eval-smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install -r eval/requirements.txt
- run: python -m eval.run --suite smoke --baseline main
- run: python -m eval.check_regression --max-drop 0.01
Flaky eval handling
LLM outputs are stochastic. Reduce flake:
- Set
temperature=0for regression suites (or fixed seed where supported). - Run $n=3$ samples for borderline items; majority vote.
- Use confidence intervals on binary pass rates; gate on lower bound.
- Cache model responses for pure prompt-diff PRs when model is unchanged.
15. A/B Testing in Production
A/B tests compare variants (model, prompt, retrieval config) on live traffic with statistical rigor. They are the final extrinsic eval before full rollout.
Experiment design
- Hypothesis: "Candidate reduces escalation rate without increasing cost."
- Primary metric: One pre-registered success metric (escalation rate).
- Guardrail metrics: Safety ASR, latency p95, cost, thumbs-down.
- Unit of randomization: User ID or session ID (consistent experience).
- Power analysis: Estimate sample size for detectable effect.
- Duration: Run through weekly seasonality if possible.
Variant attribution requirements
Every request log must include:
{
"experiment_id": "exp-2025-07-prompt-v7",
"variant": "B",
"model": "gpt-4o-mini",
"prompt_hash": "a3f91c",
"retrieval_index_version": "idx-2025-06-14",
"user_id_hash": "u-8e2f..."
}
Statistical considerations
| Issue | Solution |
|---|---|
| Peeking (early stop) | Fixed horizon or sequential testing (SPRT) |
| Multiple comparisons | Bonferroni or FDR correction on secondary metrics |
| Network effects | Cluster randomization by team/org |
| Non-stationary traffic | CUPED variance reduction, covariate adjustment |
| Small effect sizes | Longer run or proxy metrics with higher sensitivity |
LLM-specific A/B pitfalls
- Length confound: Variant B is more verbose and looks "better" in ratings.
- Novelty effect: Short-term engagement bump after UI change.
- Heterogeneous treatment effect: Wins on simple queries, loses on complex ones.
- Cost masking: Quality improves 2% but cost rises 40%.
16. Harness Architecture
An eval harness is reproducible infrastructure that runs prompts through your system, scores outputs, and stores results for comparison. Treat it as a first-class service, not a notebook script.
Architecture components
| Component | Responsibility |
|---|---|
| Dataset loader | Versioned golden sets, filters by slice/tag |
| Runner | Invokes model + RAG + tools with frozen config |
| Scorer registry | Pluggable metrics (exact match, judge, RAGAS) |
| Result store | JSON/Parquet per run with git SHA and config hash |
| Comparator | Diff vs baseline, highlight regressions |
| Reporter | Dashboards, PR comments, Slack alerts |
Reference Python harness
"""Minimal but production-shaped LLM eval harness."""
from __future__ import annotations
import hashlib
import json
import time
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Callable
@dataclass
class EvalConfig:
model: str
temperature: float
prompt_template: str
retrieval_index: str | None = None
def fingerprint(self) -> str:
blob = json.dumps(asdict(self), sort_keys=True)
return hashlib.sha256(blob.encode()).hexdigest()[:12]
@dataclass
class EvalItem:
id: str
prompt: str
metadata: dict
expected: dict | None = None
@dataclass
class EvalResult:
item_id: str
output: str
scores: dict[str, float]
latency_ms: float
error: str | None = None
class EvalHarness:
def __init__(
self,
config: EvalConfig,
generate_fn: Callable[[str], str],
scorers: dict[str, Callable[[EvalItem, str], float]],
):
self.config = config
self.generate_fn = generate_fn
self.scorers = scorers
def run_item(self, item: EvalItem) -> EvalResult:
t0 = time.perf_counter()
try:
output = self.generate_fn(item.prompt)
err = None
except Exception as e:
output = ""
err = str(e)
latency = (time.perf_counter() - t0) * 1000
scores = {}
if not err:
for name, fn in self.scorers.items():
scores[name] = fn(item, output)
return EvalResult(item.id, output, scores, latency, err)
def run_suite(self, items: list[EvalItem]) -> list[EvalResult]:
return [self.run_item(it) for it in items]
def save_run(self, results: list[EvalResult], path: Path, git_sha: str):
payload = {
"git_sha": git_sha,
"config": asdict(self.config),
"fingerprint": self.config.fingerprint(),
"results": [asdict(r) for r in results],
"aggregate": self.aggregate(results),
}
path.write_text(json.dumps(payload, indent=2))
def aggregate(self, results: list[EvalResult]) -> dict:
ok = [r for r in results if r.error is None]
if not ok:
return {"n": len(results), "errors": len(results)}
keys = ok[0].scores.keys()
return {
"n": len(results),
"errors": len(results) - len(ok),
"mean_latency_ms": sum(r.latency_ms for r in ok) / len(ok),
**{f"mean_{k}": sum(r.scores[k] for r in ok) / len(ok) for k in keys},
}
# --- scorers ---
def exact_match(item: EvalItem, output: str) -> float:
ref = (item.expected or {}).get("reference_answer", "")
return float(output.strip().lower() == ref.strip().lower())
def json_valid(item: EvalItem, output: str) -> float:
try:
json.loads(output)
return 1.0
except json.JSONDecodeError:
return 0.0
# --- usage ---
# harness = EvalHarness(config, my_generate, {"em": exact_match, "json": json_valid})
# results = harness.run_suite(load_items("golden_v3.jsonl"))
# harness.save_run(results, Path("results/run-0042.json"), git_sha="abc123")
Design requirements
- Deterministic config capture: Every run records model, prompt, tools, index version.
- Parallel execution with rate limiting and retry on 429/5xx.
- Resume support for long suites (checkpoint per item).
- Idempotent run IDs so CI reruns do not duplicate data.
- PII scrubbing before writing outputs to shared storage.
17. Platform Tools and Frameworks
Mature eval platforms add dataset management, experiment tracking, human review UI, and production trace linking. Pick tools that integrate with your observability stack.
Open-source and commercial platforms
| Tool | Strengths | Typical use |
|---|---|---|
| Langfuse | Tracing, datasets, evals, prompt mgmt | End-to-end LLM ops |
| Braintrust | Eval loops, CI, human review | Eval-driven dev workflows |
| Weights & Biases (W&B) | Experiment tracking, tables | Research + production evals |
| LangSmith | LangChain-native tracing and datasets | LangChain/LangGraph apps |
| Phoenix (Arize) | Embeddings drift, trace analysis | RAG debugging |
| Promptfoo | YAML-driven red-team and regression | CLI CI gates, safety tests |
| RAGAS | RAG metric library | Faithfulness, context metrics |
| DeepEval | Pytest-style LLM unit tests | Developer-local testing |
| OpenAI Evals | Template eval framework | Custom benchmark prototyping |
Metric and judge libraries
| Library | Metrics |
|---|---|
| lm-eval-harness | 100+ academic benchmarks (MMLU, GSM8K, ...) |
| inspect-ai | Composable eval tasks, agent sandboxes |
| bert-score | Semantic similarity vs reference |
| sacrebleu | Standardized BLEU |
| ragas | RAG faithfulness, precision, recall |
# lm-eval-harness CLI (academic benchmarks)
# pip install lm-eval
lm_eval --model hf \
--model_args pretrained=meta-llama/Llama-3.1-8B-Instruct \
--tasks mmlu,gsm8k,ifeval \
--batch_size auto \
--output_path results/llama-8b-bench.json
# promptfoo red-team in CI
# promptfoo eval -c promptfooconfig.yaml --filter-first-n 50
Selection criteria
- Does it link production traces to eval datasets?
- Does it support your model providers (OpenAI, Anthropic, vLLM, Bedrock)?
- Can it gate CI with regression thresholds?
- Does it handle PII and access control for enterprise?
- Is there an escape hatch (export JSON, self-host)?
Refer: Langfuse notes · lm-evaluation-harness
18. Eval-Driven Development
Eval-driven development (EDD) mirrors test-driven development: define expected behavior as eval cases first, then iterate prompts, models, and pipelines until the suite passes. It shifts LLM work from artisanal tweaking to engineering discipline.
EDD workflow
- Capture failure: User report or prod trace becomes a new golden item.
- Write rubric: Define pass criteria before changing the system.
- Reproduce: Confirm baseline fails the new item.
- Fix: Adjust prompt, retrieval, or model.
- Verify: New item passes; regression suite still passes.
- Commit: Golden item + fix in same PR.
Culture and process
| Practice | Outcome |
|---|---|
| Every bug becomes an eval | Failure never recurs silently |
| Eval review in PRs | Label quality stays high |
| Weekly scorecards | Visibility across model/prompt changes |
| Eval budget in sprints | Time allocated for dataset curation |
| PM owns success criteria | Metrics align with product value |
Relationship to fine-tuning
Eval sets drive training data too:
- Failed golden items become SFT examples with corrected outputs.
- Pairwise preferences from human SBS feed DPO/RLHF.
- Keep train/eval disjoint to avoid fine-tuning leakage.
19. Failure Modes and Debugging
When eval scores drop or users complain, systematic debugging beats random prompt edits. Map symptoms to likely layers.
Symptom to layer map
| Symptom | Likely layer | First checks |
|---|---|---|
| Wrong facts on static knowledge | Base model | Intrinsic benchmarks, knowledge cutoff |
| Ignores instructions | Prompt / system message | IFEval, schema compliance rate |
| Answers without doc support | RAG generation | Faithfulness metric, empty-context test |
| Missing relevant info | Retrieval | Recall@k, chunk overlap analysis |
| Wrong API called | Agent planner | Tool selection accuracy |
| Sudden cost spike | Inference config | Tokens per request, loop detection |
| Jailbreaks after deploy | Safety stack | Red-team ASR, guardrail logs |
| Regression on one locale | Data slice | Stratified eval by language |
Common eval system failures
- Metric gaming: Optimizing LLM judge score without improving user outcomes.
- Stale golden set: Tests outdated product behavior; passing CI but failing users.
- Judge drift: Upgraded judge model changes scores without system change.
- Non-deterministic CI: temperature > 0 causes flaky gates.
- Label noise: Ambiguous rubrics produce contradictory human labels.
- Survivorship bias: Eval set lacks adversarial or edge-case prompts.
- Average trap: Mean score hides 0% pass rate on critical slice.
Debugging playbook
- Reproduce with frozen config (model ID, prompt hash, seed).
- Inspect retrieved context and tool traces for the failing item.
- Compare baseline vs candidate side-by-side on failure bucket.
- Cluster failures by embedding similarity to find patterns.
- Add 5-10 representative failures to golden set before fixing.
- Verify fix on failure bucket AND full regression suite.
# Failure clustering sketch
from sklearn.cluster import KMeans
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("all-MiniLM-L6-v2")
failures = load_failed_prompts("run-0042.json")
emb = model.encode(failures)
labels = KMeans(n_clusters=8, random_state=0).fit_predict(emb)
# Review largest cluster first: often one root cause
20. Metric Selection Guide
Choose metrics that match task verifiability, risk level, and budget. This table is the quick reference for designing an eval suite.
| Task type | Primary metric | Secondary metrics | Human eval? | CI gate suitable? |
|---|---|---|---|---|
| Short-form QA (factoid) | Exact match / F1 | LLM judge vs reference | Spot-check monthly | Yes |
| Open-ended chat / support | LLM-as-judge (helpfulness, correctness) | Escalation rate, thumbs-down | Yes, weekly sample | Smoke only (with $n$ samples) |
| Summarization | LLM judge + faithfulness to source | ROUGE-L (sanity), compression ratio | Yes for launch | Judge pass rate |
| Structured extraction (JSON) | Schema validity + field EM | Latency, cost per doc | Rare | Yes, strict |
| Code generation | pass@k (unit tests) | Static analysis, security scan | Spot-check hard bugs | Yes |
| SQL / API agents | Execution accuracy | Tool call validity, steps to success | On failure buckets | Yes in sandbox |
| RAG Q&A | Faithfulness + answer relevancy | Recall@k, citation precision | Yes for high-risk domains | Faithfulness threshold |
| Classification / routing | Accuracy, macro-F1 | Per-class recall, calibration | Low-disagreement audit | Yes |
| Translation / localization | COMET / BLEURT | Human fluency adequacy | Yes per locale | COMET gate per locale |
| Safety / policy | Attack success rate (ASR) | Over-refusal on benign set | Red-team review | Yes, zero-tolerance slices |
| Creative writing / marketing | Human preference (SBS) | Brand rubric checklist | Always | No (too subjective) |
| Multimodal (image QA) | Task-specific accuracy | Human review on failures | Yes | Subset smoke |
Decision flowchart (text)
- Can you write a programmatic verifier (unit test, SQL, regex, schema)? Use it as primary.
- Is there a reference answer? EM / F1 + semantic similarity.
- Is grounding in source docs required? Faithfulness first.
- Is output subjective? LLM judge calibrated to humans + periodic SBS.
- Is risk high (legal, medical, safety)? Add human eval and conservative gates.
- Always report latency, cost, and error rate alongside quality.