Skip to content
All insights
EvaluationDraft· 6 min read

Write the evaluation set before the prompt

An evaluation set written before implementation is a specification. Written afterwards, it is a description of whatever the system already does.

Byline pendingUnpublished draft — no date

Teams building with language models usually arrive at evaluation in the same order: build something that works on a few examples, demo it, discover it fails in ways nobody anticipated, then assemble an evaluation set from those failures.

That set is useful — it prevents the specific regressions it was built from. But it is not a specification. It describes the system that exists rather than the behaviour that was wanted, and it inherits every assumption that was never questioned during the build.

The set is the requirements document

Writing examples before implementation forces the questions that are otherwise deferred. What should happen when the document is a credit note rather than an invoice? When the question rests on a metric nobody has defined? When two tools disagree? Each of these becomes a row with an expected outcome, and answering them is the design work.

This is the same argument as test-first development, with one difference that makes it stronger: for a deterministic function you can often reason your way to correct behaviour, whereas for a model-driven system the only way to know what it does is to run it. The evaluation set is the only specification that can actually be checked.

Assert on the decision, not the prose

Scoring free text against a reference answer is unreliable and expensive. The productive move is to make the system emit a structured decision alongside its prose, and to assert on the structure.

ts
interface EvalCase {
  id: string
  input: string
  /** The decision the system must reach. */
  expect: {
    action: 'answer' | 'escalate' | 'refuse'
    /** Ids that must appear in the cited sources. */
    citesAnyOf?: string[]
    /** Fields that must be null — i.e. the system must admit it doesn't know. */
    nullFields?: string[]
  }
}

const cases: EvalCase[] = [
  {
    id: 'credit-note-misfiled-as-invoice',
    input: 'fixtures/credit-note-0042.pdf',
    expect: { action: 'escalate', nullFields: ['po_reference'] },
  },
  {
    id: 'uncertified-metric',
    input: 'How healthy is the north region?',
    expect: { action: 'refuse' },
  },
]
A row from an evaluation set that asserts on routing and grounding, not wording.

The second case is the one teams leave out. A set that contains only questions the system should answer cannot detect over-reach, and over-reach is the failure that reaches users. Correct refusal has to be scored as a pass.

Score failure types separately

A single accuracy number hides the only tradeoff that matters. In an operations workflow, acting when the system should have escalated is a safety failure; escalating when it could have acted is a cost failure. Both reduce one score, and they are not equally bad.

Report them separately and the tuning conversation becomes concrete: we will accept more escalations to drive safety failures toward zero. That is a decision a business can make. "Accuracy is 91%" is not.

Keep it small enough to run constantly

An evaluation set of forty well-chosen cases that runs on every change is worth far more than four hundred cases that run before a release. Choose cases for coverage of distinct failure modes rather than volume, and add a new one each time production surprises you.

  • Agent reliabilityDraft

    Confidence is not a safety mechanism

    Confidence scores tell you how certain a model is. They tell you nothing about what it costs to be wrong — and those are different questions that production systems keep conflating.

    7 min read
  • RetrievalDraft

    Retrieval is a ranking problem, not a vector problem

    Most RAG systems that disappoint are not failing at embedding. They are failing at everything that happens before and after it — chunking, ranking, and knowing when to return nothing.

    8 min read
07Next step

Have something difficult in mind?

We like ambitious problems — the ones with awkward constraints, messy data and a real cost attached to getting them wrong. Tell us what you are building and what makes it hard.

Good fit
A real workflow with real constraints, an internal owner who can make decisions, and a problem where being wrong actually matters.
Less good fit
A chatbot with no defined job, a fixed-scope rebuild of something that already works, or a project that needs to look finished before it is understood.