LUNAROPS · OPERATIONAL UPLINK 100% UPTIME 1,247d POSTS 893 JEFF.MOON@LUNAROPS.DEV UTC --:--:--

LLM Evals: Testing Your AI Application Like Real Software

llmevalstestingaibraintrustlangfusepromptfooinspectdeepevalci-cdmlops

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# promptfooconfig.yaml
providers:
  - id: openai:gpt-4o-mini
  - id: anthropic:claude-haiku-4-5

prompts:
  - "Summarise the following in one sentence: {{article}}"
  - "Provide a one-sentence summary of: {{article}}"

tests:
  - vars:
      article: "{{file('tests/articles/q1-earnings.txt')}}"
    assert:
      - type: llm-rubric
        value: "Summary is factually accurate and under 30 words"
      - type: not-contains
        value: "I cannot"
  - vars:
      article: "{{file('tests/articles/product-recall.txt')}}"
    assert:
      - type: llm-rubric
        value: "Summary mentions the product name and recall reason"
      - type: javascript
        value: "output.split(' ').length <= 35"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Run evals
npx promptfoo eval

# Compare two prompt variants
npx promptfoo eval --output results.json

# View results in browser
npx promptfoo view

# CI: fail on threshold
npx promptfoo eval --fail-threshold 0.9

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from deepeval import assert_test
from deepeval.test_case import LLMTestCase
from deepeval.metrics import (
    AnswerRelevancyMetric,
    FaithfulnessMetric,
    HallucinationMetric,
    ContextualRecallMetric,
)

def test_rag_response():
    test_case = LLMTestCase(
        input="What is the refund policy?",
        actual_output=rag_pipeline.run("What is the refund policy?"),
        expected_output="Refunds are available within 30 days of purchase.",
        retrieval_context=rag_pipeline.get_context("What is the refund policy?"),
    )

    assert_test(test_case, [
        AnswerRelevancyMetric(threshold=0.7),
        FaithfulnessMetric(threshold=0.8),
        HallucinationMetric(threshold=0.3),
    ])
1
deepeval test run test_rag.py

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import braintrust
from braintrust import Eval
from autoevals import Factuality, ClosedQA

Eval(
    "Customer Support Bot",
    data=lambda: [
        {"input": {"question": q}, "expected": e}
        for q, e in load_golden_dataset()
    ],
    task=lambda input: customer_support_bot(input["question"]),
    scores=[Factuality, ClosedQA],
)
1
BRAINTRUST_API_KEY=... braintrust eval eval_customer_support.py

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:

1
2
3
4
5
6
# .github/workflows/eval.yml
- name: Run evals
  run: braintrust eval evals/production.py
  env:
    BRAINTRUST_API_KEY: ${{ secrets.BRAINTRUST_API_KEY }}
  # Braintrust exits non-zero if any score drops below threshold

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from langfuse import Langfuse
from langfuse.decorators import observe, langfuse_context

langfuse = Langfuse()

@observe()
def customer_support_bot(question: str) -> str:
    response = llm_call(question)
    # Score is attached to the current trace
    langfuse_context.score_current_trace(
        name="relevancy",
        value=score_relevancy(question, response),
    )
    return response

For batch eval runs against a dataset stored in Langfuse:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
dataset = langfuse.get_dataset("golden-support-cases")

for item in dataset.items:
    with item.observe(run_name="gpt-4o-mini-v2") as trace_id:
        output = customer_support_bot(item.input["question"])
        item.link(trace_id, "gpt-4o-mini-v2")
        langfuse.score(
            trace_id=trace_id,
            name="factuality",
            value=evaluate_factuality(output, item.expected_output),
        )

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from inspect_ai import Task, task
from inspect_ai.dataset import json_dataset
from inspect_ai.scorer import model_graded_fact
from inspect_ai.solver import generate, chain_of_thought

@task
def knowledge_eval():
    return Task(
        dataset=json_dataset("data/knowledge_cases.json"),
        solver=[chain_of_thought(), generate()],
        scorer=model_graded_fact(),
    )
1
2
3
4
5
inspect eval knowledge_eval.py --model openai/gpt-4o
inspect eval knowledge_eval.py --model anthropic/claude-sonnet-4-6

# Compare results
inspect view

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
name: LLM Evals

on:
  pull_request:
    paths:
      - 'prompts/**'
      - 'src/llm/**'
      - 'evals/**'

jobs:
  evals:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install promptfoo
        run: npm install -g promptfoo

      - name: Run unit evals (deterministic)
        run: promptfoo eval --config evals/unit.yaml --fail-threshold 1.0

      - name: Run quality evals (LLM-as-judge)
        run: promptfoo eval --config evals/quality.yaml --fail-threshold 0.85
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

      - name: Post results to PR
        if: always()
        run: |
          promptfoo eval --output results.json
          # Post summary via gh CLI or custom script

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:

1
2
3
4
5
6
7
8
9
# promptfooconfig.yaml
thresholds:
  # Global: 85% pass rate
  pass: 0.85
  # Per-metric thresholds
  metrics:
    factuality: 0.90
    instruction-following: 0.88
    safety: 1.00  # Any safety failure blocks merge

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from inspect_ai import Task, task
from inspect_ai.dataset import json_dataset
from inspect_ai.scorer import includes, model_graded_fact
from inspect_ai.solver import use_tools, generate
from inspect_ai.tool import bash, web_search

@task
def agent_task_eval():
    return Task(
        dataset=json_dataset("data/agent_tasks.json"),
        solver=[
            use_tools([bash(), web_search()]),
            generate(),
        ],
        scorer=[
            includes(),          # Did output contain the expected result?
            model_graded_fact(), # Factual accuracy of final response
        ],
        sandbox="docker",       # Sandboxed bash execution
    )

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:

  1. Log everything in production: trace every LLM call with inputs, outputs, latency, and metadata.
  2. Tag failures: when users report bad responses, or your heuristic monitors (hallucination detectors, safety classifiers, length/format validators) flag something, tag the trace.
  3. 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.
  4. 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:

Comments