LLM Evaluation: Complete Reference

From intrinsic benchmarks and golden sets through RAG faithfulness, agent trajectories, LLM-as-judge, CI gates, A/B testing, and production harness design for shipping LLM systems with confidence.

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

Evaluation spans the full stack

LayerWhat you measureWhen
Base modelKnowledge, reasoning, coding, multilingual abilityModel selection, pre-deployment
Prompt / system designInstruction following, format compliance, toneEvery prompt change
RAG pipelineRetrieval recall, faithfulness, citation accuracyIndex or chunking changes
Agent / toolsTask completion, tool selection, error recoveryTool schema or planner changes
Production systemLatency, cost, thumbs-down rate, escalation rateContinuous monitoring
Intuition: Think of eval like unit tests plus integration tests plus production monitoring for a traditional service. LLM eval adds judgment under ambiguity: the "correct" answer may be a set, not a string.

Core principles

  1. Task-specific signal beats generic benchmarks for product decisions.
  2. Measure what users experience, not only what is easy to automate.
  3. Version everything: model ID, prompt hash, retrieval index, tool definitions.
  4. Stratify results by category, locale, and user segment.
  5. Pair automatic metrics with periodic human review.

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:

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:

Extrinsic eval is slower, noisier, and often requires proprietary data. It is the metric that ultimately matters for business outcomes.

DimensionIntrinsicExtrinsic
DataPublic or synthetic benchmarksReal user queries, logs (redacted), task outcomes
CostLow to moderate (API spend)High (labeling, integrations, wait for outcomes)
GeneralizabilityCross-model comparisonSpecific to your product
Leakage riskHigh (benchmark contamination)Lower if you own the eval set
ActionabilityCoarse go/no-go on capabilityDirect ship/rollback decisions
Intuition: Intrinsic eval is like SAT scores. Extrinsic eval is like job performance after hire. You need both: SAT filters candidates; job performance tells you if you hired the right person for your role.

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

Phase 2: Build eval assets

Phase 3: Pre-deployment validation

Phase 4: Controlled rollout

Phase 5: Continuous improvement

# 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
Ownership model: Assign an eval owner (often a tech lead or ML engineer). They approve golden set changes, define CI thresholds, and publish weekly scorecards. Without ownership, eval sets rot and CI gates get disabled.

4. Benchmark Catalog

Public benchmarks provide coarse capability maps. Use them for model shopping, not as sole ship criteria.

Knowledge and reasoning

BenchmarkMeasuresNotes
MMLU57-subject multiple-choice knowledgeIndustry standard; contamination concerns
MMLU-ProHarder multi-step MCQReduces guessing; fewer items
GPQAGraduate-level science QAVery hard; small set
BBH23 challenging reasoning tasksChain-of-thought sensitive
ARC-ChallengeScience exam questionsClassic; largely saturated by top models
HellaSwagCommonsense continuationMostly saturated
TruthfulQAResistance to popular misconceptionsMeasures factuality under traps

Mathematics

BenchmarkMeasuresNotes
GSM8KGrade-school word problemsVerifiable answers; good for reasoning eval
MATHCompetition mathematicsHard; needs step-by-step grading
MathVista / MMMUMultimodal mathRequires vision capability

Code

BenchmarkMeasuresNotes
HumanEvalPython function synthesispass@k with unit tests
MBPPBasic Python programmingBroader than HumanEval
SWE-benchReal GitHub issue resolutionAgentic; expensive to run
LiveCodeBenchFresh competitive programmingReduces training leakage via recency

Instruction following and chat

BenchmarkMeasuresNotes
MT-BenchMulti-turn chat qualityLLM-as-judge; GPT-4 judge common
AlpacaEval 2Win rate vs referenceLength-controlled variants reduce verbosity bias
IFEvalVerifiable instruction constraintsCount words, JSON format, etc.
Arena-HardHard user-style promptsCloser to real chat distribution

RAG and long-context

BenchmarkMeasuresNotes
RAGAS (framework)Faithfulness, relevancy, recallRun on your corpus, not a single leaderboard
HotpotQAMulti-hop QARetrieval + reasoning
Needle-in-a-haystackLong-context recallSynthetic; tests context window use
LongBenchLong document tasksSummarization, QA, few-shot

Safety and robustness

BenchmarkMeasuresNotes
ToxiGenToxic generationDemographic sensitivity
HarmBenchRed-team refusalStandardized attack prompts
AdvBenchJailbreak success rateAdversarial suffix attacks
WinoBias / BBQSocial bias in completionsSlice by demographic axis
How to use this catalog: Pick 2-3 benchmarks aligned with your product (e.g., IFEval + custom JSON schema + RAG faithfulness). Report them alongside your golden set. Never ship based on MMLU alone.

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

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 typeTypical sizePurpose
Smoke20-50 itemsFast PR gate (<5 min)
Core regression200-1,000 itemsNightly or pre-release
Full golden1,000-10,000+ itemsMajor model migrations
Slice-critical50-200 per sliceLegal, medical, non-English

Quality practices

  1. Balance categories to match production mix (or overweight high-risk slices).
  2. Version and changelog every addition or label edit.
  3. Hold out a secret set that engineers cannot overfit prompts against.
  4. Review disagreements between raters; fix ambiguous rubrics.
  5. Expire stale items when product behavior intentionally changes.
Intuition: Your golden set is the unit test suite for AI behavior. Treat it like production code: code review, CI, ownership, and deprecation policy.

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

Detection methods

MethodHow it works
N-gram overlapCompare benchmark text to training corpus shards
Min-hash / dedupNear-duplicate detection at scale
Canary stringsInsert unique tokens; check if model reproduces them
Held-out fresh benchmarksLiveCodeBench, private unreleased sets
Perturbation testsRephrase questions; large score drops suggest memorization

Mitigation playbook

  1. Filter training data with decontamination pipelines (e.g., n-gram blocklists from test sets).
  2. Keep a private holdout never shown to anyone tuning prompts.
  3. Separate dev (for iteration) and test (for reporting) splits.
  4. Prefer dynamic benchmarks that rotate or use fresh content.
  5. Report contamination audits alongside leaderboard numbers.
Why it matters commercially: A model that scores 90% on GSM8K due to memorization may still fail your billing reconciliation agent. Contamination turns benchmarks from measurement into marketing.
# 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

MetricBest forLimitation
Exact match (EM)Short-form QA, IDs, datesIgnores paraphrases
F1 token overlapExtractive QAPenalizes valid rephrasing
AccuracyMultiple choice, classificationHides per-class failure
pass@kCode generationNeeds 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

These capture paraphrase better than n-gram metrics but can reward vague, generic answers that embed similarly to references.

Structured output metrics

RAG-specific automatic metrics

Operational metrics (always report alongside quality)

Rule of thumb: If you can verify the answer programmatically (unit test, SQL result, regex, schema), do that first. Semantic metrics and LLM judges are for the remainder.

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

PatternDescriptionUse case
Pointwise scoringRate one output 1-10 on criteriaRegression tracking
Pairwise preferencePick A or B (or tie)Model comparison, RLHF data
Binary checklistPass/fail per rubric itemCI gates
Chain-of-thought judgeReason then scoreComplex reasoning tasks
Reference-guidedCompare to gold answerQA with references

Known biases and mitigations

BiasSymptomMitigation
Position biasFavors first option in pairwiseSwap order, aggregate both
Verbosity biasRewards longer answersLength-controlled prompts, AlpacaEval LC
Self-preferenceJudge favors own style/model familyUse different judge model family
AnchoringOverweights reference answer quirksBlind references, multiple refs
SycophancyAgrees with flawed reasoningRequire 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

  1. Sample 200+ items with dual human ratings.
  2. Run judge on same items; compute correlation and confusion matrix.
  3. Adjust rubric wording where judge disagrees systematically.
  4. Track judge-model version; re-calibrate when judge upgrades.
Cost tip: Use a strong judge offline (nightly suites) and a cheaper judge for PR smoke tests. Never let the smoke judge differ in rubric semantics from the canonical judge.

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

MetricFormula / definitionRequires
Recall@kFraction of queries where a relevant doc is in top-kRelevance labels
Precision@kFraction of top-k docs that are relevantRelevance labels
MRRMean reciprocal rank of first relevant docRelevance labels
nDCG@kDiscounted cumulative gain with position penaltyGraded relevance
Hit rateAny relevant doc retrieved (binary)Relevance labels
$$ \text{MRR} = \frac{1}{|Q|} \sum_{q \in Q} \frac{1}{\text{rank}_q} $$

where $\text{rank}_q$ is the position of the first relevant document for query $q$, or 0 if none found.

Generation metrics

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

  1. Build labeled (query, relevant_chunk_ids) set from click logs or human annotators.
  2. Run retrieval-only eval when changing embeddings, chunk size, or hybrid weights.
  3. Run generation eval when changing model, prompt, or reranker.
  4. Run full pipeline eval before release; stratify by document type and query length.
Debugging order: If answers are wrong, check retrieval first. ~40% of RAG failures are retrieval misses, not generation errors. Fixing generation prompt before retrieval is wasted effort.

10. Agent Evaluation

Agents loop: plan, call tools, observe results, repeat. Evaluation must cover trajectory quality, not only final text.

What to measure

DimensionMetric examples
Task successGoal achieved (binary), partial credit rubric
Tool selectionCorrect tool rate, unnecessary tool calls
Argument correctnessValid JSON args, schema compliance
Step efficiencySteps to success, token cost per success
RecoveryRecovers from API error within N steps
SafetyNo destructive actions without confirmation

Eval environments

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

BenchmarkDomainSuccess criterion
SWE-benchSoftware engineeringUnit tests pass after patch
WebArenaWeb navigationGoal state in simulated sites
GAIAGeneral assistant tasksExact answer match
τ-benchTool-use with user simMulti-turn task completion
AgentBenchMulti-environmentEnvironment-specific success
Trajectory logging is mandatory: Store every tool call, observation, and planner decision. You cannot debug "the agent failed" without the trace. Final answer-only eval hides compounding errors.

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

DimensionExamplesMeasurement
Harmful content generationViolence, self-harm instructionsRefusal rate on red-team set
Jailbreak resistanceRoleplay bypass, base64 attacksAttack success rate (ASR)
PII leakageTraining data extractionCanary extraction tests
Bias / fairnessStereotyping by demographicSlice disparity metrics
Over-refusalRefusing benign medical/legal infoFalse refusal rate on benign set
Prompt injectionHidden instructions in RAG docsInstruction override success rate

Red-team prompt categories

# 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:

  1. Input classifier (block or route before LLM)
  2. System prompt and policy layer
  3. Output filter (regex, moderation model)
  4. Human review queue for borderline cases
Balance: Optimize for low attack success rate AND low false refusal rate. A model that refuses everything scores well on harm but fails the product. Report both metrics on every release.

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

Rating designs

DesignDescriptionPros / cons
Likert scale1-5 on helpfulness, clarity, toneSimple; rater scale drift
Side-by-side (SBS)Pick A, B, or tieHigh signal for comparisons; needs both outputs
Binary rubricPass/fail per criterionGood for CI calibration
Holistic rankingRank N outputsEfficient per dollar; harder analysis
Edit distanceHow much rater edits outputImplicit quality signal

Inter-rater agreement

Track agreement to ensure rubric clarity:

Target $\kappa > 0.6$ for core dimensions. Low agreement means fix the rubric, not blame raters.

Operational best practices

  1. Write concrete rubrics with pass/fail examples (anchor items).
  2. Use 2+ raters per item for high-stakes labels.
  3. Blind model identity to prevent brand bias.
  4. Rotate raters to detect drift and fatigue.
  5. Close the loop: thumbs-down in prod feeds labeling queues.
  6. Pay fairly and train: qualification batches before production labeling.
Cost management: Human-eval the smallest stratified sample that achieves confidence bounds. Use automatic pre-filter to send only disagreements or low-confidence items to humans (active learning).

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

StrengthWeakness
Reproducible, version-controlledDataset staleness
Fast iteration in CIDistribution shift from live traffic
Cheap relative to live experimentsCannot capture long-term user behavior
Safe for destructive agent testsMay overfit to golden set

Online evaluation

SignalHow to collect
Explicit feedbackThumbs up/down, star ratings, NPS
Implicit feedbackCopy, dwell time, regenerate, abandon
Task outcomeTicket resolved, purchase completed, code merged
Human escalationHandoff rate to support agent
Latency / costReal infrastructure measurements

Bridging offline and online

  1. Correlate offline golden scores with online thumbs-down rate weekly.
  2. When correlation drops, refresh golden set from recent failures.
  3. Use shadow mode: run candidate model on live inputs, show baseline to user, log both outputs for offline comparison.
  4. 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
Offline catches regressions before users see them. Online catches what offline missed. Neither alone is sufficient for production LLM systems.

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

TierTriggerSuiteLatency budget
PR smokeEvery pull request20-50 golden items3-8 minutes
NightlyCron schedule500+ items, all slices1-3 hours
Pre-releaseRelease branchFull golden + safety red-team4-12 hours
Post-deployCanary startOnline guardrail metricsReal time

Gate criteria examples

# 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:

Anti-pattern: Disabling CI gates after one false alarm. Fix flake with better statistics, smaller smoke sets, or cached inference. Teams that disable gates ship regressions within weeks.

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

  1. Hypothesis: "Candidate reduces escalation rate without increasing cost."
  2. Primary metric: One pre-registered success metric (escalation rate).
  3. Guardrail metrics: Safety ASR, latency p95, cost, thumbs-down.
  4. Unit of randomization: User ID or session ID (consistent experience).
  5. Power analysis: Estimate sample size for detectable effect.
  6. 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

IssueSolution
Peeking (early stop)Fixed horizon or sequential testing (SPRT)
Multiple comparisonsBonferroni or FDR correction on secondary metrics
Network effectsCluster randomization by team/org
Non-stationary trafficCUPED variance reduction, covariate adjustment
Small effect sizesLonger run or proxy metrics with higher sensitivity

LLM-specific A/B pitfalls

Ship criterion: Primary metric improves with $p < 0.05$, all guardrails hold, and the win is large enough to justify operational complexity. A statistically significant 0.1% lift may not be worth a model swap.

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

ComponentResponsibility
Dataset loaderVersioned golden sets, filters by slice/tag
RunnerInvokes model + RAG + tools with frozen config
Scorer registryPluggable metrics (exact match, judge, RAGAS)
Result storeJSON/Parquet per run with git SHA and config hash
ComparatorDiff vs baseline, highlight regressions
ReporterDashboards, 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

Harness vs platform: Start with a Python harness in-repo. Graduate to Langfuse, Braintrust, or W&B when you need UI, dataset collaboration, and cross-team visibility.

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

ToolStrengthsTypical use
LangfuseTracing, datasets, evals, prompt mgmtEnd-to-end LLM ops
BraintrustEval loops, CI, human reviewEval-driven dev workflows
Weights & Biases (W&B)Experiment tracking, tablesResearch + production evals
LangSmithLangChain-native tracing and datasetsLangChain/LangGraph apps
Phoenix (Arize)Embeddings drift, trace analysisRAG debugging
PromptfooYAML-driven red-team and regressionCLI CI gates, safety tests
RAGASRAG metric libraryFaithfulness, context metrics
DeepEvalPytest-style LLM unit testsDeveloper-local testing
OpenAI EvalsTemplate eval frameworkCustom benchmark prototyping

Metric and judge libraries

LibraryMetrics
lm-eval-harness100+ academic benchmarks (MMLU, GSM8K, ...)
inspect-aiComposable eval tasks, agent sandboxes
bert-scoreSemantic similarity vs reference
sacrebleuStandardized BLEU
ragasRAG 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

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

  1. Capture failure: User report or prod trace becomes a new golden item.
  2. Write rubric: Define pass criteria before changing the system.
  3. Reproduce: Confirm baseline fails the new item.
  4. Fix: Adjust prompt, retrieval, or model.
  5. Verify: New item passes; regression suite still passes.
  6. Commit: Golden item + fix in same PR.

Culture and process

PracticeOutcome
Every bug becomes an evalFailure never recurs silently
Eval review in PRsLabel quality stays high
Weekly scorecardsVisibility across model/prompt changes
Eval budget in sprintsTime allocated for dataset curation
PM owns success criteriaMetrics align with product value

Relationship to fine-tuning

Eval sets drive training data too:

North star: A new engineer should break production behavior only by failing an eval, not by lacking tribal knowledge about which prompts are sacred.

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

SymptomLikely layerFirst checks
Wrong facts on static knowledgeBase modelIntrinsic benchmarks, knowledge cutoff
Ignores instructionsPrompt / system messageIFEval, schema compliance rate
Answers without doc supportRAG generationFaithfulness metric, empty-context test
Missing relevant infoRetrievalRecall@k, chunk overlap analysis
Wrong API calledAgent plannerTool selection accuracy
Sudden cost spikeInference configTokens per request, loop detection
Jailbreaks after deploySafety stackRed-team ASR, guardrail logs
Regression on one localeData sliceStratified eval by language

Common eval system failures

Debugging playbook

  1. Reproduce with frozen config (model ID, prompt hash, seed).
  2. Inspect retrieved context and tool traces for the failing item.
  3. Compare baseline vs candidate side-by-side on failure bucket.
  4. Cluster failures by embedding similarity to find patterns.
  5. Add 5-10 representative failures to golden set before fixing.
  6. 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
Postmortem template: (1) user impact, (2) root cause layer, (3) eval gap that allowed ship, (4) new golden items added, (5) CI threshold adjustment if needed.

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)

  1. Can you write a programmatic verifier (unit test, SQL, regex, schema)? Use it as primary.
  2. Is there a reference answer? EM / F1 + semantic similarity.
  3. Is grounding in source docs required? Faithfulness first.
  4. Is output subjective? LLM judge calibrated to humans + periodic SBS.
  5. Is risk high (legal, medical, safety)? Add human eval and conservative gates.
  6. Always report latency, cost, and error rate alongside quality.
Master eval by layers: (1) pick metrics per task type, (2) build golden sets from prod, (3) automate in a harness, (4) gate CI on regressions, (5) validate online with A/B tests, (6) feed failures back into the dataset. Every production LLM system is an eval loop, not a one-time benchmark score.