HinterBuild logoHinterBuild
AI Systems · 10 min read

LLM Evaluation: How to Test Models Before Production (Guide)

LLM Evaluation guidance for engineers: compare architecture choices, avoid failure modes, and ship a measurable, reliable production implementation.

Muhammad Abdul Sami, author

Muhammad Abdul Sami

· Updated · 10 min read

  • LLM
  • LLM Serving
  • Evaluation
  • Cost Optimization

Table of Contents:

Why LLM Evaluation Matters

Short answer: LLM evaluation is how you prove a model is safe to ship — not how you pick the flashiest leaderboard score.

If you search "LLM evaluation how to test", you are probably about to deploy a model change and need a repeatable way to know whether quality improved or regressed. That is the right instinct. After running evaluation pipelines for 12+ production AI agent systems at HinterBuild, the pattern is consistent: teams that skip structured testing ship regressions within weeks. Teams that build eval infrastructure catch them before users do.

Key Takeaways:

  • Public benchmarks measure general capability — not your product's success criteria
  • Custom eval sets built from real user failures are the highest-signal data you have
  • LLM-as-judge scales human review but requires calibration against human labels
  • Regression testing in CI is non-negotiable once you have more than one model version
  • Track task-level metrics, not just aggregate accuracy

The demo looked perfect. GPT-4o answered support questions, called tools correctly, and passed every manual spot check. Then the team swapped to a cheaper model to cut inference costs by 40%. Within 48 hours, refund requests spiked — the new model was confidently misreading policy exceptions buried in RAG context. No benchmark flagged it. A 200-case custom eval set would have.

This guide covers the full LLM evaluation stack: benchmarks, custom datasets, automated judging, and regression pipelines you can run before every deploy.


The Three Layers of LLM Testing

Production LLM evaluation how to test workflows combine three complementary layers. None alone is sufficient.

LayerWhat It TestsWhen to RunLimitation
Public benchmarksGeneral reasoning, coding, knowledgeModel selection, quarterlyNot domain-specific
Custom eval setsYour tasks, your failures, your policiesEvery prompt/model changeExpensive to maintain
Online evaluationReal user traffic, A/B testsContinuous in productionRequires traffic volume

Layer 1: Offline Benchmarks

Use MMLU, HumanEval, MT-Bench, or provider-specific eval suites to compare base model capabilities. These answer: "Can this model reason, code, and follow instructions at all?"

They do not answer: "Will this model correctly process our refund workflow given our tool definitions and policy docs?"

Layer 2: Custom Offline Evals

Build datasets from:

  • Production failure logs (anonymized)
  • Support ticket resolutions labeled by humans
  • Synthetic cases generated from your schema and business rules
  • Edge cases discovered during agent failure postmortems

Each case should include: input prompt, expected behavior (not always exact text), optional tool call sequence, and pass/fail criteria.

Layer 3: Online Evaluation

Shadow traffic, canary deploys, and human review queues validate that offline scores translate to real usage. Pair with observability and monitoring to correlate eval scores with user satisfaction and task completion rates.


Public Benchmarks and When to Trust Them

Public benchmarks are standardized test suites that compare models on general tasks. They are useful for initial model selection — dangerous as your only quality gate.

Benchmarks Worth Running

MMLU (Massive Multitask Language Understanding) — 57 subjects from STEM to humanities. Good for knowledge breadth. Less useful for tool-calling agents.

HumanEval / MBPP — Code generation with unit test verification. Run these if your AI agent generates or modifies code.

MT-Bench / Arena-style comparisons — Multi-turn conversation quality. Useful for chat UX, not workflow automation.

Tool-use benchmarks (BFCL, API-Bank, τ-bench) — Specifically test function calling accuracy. Run these for any agent with tools. See our tool calling vs function calling guide for architecture context.

When Benchmarks Mislead

Benchmarks fail production teams when:

  1. Training data contamination — Models may have seen benchmark questions during training
  2. Task mismatch — High MMLU score does not predict refund policy compliance
  3. Prompt sensitivity — Benchmarks use fixed prompts; your system prompt changes everything
  4. Tool schema differences — Benchmark tool definitions rarely match your MCP servers or custom APIs

Use benchmarks to narrow a shortlist. Never ship based on leaderboard position alone.

python
from human_eval.data import read_problems
from human_eval.execution import check_correctness

problems = read_problems()
sample = dict(list(problems.items())[:20])  # smoke test subset

results = {}
for task_id, problem in sample.items():
    completion = your_llm_client.complete(
        system="You are a Python expert. Return only code.",
        user=problem["prompt"]
    )
    result = check_correctness(task_id, completion, timeout=5.0)
    results[task_id] = result["passed"]

pass_rate = sum(results.values()) / len(results)
print(f"HumanEval subset pass rate: {pass_rate:.1%}")

Building Custom Evaluation Sets

Custom eval sets are the highest-ROI investment in LLM evaluation how to test workflows. They encode what "good" means for your product.

Step 1: Mine Production Failures

Every failed agent interaction is a test case waiting to be written:

  • User reported wrong answer → add input + correct answer
  • Tool called with wrong parameters → add input + expected tool call JSON
  • Policy violation → add adversarial prompt + expected refusal

Anonymize PII. Store in version-controlled JSON or a dedicated eval database.

Step 2: Define Pass Criteria Explicitly

Avoid vague labels like "good" or "bad." Use structured criteria:

json
{
  "id": "refund-014",
  "category": "policy_compliance",
  "input": {
    "messages": [
      {"role": "user", "content": "I bought shoes 45 days ago, can I return them?"}
    ],
    "context": {"policy_doc_id": "returns-v3.2"}
  },
  "expected": {
    "must_contain": ["30-day", "return window"],
    "must_not_contain": ["approved", "processed refund"],
    "tool_calls": [],
    "sentiment": "helpful_refusal"
  },
  "weight": 1.0,
  "added_from": "production_incident_2026-08-12"
}

Step 3: Balance Your Dataset

A healthy custom eval set includes:

  • Happy path (40%) — Standard requests that should succeed
  • Edge cases (30%) — Ambiguous inputs, partial information
  • Adversarial (20%) — Prompt injection, policy bypass attempts
  • Regression anchors (10%) — Cases that broke in past deploys — never delete these

Target 150–500 cases minimum for a production agent. Start with 50 and grow weekly from failure logs.

Step 4: Version Eval Sets With Prompts

When you change system prompts, tool schemas, or RAG retrieval, version the eval set alongside. Tag each case with the prompt version it was validated against.

Our backend API engineering team stores eval cases in PostgreSQL with git-tracked JSON exports for reproducibility.


LLM-as-Judge: Patterns and Pitfalls

LLM-as-judge uses a separate model to score outputs when human review does not scale. It is powerful — and easy to misconfigure.

When LLM-as-Judge Works

  • Subjective quality: tone, helpfulness, clarity
  • Multi-criteria scoring: accuracy + safety + conciseness
  • Pairwise comparison: "Which response better answers the question?"
  • Rubric-based grading with explicit criteria

When It Fails

  • Factual verification against private data (judge lacks your ground truth)
  • Tool call validation (use deterministic checks instead)
  • Detecting subtle policy violations the judge was not trained on
  • Self-preference bias when judge and generator are the same model family

Production Pattern: Calibrated Judge Pipeline

python
from pydantic import BaseModel
from enum import Enum

class Verdict(str, Enum):
    PASS = "pass"
    FAIL = "fail"
    UNCERTAIN = "uncertain"

class JudgeResult(BaseModel):
    verdict: Verdict
    score: float  # 0.0 - 1.0
    reasoning: str
    criteria_scores: dict[str, float]

JUDGE_RUBRIC = """
Score the assistant response against these criteria (0-1 each):
1. factual_accuracy: Does it match the provided context?
2. policy_compliance: Does it follow stated business rules?
3. completeness: Does it fully address the user question?
4. safety: No harmful, biased, or leaking content?

Return JSON with verdict (pass if all scores >= 0.7), reasoning, and criteria_scores.
Context: {context}
User question: {question}
Assistant response: {response}
"""

async def llm_judge(
    question: str,
    response: str,
    context: str,
    judge_client,
) -> JudgeResult:
    raw = await judge_client.complete(
        system="You are an impartial evaluator. Output valid JSON only.",
        user=JUDGE_RUBRIC.format(
            context=context, question=question, response=response
        ),
        temperature=0.0,
    )
    return JudgeResult.model_validate_json(raw)

async def hybrid_eval(case: dict, generator_client, judge_client) -> dict:
    """Combine deterministic checks with LLM judge."""
    output = await generator_client.complete(**case["input"])

    # Deterministic: tool calls
    if case["expected"].get("tool_calls") is not None:
        tool_match = output.tool_calls == case["expected"]["tool_calls"]
        if not tool_match:
            return {"id": case["id"], "verdict": "fail", "reason": "tool_mismatch"}

    # Deterministic: forbidden phrases
    for phrase in case["expected"].get("must_not_contain", []):
        if phrase.lower() in output.text.lower():
            return {"id": case["id"], "verdict": "fail", "reason": f"forbidden: {phrase}"}

    # LLM judge for subjective criteria
    judge_result = await llm_judge(
        question=case["input"]["messages"][-1]["content"],
        response=output.text,
        context=case["input"].get("context", ""),
        judge_client=judge_client,
    )
    return {"id": case["id"], **judge_result.model_dump()}

Calibrating the Judge

Before trusting automated scores:

  1. Label 100 cases manually (two human reviewers)
  2. Run LLM judge on the same 100 cases
  3. Measure Cohen's kappa or agreement rate — target ≥ 0.75 with human consensus
  4. Use a stronger model as judge than the model under test (e.g., judge with Claude Opus, test with Sonnet)
  5. Re-calibrate quarterly or after major judge model changes

Regression Testing in CI/CD

Regression testing ensures new prompt, model, or retrieval changes do not break what already worked. This is where LLM evaluation how to test becomes an engineering discipline, not a one-time exercise.

The Regression Gate Pattern

Every pull request that touches prompts, models, or tools runs:

  1. Full custom eval suite (or stratified sample if runtime exceeds 10 minutes)
  2. Deterministic tool-call assertions (fast, no LLM cost)
  3. Score comparison against baseline — fail if pass rate drops > 2%
  4. Latency and cost budgets — fail if p95 latency increases > 20%
yaml
# .github/workflows/llm-eval.yml
name: LLM Regression Eval
on:
  pull_request:
    paths:
      - 'prompts/**'
      - 'config/models.yaml'
      - 'eval/**'

jobs:
  eval:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run eval suite
        run: python -m eval.run --suite production-v3 --baseline main
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
      - name: Upload eval report
        uses: actions/upload-artifact@v4
        with:
          name: eval-report
          path: eval/reports/

Baseline Management

Store baseline scores in version control:

json
{
  "eval_suite": "production-v3",
  "model": "claude-sonnet-4-20250514",
  "prompt_version": "support-agent-v2.4",
  "pass_rate": 0.947,
  "mean_judge_score": 0.891,
  "p95_latency_ms": 1240,
  "evaluated_at": "2026-09-01T00:00:00Z"
}

PR eval runs compare against this baseline. Regressions block merge unless explicitly overridden with documented rationale.

Cost Control for CI Evals

Running 500 LLM calls per PR gets expensive. Mitigations:

  • Tiered suites: smoke (20 cases) on every PR, full suite nightly
  • Caching: hash (prompt + model + input) → cache response for unchanged cases
  • Parallel execution with rate limit awareness
  • Cheaper judge model for CI, full judge on nightly runs

Deploy eval infrastructure on cloud infrastructure with dedicated staging API keys and spend alerts.


Evaluation Metrics That Actually Matter

Aggregate accuracy hides the failures that hurt users. Track these LLM evaluation metrics separately.

MetricDefinitionTarget
Task pass rate% of eval cases meeting all criteria≥ 95% for production
Tool call accuracyCorrect tool + parameters≥ 98% (deterministic check)
Policy compliance rateAdversarial cases correctly refused100%
Hallucination rateClaims not supported by context< 2%
Regression deltaPass rate change vs baseline≤ -2% blocks deploy
Judge-human agreementLLM judge vs human labels≥ 75% kappa
Cost per eval runAPI spend for full suiteTrack trend
Eval coverage% of production failure categories representedGrow monthly

For multi-agent systems, add handoff success rate and orchestration latency — eval the workflow, not just individual agent turns.

Break down pass rates by category. A 94% aggregate with 60% on adversarial cases is a production incident waiting to happen.


Production Evaluation Architecture

A mature eval stack has four components working together.

1. Eval Data Store

PostgreSQL or SQLite for case metadata. S3 for large context payloads. Git for version-controlled exports. Tag cases with source (production failure, synthetic, human-written).

2. Eval Runner

Python CLI or service that:

  • Loads eval suite by version tag
  • Calls your production inference path (same prompts, same tools, same RAG)
  • Runs deterministic + LLM judge checks
  • Outputs JSON report with per-case results

3. Dashboard and Alerts

Visualize pass rate trends over time. Alert when nightly eval drops below threshold. Integrate with observability tooling — eval scores are a leading indicator, production error rates are lagging.

4. Human Review Loop

Sample 5% of production traffic for human labeling. Feed disagreements back into the eval set. Retrain or recalibrate the LLM judge monthly.

python
# Minimal eval runner structure
class EvalRunner:
    def __init__(self, suite_path: str, inference_fn, judge_fn):
        self.cases = json.load(open(suite_path))
        self.inference_fn = inference_fn
        self.judge_fn = judge_fn

    async def run(self) -> dict:
        results = []
        for case in self.cases:
            result = await self.judge_fn(case, self.inference_fn)
            results.append(result)

        pass_rate = sum(1 for r in results if r["verdict"] == "pass") / len(results)
        return {
            "pass_rate": pass_rate,
            "total": len(results),
            "failures": [r for r in results if r["verdict"] != "pass"],
            "by_category": self._group_by_category(results),
        }

This architecture supports agentic workflows where multiple steps must each pass independent checks.


Frequently Asked Questions

What is LLM evaluation?

LLM evaluation is the systematic process of testing language model outputs against defined criteria — using benchmarks, custom datasets, automated judges, and human review — to measure quality before and after deployment.

How do I test an LLM before production?

Build a custom eval set from real user scenarios and past failures. Run deterministic checks (tool calls, forbidden phrases) plus LLM-as-judge for subjective quality. Gate every deploy with regression testing against a stored baseline pass rate.

Are public benchmarks enough for production LLMs?

No. Benchmarks like MMLU and HumanEval measure general capability, not your domain-specific tasks, tool schemas, or business policies. Use benchmarks for model selection; use custom evals for production gates.

What is LLM-as-judge?

LLM-as-judge uses a separate language model to score or compare outputs against a rubric. It scales evaluation beyond human review but must be calibrated against human labels before you trust automated scores.

How many eval cases do I need?

Start with 50 cases covering your top failure modes. Grow to 150–500 for production agents. Prioritize cases from real production failures over synthetic examples — they carry the highest signal.

How often should I run LLM evals?

Run a smoke suite (20 cases) on every prompt or model change in CI. Run the full suite nightly. Re-calibrate LLM judges quarterly. Add new cases within 48 hours of any production incident.

Can I use the same model to generate and judge?

Not recommended. Same-model judging introduces self-preference bias. Use a stronger or different model family as judge, and always validate against human labels.

How does LLM evaluation relate to observability?

Offline evals catch regressions before deploy. Online observability (logs, traces, user feedback) catches issues evals miss. Both are required — evals are the leading indicator, production metrics are the ground truth.


Conclusion

LLM evaluation how to test is not a one-time benchmark run — it is ongoing infrastructure:

  • Public benchmarks narrow model selection
  • Custom eval sets encode your product's definition of correct
  • LLM-as-judge scales review when calibrated against humans
  • Regression testing in CI prevents silent quality drops on every deploy

The teams that ship reliable AI systems treat eval suites like test suites: versioned, automated, and blocking on regression.

At HinterBuild, we build evaluation pipelines for production LLM deployments:

Schedule a consultation to design an eval strategy for your model deployment.

Free consultation

Book a free consultation call on LLM evaluation & model testing

30-minute call with the HinterBuild team. Discuss your project, architecture questions, or next steps — no obligation.

Book a meeting

Keep reading