How to Evaluate LLM Prompts Without Fooling Yourself
Freeze a small case set, score it with a rubric you can defend, run it in CI, and stop shipping prompt changes on vibes. How to build an evaluation harness for prompts.
InnovProduct Team
Library Team
Prompt work has a measurement problem. Code has tests, design has review, and prompts have vibes. You change three words, the demo output looks better, and it ships. Three weeks later a user reports the thing you broke, and nobody can tell which of the last nine changes did it, because there is no record of what the prompt used to do. A prompt evaluation harness is a small investment that turns opinions into a number you can defend.
Start small and freeze it
Twenty to fifty cases is enough to start, and small is an advantage rather than a compromise. A set you can read in full gets used; a set of a thousand does not. Build it from real inputs, not imagined ones: production logs, support tickets, the queries your team actually typed. Include the awkward cases, the empty inputs, the ones in another language, and the ones where two people on your team disagreed about the right answer.
Freeze the set the day you create it. A test set you keep editing stops measuring regression and starts measuring your mood.
Two kinds of score, never blended
Most teams collapse every judgment into one number and then cannot tell what moved. Split them. Mechanical checks, like schema validity, required fields, or refusal correctness, are pass or fail and can be automated completely. Quality judgments need a rubric, and a rubric has to be specific enough that two reviewers would land in the same place.
| Metric | Type | How to measure |
|---|---|---|
| Format compliance | Mechanical | Validate the response against the schema; binary |
| Refusal correctness | Mechanical | Does it refuse exactly the cases it should, and no others |
| Factuality | Judged | Every factual claim traceable to the provided context, no outside knowledge |
| Instruction adherence | Judged | Did it do the specific thing asked, not a related thing |
| Tone and style | Judged | Scored against a written rubric, not a general impression |
| Latency and token cost | Mechanical | Measured per call, tracked as a trend |
Keep them separate in reporting. A change that lifts style scores and drops factuality is a regression wearing a disguise, and it will hide inside a blended average every single time. Report mechanical and judged scores on different lines and treat a drop in either as a failure.
Rubrics that survive disagreement
- Describe the failure, not the ideal. "Mentions the refund policy" beats "high quality".
- Make each level observable. If you cannot point to the sentence that proves the score, it is a vibe.
- Anchor with real examples from your own set, including one at each level.
- Allow "cannot tell" when it is true. Forcing a score on an ambiguous case adds noise.
- Have two people score ten cases independently before you trust the rubric. Their agreement rate is your rubric quality.
RUBRIC v4 — support ticket classification
For each ticket, output a JSON object:
{"label": "pass|fail|unclear", "reason": "<one sentence>"}
"reason" must quote the ticket text that decided the score.
PASS requires all three:
1. The label matches the annotated label exactly.
2. The reason cites words that appear in the ticket.
3. The confidence field is not "low" when the label is not "other".
FAIL when the label is wrong OR the reason invents text not in the ticket.
UNCLEAR only when the ticket is genuinely ambiguous, meaning two
annotators disagreed. Do not use UNCLEAR for a hard case you got wrong.
Worked examples at each level are in eval/rubric-examples-v4.md.Writing the rubric in the same declarative style you would use for the model is not a coincidence. The rubric is itself a prompt, so it inherits the same failure modes, which is another reason to keep it versioned and reviewed like one.
When two runs disagree
A non-deterministic model is not a flaky one, and reading disagreement is a core evaluation skill. If a single case flips between runs, the model is sitting on an unresolved decision boundary and the score is telling you the prompt has not settled it. Sampling several completions per case and reporting the mean alongside the spread separates "this prompt is reliable" from "this prompt is right on average", which are very different claims to make about a system.
| Pattern | What it means | Action |
|---|---|---|
| One case flips on every run | Decision boundary, prompt under-specified | Tighten the rule or add a worked example for that case |
| All runs agree but the mean is low | Systematic bias, not noise | Fix the bias; more sampling will not help |
| Score improves as runs increase | High variance, not real capability | Lower temperature or cap outputs at two |
| Judge and human reference disagree | Weak rubric, not a weak model | Add explicit tie-breaking criteria to the rubric |
Judge-based grading adds a second layer of variance and deserves to be treated as a measured instrument rather than an oracle. Report agreement between judge and human reviewers, hand-check a sample of disagreements, and keep the rubric short enough that a person can apply it consistently. A judge with a vague rubric is slower and less honest than a human reading the same output.
What to do with the score
A score without a decision rule becomes decoration. Write down the number that must hold before you ship, the number that triggers a rewrite, and who owns the decision. Most teams find that a 3 percent regression on a high-traffic case is worth more attention than a 15 percent regression on a rare one, and weighting the cases by real volume is the only way to know which is which.
- Set a ship threshold and a rewrite threshold, and put both in the pull request template.
- Weight cases by real traffic rather than treating every row as equal.
- Track the eval score in the same place as error rates, so a regression has somewhere to surface.
- Re-baseline deliberately when the model changes, and record why in the eval file.
Put it where changes happen
An evaluation that only runs when someone remembers is not a harness, it is a script. Run it in CI on the pull request that changes a prompt, and keep the prompt in version control as code rather than in a dashboard field, so the diff is reviewable and the history is real. Score against the frozen set on every run and store the numbers, because the trend is worth more than any single reading.
If you cannot state the before-and-after number, you made a change, not an improvement.
Pair it with production monitoring, because a frozen set tells you about the cases you thought of. Track disagreement rates, escalation rates, and user corrections on live traffic, and when one spikes, add that case to the frozen set with an annotation. The set then grows from evidence instead of from imagination.
What evaluation is not for
- It is not a way to pick a model by vibes. Compare models on the same set, in the same run, or the comparison means nothing.
- It is not a substitute for reading outputs. Skim the failures every time; the pattern in the failures usually names the next fix.
- It is not a license to optimize the metric. Once a number is a target, it stops being a measurement. Rotate in fresh cases.
- It is not exhaustive. Fifty cases cannot prove correctness. They can prove you did not break what already worked.
How many test cases do I need for prompt evaluation?
Twenty to fifty is a workable start and enough to catch most regressions. Readability matters more than volume: a set your team actually reviews will beat a large set nobody opens. Grow it from production failures rather than from imagination.
Should I use an LLM to grade my LLM outputs?
It works well as a first pass and badly as the only pass. LLM judges share the biases of the model under test and drift between versions, so pin the judge version, calibrate it against human labels on a sample, and report agreement rather than trusting the score.
How often should I run the evaluation?
On every pull request that touches the prompt, the model, or the schema. That is the point of putting it in CI. Also re-run on a schedule after a provider model update, since silent behavior drift is the most common cause of a regression nobody can explain.
My prompt scores well but users complain. What now?
Your set measures the cases you anticipated. Pull the real failing inputs from logs, annotate them, add them to the frozen set, and re-score. Repeat until the complaints stop. A test set that never grows is measuring a problem you solved months ago.
The practices this measurement supports
An evaluation harness is what makes every other prompt practice verifiable rather than assumed.
- Best practices for training AI models with promptsThe pillar overview, including why frozen eval sets and one-change-per-iteration come before optimization.
- Few-shot prompting: how many examples you needA method for deciding example count that depends entirely on having something to measure against.
- Prompting AI agents: task decomposition and tool useAgent behavior is the hardest thing to eyeball. This covers the traces and evals that make it tractable.
Stop shipping prompts on taste alone
Prompt bundles that ship with their expected outputs, edge cases, and failure behavior give you something concrete to test against. Browse a library built for evaluation rather than for the screenshot.
Browse the library
