LLM Evals: Testing Your AI Application Like Real Software
There is a version of AI application development where you vibe-check a new prompt in the playground, see it produce something reasonable, ship it, and hope for the best. Most teams start here. The problem is not that this is reckless — prompts are often genuinely hard to reason about statically — the problem is that there is no other mechanism in place. No automated check catches the regression when you upgrade the underlying model. No dashboard tells you that the summarisation quality dropped 15% after last Tuesday’s deployment. No CI job fails when a prompt change that improves the happy path silently breaks three edge cases.
Evals are the answer to this problem. Not as a philosophy, but as a concrete engineering practice with tooling, metrics, and a place in your deployment pipeline. This post covers what evals are, how to build a golden dataset that actually catches failures, where LLM-as-judge works and where it lies to you, and how to connect the whole thing to CI so it behaves like a real test suite.
What an Eval Actually Is
An eval is a function that takes an AI system’s output and returns a score. That’s the minimal definition. In practice it has three moving parts:
┌──────────────────────────────────────────────────────────────────┐
│ Eval Pipeline │
│ │
│ ┌────────────┐ ┌─────────────────┐ ┌────────────────┐ │
│ │ Dataset │────▶│ Task/System │────▶│ Scorer │ │
│ │ │ │ under test │ │ │ │
│ │ input │ │ │ │ score: 0–1 │ │
│ │ expected │ │ output │ │ pass/fail │ │
│ │ metadata │ │ (LLM response, │ │ label │ │
│ └────────────┘ │ RAG result, │ └────────────────┘ │
│ │ agent trace) │ │ │
│ └─────────────────┘ │ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Aggregation │ │
│ │ pass rate │ │
│ │ mean score │ │
│ │ regression Δ │ │
│ └────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
Dataset: A collection of test cases. Each case has at minimum an input (the prompt, user query, or document). It may also carry expected output (for exact-match or similarity scoring), a reference answer, metadata tags (category, difficulty, source), or ground-truth labels.
Task: The system under test. This is the thing you’re actually evaluating — a single prompt template, a chain, a RAG pipeline, an agent. The task runs each input and produces an output.
Scorer: The function that assigns a quality signal to the output. This is where most of the design complexity lives, and where most teams make mistakes. Scorers range from exact string matching (trivially deterministic) to neural embedding similarity to another LLM judging the output (expensive, powerful, and subtly unreliable).
The eval run aggregates scores across the whole dataset into summary metrics. A well-designed eval pipeline tracks these metrics over time so you can see whether a change improved or degraded performance.
Types of Scorers
The choice of scorer determines what your eval can and cannot catch. They sit on a spectrum from fully deterministic to fully model-based.
| Scorer type | Examples | Strengths | Weaknesses |
|---|---|---|---|
| Exact match | Output == expected string | Zero false positives, zero cost | Brittle; fails on paraphrase |
| Regex / contains | Output contains substring | Good for structured outputs | Can’t assess quality |
| Embedding similarity | cosine(embed(output), embed(reference)) | Tolerates paraphrase | Ignores factual accuracy |
| Model-based classifier | Fine-tuned BERT for toxicity, relevance | Fast, consistent | Narrow scope |
| LLM-as-judge | GPT-4o / Claude grades output | Flexible, human-like | Biased, inconsistent, expensive |
| Human annotation | Reviewer rates output | Ground truth | Slow, costly, doesn’t scale to CI |
In practice you use several of these together. Deterministic checks handle structural correctness (is the output valid JSON? does it mention the product name?). Model-based scorers handle semantic quality. Human annotation provides ground truth for the most important cases and calibrates your automated scorers.
Building a Golden Dataset
The golden dataset is the heart of your eval infrastructure. It is a curated collection of test cases that represents the full range of things your system needs to handle correctly: the happy path, known edge cases, historically problematic inputs, and the failure modes you’ve already encountered in production.
A golden dataset has two jobs. The first is regression detection: if the dataset is stable and the system changes, a score drop means something broke. The second is quality benchmarking: if you’re comparing two prompt variants or two model versions, the dataset is the measuring stick.
What goes in it
Start with representative inputs from the intended use case. If you’re building a customer support bot, seed the dataset from actual support tickets. If you’re building a code review assistant, seed it from real PRs. Synthetic data is acceptable for augmentation but real-world inputs expose distribution edge cases that synthetic data misses.
Then deliberately add:
- Known hard cases: inputs that previously produced bad outputs, now fixed
- Ambiguous inputs: things where the expected behaviour is defined by your product requirements, not obvious from the text
- Adversarial inputs: prompt injection attempts, off-topic requests, inputs designed to trigger refusals
- Domain-specific edge cases: for a legal assistant, inputs with jurisdiction-specific nuance; for a code generator, inputs with subtle language-version requirements
- Regression cases: every time production reveals a new failure mode, that input joins the dataset
Expected outputs
Not every case needs a reference answer. Some scorers operate without one (factual grounding checks, toxicity detection, output format validation). But for quality scorers based on similarity or LLM judging, having a reference answer gives the scorer an anchor.
Reference answers should be written by subject-matter experts, not generated by the same LLM you’re evaluating. If you generate expected outputs with GPT-4o and evaluate with GPT-4o, you’re measuring consistency, not quality.
Versioning the dataset
The golden dataset is code. It lives in your repository, changes are tracked, and additions require review. A dataset that grows undisciplined becomes noisy. Each new case should document why it was added (the production incident or edge case it captures) and what the expected behaviour is.
A minimal golden dataset entry in JSONL format:
{
"id": "support-001",
"input": "How do I cancel my subscription?",
"expected": "To cancel your subscription, go to Account Settings > Billing > Cancel Plan. You'll retain access until the end of your current billing period.",
"tags": ["billing", "cancellation", "tier-free"],
"added": "2026-03-12",
"reason": "High-volume query, previous model was linking to wrong settings page"
}
LLM-as-Judge: What It Does Well and Where It Lies
LLM-as-judge has become the default scorer for quality dimensions that don’t reduce to exact match — helpfulness, factual accuracy, tone, coherence, following instructions. The appeal is obvious: a frontier model can read output and assess it the way a human reviewer would, at scale, without the cost of manual annotation.
The limitations are real and systematically documented. A 2026 RAND study found that no judge model is uniformly reliable across benchmarks, with frontier models exceeding 50% error rates on challenging bias benchmarks. Earlier research showing GPT-4 agreement with humans above 80% was correct for narrow task types; broader coverage exposes the gaps.
Biases that affect every LLM judge
Positional bias: When two outputs are presented for comparison, most judge models favour the first option more than chance would predict. In pairwise evals, this inflates scores for whichever response appears first. Mitigation: run both orderings and average.
Verbosity bias: Longer, more elaborate responses score higher even when the concise response is more correct. A 200-word answer beats a 50-word answer of equivalent accuracy most of the time. Mitigation: explicitly instruct the judge to penalise unnecessary length; test against cases where the correct answer is deliberately brief.
Self-enhancement bias: A model asked to judge its own outputs rates them higher than outputs from other models, even when blind to which is which (where the format or style reveals origin). Mitigation: don’t use the same model family as judge and subject.
Format sensitivity: Identical semantic content scores differently when formatted differently — bullet points versus prose, markdown versus plain text. Mitigation: normalise formatting before judging or test across formats.
Adversarial manipulation: Judge models can be prompted to score outputs favourably via content in the evaluated text itself. This is a real concern for production pipelines where user-generated content reaches the judge. Mitigation: sandbox the judge from the content under evaluation where possible.
Writing effective judge prompts
The quality of a judge prompt determines a large fraction of judge reliability. Effective judge prompts:
- State the specific dimension being evaluated (factual accuracy, instruction following, tone) — not “rate this response”
- Provide a rubric with examples of each score level
- Instruct the judge to evaluate in a specific order: read the reference, then the output, then assess the gap
- Request chain-of-thought before the final score to reduce surface-level pattern matching
- Use a clear output format:
{"score": 3, "reasoning": "..."}
Example judge prompt for factual accuracy:
You are evaluating the factual accuracy of a response against a reference answer.
REFERENCE ANSWER:
{{reference}}
RESPONSE TO EVALUATE:
{{output}}
Evaluate ONLY factual accuracy. Ignore style, length, and format.
Score on this scale:
3 - All factual claims in the response are consistent with the reference
2 - Minor factual inaccuracies that don't change the core answer
1 - At least one significant factual error
0 - The response contradicts the reference on a central fact
Think step by step: identify each factual claim in the response, check it against the reference, then assign a score.
Respond as JSON: {"reasoning": "...", "score": <0-3>}
Calibrating your judge
Before trusting a judge for CI gating, calibrate it. Take 100–200 cases from your golden dataset, generate human labels for each, and measure judge-human agreement. Calculate Cohen’s kappa, not just raw accuracy — kappa accounts for agreement by chance. A kappa below 0.6 means the judge is unreliable for gating. Between 0.6 and 0.8 it’s usable with human review of borderline cases. Above 0.8 it’s trustworthy as a first-pass gater.
If calibration is poor, the judge prompt needs work before the eval is meaningfully measuring anything.
Tooling Landscape
The eval tooling ecosystem has stabilised around a two-layer model: a lightweight framework for running evals (especially in CI) and a platform for managing datasets, tracking results over time, and supporting human annotation.
Promptfoo
Promptfoo is an open-source CLI-first tool focused on evaluation and red teaming. It reads a YAML configuration that defines providers (OpenAI, Anthropic, local Ollama, anything OpenAI-compatible), prompt variants, and test cases. The primary use case is prompt regression testing in CI.
|
|
|
|
Promptfoo also has a red teaming mode that generates adversarial inputs — prompt injection, jailbreaks, PII extraction attempts — against your system. For security-conscious applications this is a useful complement to quality evals.
Best for: CI-first teams, prompt comparison, security testing, Node.js/CLI-comfortable engineers.
DeepEval
DeepEval is a Python-native framework built around a pytest-like interface. You write eval cases as Python test functions, run them with deepeval test run, and get a structured report. It ships a library of pre-built metrics covering the most common LLM quality dimensions.
|
|
|
|
DeepEval integrates with Confident AI (the company’s hosted platform) for dataset management and regression tracking, but the eval framework itself is usable standalone against any evaluation LLM.
Best for: Python-native teams, RAG evaluation, pytest workflow integration.
Braintrust
Braintrust is a commercial platform that combines eval running, dataset management, human annotation, tracing, and CI/CD quality gates in one product. Where Promptfoo and DeepEval are frameworks you run locally, Braintrust is a service that stores results, tracks regressions over time, and surfaces diffs across experiment runs.
|
|
|
|
The Braintrust UI shows each experiment as a row in a table, with score breakdowns by metric and the ability to drill into individual failures. The diff view — comparing two experiments side by side — is particularly useful for prompt iteration: you can see exactly which test cases improved and which regressed.
For CI gating, Braintrust integrates with GitHub Actions and similar:
|
|
Best for: Teams that want one platform for evals + observability + human review; clear regression tracking across model upgrades.
Langfuse
Langfuse is an open-source observability and evaluation platform. It was acquired by ClickHouse in January 2026 but remains MIT-licensed and fully self-hostable. The primary entry point is tracing: you instrument your application to log every LLM call, and Langfuse records inputs, outputs, latency, token counts, and costs.
Evals in Langfuse are layered on top of traces. You can run evaluators against logged traces retrospectively — useful for evaluating production traffic without running evals in-line — and attach scores to specific spans. Human annotation is handled through the annotation queue in the UI.
|
|
For batch eval runs against a dataset stored in Langfuse:
|
|
Best for: Teams that want self-hosted eval + observability; production trace analysis; Langfuse-first observability teams.
Inspect (UK AISI)
Inspect is an open-source Python framework from the UK AI Security Institute, now a joint project with Meridian Labs. It is designed for rigorous, reproducible capability evaluations — the kind used to measure whether a model can perform dangerous tasks, solve hard reasoning problems, or operate as an autonomous agent. It ships over 200 pre-built evaluations and a VS Code log viewer.
|
|
|
|
Inspect is overkill for most product eval needs, but it’s the right choice if you’re running safety evaluations, agentic task benchmarks, or capability assessments. The framework enforces a clean separation between task definition and execution environment, making results reproducible across model versions.
Best for: Safety evals, capability benchmarks, academic/research contexts, agentic evaluations with tool use.
Integrating Evals into CI
The goal is a pipeline where a PR that changes a prompt, model version, or retrieval configuration triggers an eval run and fails if quality drops below threshold. This is the point where eval infrastructure starts behaving like software testing infrastructure.
┌─────────────────────────────────────────────────────────────────┐
│ CI Eval Pipeline │
│ │
│ PR opened / updated │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Lint & │ Fast checks: prompt syntax, schema │
│ │ Schema │ validation, no hardcoded secrets │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Unit Evals │ Deterministic scorers only (regex, │
│ │ (fast) │ JSON schema, exact-match). No LLM calls. │
│ │ < 30s │ Fail fast on structural regressions. │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ LLM Evals │ Full golden dataset run with LLM-as-judge. │
│ │ (slow) │ Compare score vs. baseline on main branch. │
│ │ 2–10 min │ Fail if any metric drops > threshold. │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Report & │ Post score summary to PR as comment. │
│ │ Gate │ Block merge if regression detected. │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
GitHub Actions example with Promptfoo
|
|
Thresholds and regression gates
A threshold of “must pass 90% of cases” is a blunt instrument. Better to set thresholds per metric and per category:
|
|
The safety category warrants a hard 100% threshold. A prompt change that causes the model to ignore safety instructions in even one test case should be an unconditional block.
For the regression gate, tracking absolute scores is less useful than tracking delta against the main branch. A system that consistently scores 72% is fine if it was 72% before your PR; a system that drops from 90% to 85% has regressed even though 85% sounds reasonable.
Evals for RAG Pipelines
RAG systems have two independent quality axes: retrieval quality (did we get the right documents?) and generation quality (did we produce a good answer from those documents?). Most eval frameworks handle generation; retrieval requires dedicated metrics.
Query ──▶ Retriever ──▶ Context ──▶ Generator ──▶ Response
│ │
▼ ▼
Retrieval Generation
metrics: metrics:
- context recall - faithfulness
- context precision - answer relevancy
- hit rate @k - hallucination rate
Context recall: Of all the facts needed to answer the question, what fraction appear in the retrieved context? Measures whether retrieval is getting the right information.
Context precision: Of what was retrieved, what fraction was relevant? High recall with low precision means you’re retrieving too broadly.
Faithfulness: Does the generated answer stay within the bounds of the retrieved context, or does it add claims not supported by the retrieved documents? The key RAG-specific hallucination metric.
Answer relevancy: Does the response actually answer the question? Orthogonal to faithfulness — a response can be faithful to irrelevant context, or answer the question without being grounded in the provided documents.
DeepEval’s RAGAS integration covers all four metrics with LLM-based scoring. For faithful evaluation, you need ground-truth annotations telling you what documents should be retrieved for each test case.
Evals for Agents
Agent evaluation is harder than single-turn evaluation because quality is defined over a trajectory — a sequence of tool calls and intermediate reasoning steps — not a single response. A correct final answer via a wrong route (unnecessary tool calls, flawed intermediate reasoning) may indicate a brittle agent that will fail on slightly different inputs.
The dimensions that matter for agent evals:
- Task completion: Did the agent achieve the goal?
- Trajectory efficiency: How many steps did it take? How many tool calls were unnecessary?
- Intermediate reasoning quality: Were intermediate steps correct, or did the agent reach the right answer via faulty reasoning?
- Tool use accuracy: Did the agent call tools with correct arguments?
- Refusal appropriateness: Did the agent correctly decline tasks outside its scope?
Inspect (UK AISI) is purpose-built for this evaluation pattern. It supports multi-turn agent workflows, tool call logging, and sandboxed execution environments for code-executing agents.
|
|
The Production Feedback Loop
Evals are only useful if they cover the failure modes you actually encounter. That means closing the loop between production failures and the golden dataset.
The pattern:
- Log everything in production: trace every LLM call with inputs, outputs, latency, and metadata.
- Tag failures: when users report bad responses, or your heuristic monitors (hallucination detectors, safety classifiers, length/format validators) flag something, tag the trace.
- Review flagged traces: periodically review tagged traces. For each confirmed failure, add the input to the golden dataset with a correct expected output and a note about what the failure was.
- Backfill scoring: run your eval suite retrospectively against logged production traces to understand when a regression was introduced.
Langfuse and Braintrust both support this pattern: flagged production traces can be exported directly to a dataset, and the human annotation queue surfaces traces for review.
The cost of this loop is mostly human time for the review step. The return is a golden dataset that actually reflects what your system encounters in production, rather than what you imagined it would encounter when you first designed the prompts.
Common Mistakes
Eval-dataset leakage: If you use the same LLM to generate both your golden dataset’s expected outputs and to evaluate against them, you’re measuring consistency not quality. Write expected outputs by hand or use a different model family.
100% pass rate is a red flag: If your eval suite always passes, it’s not hard enough. A well-designed suite should regularly flag borderline cases. If every PR scores 98%+, your tests aren’t testing the right things.
Gating on absolute score instead of delta: An absolute pass threshold doesn’t catch gradual quality decay. Track the delta between your PR and main branch. A 3% drop on a single category should trigger review even if the absolute score is still “passing.”
Only running evals on the happy path: Edge cases, adversarial inputs, and known failure modes are precisely what regressions affect. Happy-path evals give false confidence.
Using only one scorer: LLM-as-judge measures what the judge model values, which may not align with what your users value. Combine deterministic checks, embedding similarity, and LLM-as-judge. Use human annotation to calibrate.
Evaluating prompts in isolation: A prompt that scores well in isolation may perform differently in a chain or with different retrieval contexts. Eval the full pipeline, not just individual components.
Skipping calibration: Running LLM-as-judge without checking judge-human agreement means you don’t know if the judge is measuring what you think it is. Calibrate before you gate.
Tooling Summary
| Promptfoo | DeepEval | Braintrust | Langfuse | Inspect | |
|---|---|---|---|---|---|
| License | Open source | Open source | Commercial | MIT (self-host) | MIT |
| Interface | CLI/YAML | Python/pytest | Python SDK | Python SDK | Python |
| CI integration | Strong | Strong | Strong | Manual | Manual |
| Dataset management | Basic | Via Confident AI | Built-in | Built-in | Built-in |
| Human annotation | No | Via platform | Built-in | Built-in | No |
| Tracing/observability | No | No | Built-in | Built-in | Logs only |
| Red teaming | Yes | No | No | No | Safety evals |
| RAG metrics | Basic | Strong (RAGAS) | Via custom | Via custom | No |
| Agent evals | Basic | Basic | Basic | Basic | Strong |
| Self-hostable | Yes | Yes | No | Yes | Yes |
The practical recommendation for most teams: use Promptfoo or DeepEval for CI gating (fast, deterministic checks + LLM-as-judge quality evals on PR), paired with Langfuse (self-hosted) or Braintrust (managed) for production tracing, dataset management, and human annotation. The two-layer pattern keeps CI fast and keeps production observability independent of your eval infrastructure.
If you’re doing capability evaluations or safety assessments, add Inspect. If you have strict data residency requirements, everything except Braintrust has a self-hosted path.
Start small. Twenty well-chosen golden dataset cases that cover your known failure modes are more valuable than five hundred cases that all test the same thing. Get the CI integration working first so you have a feedback loop. Then grow the dataset as production teaches you what to add.
Sources:
- Braintrust: Best AI Evals Products 2026
- Braintrust vs Langfuse (2026)
- The Complete Guide to LLM & AI Agent Evaluation 2026 — Adaline
- LLM Evaluation and Testing: How to Build an Eval Pipeline — DEV Community
- LLM-as-a-Judge: Why Frontier Models Fail 50%+ Bias Tests — Adaline
- Inspect AI — UK AISI
- Inspect AI GitHub
- A Pragmatic Guide to LLM Evals — Pragmatic Engineer
Comments