Evaluating Prompts Systematically

Building an evaluation dataset, A/B testing prompt variants, and regression-testing prompts before shipping.

Why "it looks good when I tried it" isn't evaluation

Testing a prompt by trying it a few times and reading the output is exactly how most prompts get shipped — and exactly why prompt quality regressions are so common in production. LLM output is non-deterministic (even at low temperature, small input variations can shift output meaningfully), so a handful of manual tries tells you almost nothing about how a prompt performs across the actual range of real inputs it will see, and gives you no way to notice when a "small tweak" quietly makes things worse on cases you didn't happen to re-check.

Building an evaluation dataset

A prompt evaluation dataset is a set of representative inputs, ideally paired with either a known-correct answer or a clear rubric for what a good answer looks like. It should deliberately include:

  • Typical cases — the bulk of what the prompt will actually see in production.
  • Edge cases — ambiguous, unusual, or boundary inputs that are disproportionately likely to reveal a prompt's weak spots (an empty input, a wildly off-topic request, a case with two plausible right answers).
  • Known past failures — every time a prompt produces a bad output in production, that exact input becomes a permanent addition to the evaluation dataset, so the same mistake gets caught automatically the next time the prompt changes.
JSON
[
  {
    "input": "The battery life is disappointing, but the camera quality is excellent.",
    "expected_category": "mixed",
    "notes": "Known edge case: previously misclassified as purely positive"
  },
  {
    "input": "Fast shipping and works exactly as described.",
    "expected_category": "positive"
  },
  {
    "input": "",
    "expected_category": "invalid_input",
    "notes": "Empty input -- should be rejected, not classified"
  }
]

A/B testing prompts

A/B testing compares two prompt variants against real (or held-out) traffic, measuring which one performs better on a defined metric — task success rate, user satisfaction rating, downstream conversion, or a scored rubric. The core discipline is changing exactly one meaningful thing between variant A and variant B; changing several elements of a prompt at once (tone, examples, and format instructions, all in one revision) makes it impossible to attribute a measured difference to any specific change.

Plaintext
Variant A (current):
"Summarize this support ticket in 2-3 sentences."

Variant B (candidate):
"Summarize this support ticket in 2-3 sentences. Include the customer's
specific request and any error messages they mentioned, if present."

-- Run both against the same 200 held-out tickets, score each summary
-- against a rubric (or human review), compare aggregate scores.

Regression testing prompts

Just as application code gets a test suite that runs before every change ships, a prompt in active use benefits from an evaluation suite that runs automatically whenever the prompt changes — checking the new version against the full evaluation dataset (including every previously-recorded failure case) before it replaces the old one in production.

Python
def evaluate_prompt(prompt_template, eval_dataset, judge_fn):
    results = []
    for case in eval_dataset:
        output = run_prompt(prompt_template, case["input"])
        passed = judge_fn(output, case)
        results.append({"input": case["input"], "passed": passed})
    pass_rate = sum(r["passed"] for r in results) / len(results)
    return pass_rate, results

# Before shipping a prompt change, confirm it doesn't regress previously-fixed cases
pass_rate, results = evaluate_prompt(new_prompt, eval_dataset, judge_fn)
assert pass_rate >= baseline_pass_rate, "New prompt regresses on the eval set"

judge_fn can be an exact-match check for structured tasks, a rule-based check (does the output contain a required field, stay under a length limit), or an LLM-as-judge call (see this site's LLM tutorials on evaluation) for more open-ended tasks where there's no single correct string to match against.

Common mistakes

  • Evaluating a prompt change only against a handful of examples picked because they happen to be easy to check — the exact cases most likely to reveal a regression are usually the edge cases and past failures, not the easy typical ones.
  • Changing multiple things in a prompt at once and then A/B testing the result — if the new variant wins or loses, there's no way to know which specific change actually caused it.
  • Treating an evaluation dataset as a one-time setup task instead of a living artifact — every real production failure should get added to it, so the same regression can never silently ship again unnoticed.