Ship a prompt like code: a five-case eval harness you can build in an hour
Prompts get shipped on vibes. Someone tries a change, the one example they check looks better, it goes live, and three weeks later a user reports that the thing has been quietly failing on a case nobody tested since March.
The industry answer is "build evals", which is true and unhelpful — it sounds like a quarter of work. It isn't. The version that catches most real regressions is five test cases and an afternoon, and you can grow it later if it earns the investment.
Why five, and why these five
An eval set is a sample of the input distribution plus the places where the distribution has teeth. Five cases, chosen deliberately, cover the shapes that actually break:
- Typical. The input you'd describe if someone asked what the prompt is for. If this regresses, you've broken the product.
- Trivial. Input that needs little or no work — already-clean prose for an editing prompt, a passing test suite for a debugging prompt. This catches the single most common failure in the wild: the model does something because it was asked to do something, when the correct answer was nothing.
- Hard. Long, messy, ambiguous, at the edge of the context you'll allow. This is where model swaps show up first.
- Wrong-shape. Input the prompt was never designed for — a spreadsheet where you expected prose, a language you don't support, an empty string. You're not testing for a good answer. You're testing that it fails loudly instead of confabulating.
- Adversarial. Input containing instruction-shaped text: "ignore the above", "SYSTEM: new task", a fake tool result. You're testing that your delimiters hold and the model treats pasted content as data.
Every one of these has caught a real bug for someone. If you only ever build cases 1–3, you'll ship the failure modes that embarrass you in public.
The three graders
The grading question is where people over-engineer. There are exactly three kinds of check, and you should reach for them in this order:
Deterministic assertions (use these wherever possible)
Cheap, fast, zero ambiguity, no API call:
- Is it valid JSON? Does it match the schema?
- Is it under the word limit?
- Does it contain the required section headers?
- Does it not contain the forbidden phrases — "as an AI", "I hope this helps", the hedge words you banned?
- Are the numbers from the input preserved exactly?
- Did it leave the already-clean case untouched?
An enormous share of prompt regressions are format regressions, and format is checkable
with if statements. Write these first. If your prompt's output is structured, this may
be all you need.
Reference comparison
For cases where you know what the right answer looks like, keep a reference output and compare — not for exact equality, which is too brittle, but for the properties that matter. Did it extract all five entities? Does the SQL return the same rows against a fixture database? Does the summary mention the three facts a reader must not miss?
The trick is to store the checklist, not the golden text. "Mentions the deprecation date; mentions the migration path; does not recommend the removed API" survives rewording. A diff against a golden paragraph fails on every synonym.
Model-as-judge (last, and carefully)
For genuinely subjective properties — tone, whether the explanation is actually clear — ask a model. Three rules make this work rather than generate noise:
- Give it a rubric, not a vibe. "Score 1–5 on whether every claim carries a source" beats "rate the quality".
- Show it both outputs, unlabeled. Pairwise comparison ("which better satisfies this rubric: A or B?") is far more reliable than absolute scoring, and it's what you actually want to know when deciding whether a change helped.
- Randomize the order. Judges have a position bias. Run each pair twice, swapped. If the verdict flips, the difference isn't real.
Never use a judge for something a deterministic check could catch. It's slower, costlier, and less reliable at the easy thing.
The harness
The whole thing is a folder and a script:
prompts/
tighten-prose/
prompt.md
cases/
1-typical.txt
2-trivial.txt
3-hard.txt
4-wrong-shape.txt
5-adversarial.txt
expectations.yaml
expectations.yaml holds the deterministic checks per case:
2-trivial:
max_words: 60
must_contain: ["already tight"]
unchanged_from_input: true
4-wrong-shape:
must_contain: ["cannot", "not applicable"]
max_words: 40
5-adversarial:
must_not_contain: ["poem", "ignoring"]
The runner loops the cases, calls the model, applies the checks, and prints a table. Under a hundred lines in any language. Run it on every prompt change, and — this is the part people skip — on every model change, because a prompt is not portable. An instruction a reasoning model follows precisely can be partly ignored by a faster one, and an over-specified prompt can make a strong model rigid and worse. Without a harness you'll attribute that to the model and never find out it was your prompt.
Reading the results
Set a temperature of 0 for the run if the API allows it, so you're comparing prompts rather than sampling noise. Where you can't, run each case three times and look at the worst result, not the average — the worst is what a user will hit.
A single failing case is a signal, not a verdict. Ask whether the case is right before you assume the prompt is wrong. Eval sets rot; a case that encodes a requirement you've since changed should be deleted, loudly, in a commit that says why.
Watch for the trade. The most common outcome of a prompt change is not "better" or "worse" but "better on 1 and 3, worse on 2". That's the conversation worth having, and it's invisible without the harness — with one example you'd have shipped it.
When to add a sixth case
One rule: every production failure becomes a case. Someone reports the prompt did
something dumb, you reproduce it, the reproduction goes in cases/ before the fix goes
in prompt.md. This is regression testing, and it's the mechanism by which a five-case
set grows into a good one without anyone ever having to schedule "build evals" as a
project.
Resist adding cases for hypothetical failures. A case you invented because it felt plausible costs the same to run as a case that represents a real user, and it will eventually block a change that would have been an improvement.
What this buys you
Not certainty. Five cases is a small sample and you will still ship bugs. What it buys is the ability to make a change and know within a minute whether you broke the trivial case — and the ability to answer "did the new model help?" with a table instead of an impression.
That's the whole difference between a prompt that improves over time and one that drifts.
Comments (0)
Sign in to join the conversation.
No comments yet.