Answers · Putting a model into service
Should my evaluation cases be real user queries or ones I write myself?
In shortUse real user queries, sampled by behaviour rather than at random, plus a held-out part nobody tunes on. Cases you invent measure what you imagined users ask.
Real queries from your own logs, sampled by behaviour rather than at random. Cases you write yourself are a sample of your imagination of your users, and they will flatter the system precisely where it is weakest.
Take a log where 70 per cent of requests are one simple intent that every version already handles, and 2 per cent are a hard intent where the versions you are choosing between actually differ. Sample 300 uniformly and you spend cases on the easy intent and on the hard one. Weight each group by how often the versions disagree, 2.1 per cent in the easy group and 24.5 per cent in the hard one: informative cases against . The comparison rests on about six cases in total, and six cases will report no difference whatever the truth is.
| group | per cent of traffic | cases in a uniform 300 | cases where versions differ | cases under a 60-per-group quota |
|---|---|---|---|---|
| simple lookup | 70 | 210 | 4.4 | 1.3 |
| multi-step question | 14 | 42 | 1.8 | 2.6 |
| ambiguous request | 9 | 27 | 1.8 | 4.1 |
| out of scope | 5 | 15 | 1.7 | 6.7 |
| adversarial | 2 | 6 | 1.5 | 14.7 |
The natural guess runs the other way: write the cases yourself and you get clean, unambiguous, correctly spelled queries, each with a known right answer. But a query with no typo, no missing context and no half-finished sentence is not a harder version of a user query, it is a different distribution. Where the failures live in the mess, in a pronoun with no referent or a request that changes topic mid-sentence, the hand-written set scores well because you removed the thing that breaks the system. Cleanliness is a property of the set, not of the product, and the score has no idea which one it is measuring.
The fix is equal quotas, not proportional ones. Sixty per group instead of the traffic share turns 1.5 informative adversarial cases into , and the rare group starts to carry the comparison. Over-sampling a group does not distort anything provided you report two numbers from the one run: the per-group scores, which say where a change landed, and the aggregate reweighted back to real traffic shares, which estimates the user experience.
Here is the traffic share of group and its score in the set. With per-group scores of 0.95, 0.90, 0.85, 0.80 and 0.75 and the traffic shares above, , , , , , and the total is 0.923. The unweighted average of the five, 0.85, would be a number no user experiences.
Size follows from the difference you intend to detect. The standard error on a score near 50 per cent is . At 100 cases that is points, and the error on a difference between two versions is times it, 7.1 points, so a four-point improvement is invisible. At 500 cases it is points each and 3.2 on the difference, comfortably under five. At 4000 it is points each and 1.1 on the difference, which is what seeing a one-point change costs; at that price the honest move is a paired comparison on the same cases rather than a bigger set.
Traffic has blind spots. It contains nothing for a capability shipped yesterday, nothing for a request nobody has thought to make yet, and nothing for the safety case that must never happen. So a minority of the set is deliberately written by hand, each case carrying a note of what it is for, reported as its own group and never weighted as though it came from the user distribution. That is different from writing the whole set. Real logs also give you inputs, not answers, so every case still needs a label, and labelling difficulty is a reason to write a case, not a reason to replace the sample with your own.
Two slow failures arrive once the set exists. People tune against it, not dishonestly but because a visible number does that to anyone improving something, so reserve a quarter that nobody reads: 75 cases out of 300, scored separately. When the visible score rises and the held-out one does not, the gain was fitting. And traffic drifts, so a set assembled in January measures a product that no longer exists by June; replace about a tenth each month, 30 of 300, from recent traffic, keeping the group quotas, so the set stays current and stays comparable.
Worth keeping
- An evaluation set is a claim about which inputs matter, so the sampling behind it decides what the score speaks for.
- Equal quotas per behaviour, weighted back to real traffic shares for a product-level number, give both the honest total and the place where a change landed.
- A held-out quarter that nobody tunes against is what separates a real gain from fitting the visible cases.
Where this goes next
This question is one step of Judging a System, Not a Model, a course that works through its subject from the beginning. The page that teaches this part of it is Choosing the Cases, and it reads the whole way through whether or not you have an account.
What an account changes is the practice: problems standing under every idea, marked with a reason rather than a tick, in a copy of the course that is yours.
Read that pageThe whole courseOther questions people ask about this
- Why do the numbers look good while users keep complaining?Because the score measures a fixed set of cases with a fixed rule, and the users are living through behaviour that the set and the rule never covered.
- My score went up two points — is that a real improvement or just noise?No. At four hundred cases a two point gain is inside the noise, so treat it as no result.
- How many cases do I need before the evaluation can even see a small improvement?About four hundred cases per set can see only a six-point move; a two-point improvement needs roughly four thousand two hundred per set.
- How small can my test set be and still catch a regression before a user does?About sixty auto-checked cases, run in two minutes on every change, blocking only on outright failures such as a case that used to pass now failing, never on a fall in the overall score.
- Why does the model return JSON wrapped in extra text when I asked for JSON?A request for JSON does not force the model to return only JSON, so you need to validate the reply and repair it once rather than assume it will parse.
- How do I make the model answer from the documents I give it instead of its own memory?Fence the documents as labelled reference material, put them before the question, tell the model to prefer them over memory, and require a passage number for each claim.