Evaluation corpus reference
An evaluation corpus is a YAML file of questions and the answers a good
generation returns for them. ptah inference evaluate --corpus reads it,
embeds each query with the generation under test, searches, and reports how
well the results matched.
It is the only thing that answers “is this generation actually working”. Verification measures coverage and freshness — every row has a vector, and that vector was computed from the text the row holds now — and neither of those is a statement about whether search results are good.
Top level
Section titled “Top level”| Field | Meaning |
|---|---|
version |
The format version. Must be 1; anything else is refused. |
name |
A display name, for a report a person reads. Outside the digest. |
description |
Optional prose, for the same reader. Outside the digest. |
default_k |
How deep a case looks when it does not say. |
cases |
The queries. At least one. |
cases[]
Section titled “cases[]”| Field | Meaning |
|---|---|
id |
Names the case in the report. Required. |
query |
The text embedded and searched with. Required. |
k |
How deep this case looks. Zero or absent uses default_k. |
required |
Source keys that must appear in the top k. |
relevant |
A map from source key to graded relevance, for the ranked measures. |
A key is the source row’s key as a string. Where the specification declares a
composite key, the components are joined with U+001F, the ASCII unit separator,
in the order key_fields names them.
This is the corpus file’s spelling, and it is deliberately not the identity
verification compares rows by — that one is length-prefixed and reads
6:acme1:2. A key here is matched against what you wrote, so it has to be
something you can write.
A case must declare required, relevant, or both. One that declares neither
is refused: any answer satisfies it, including none, and it lifts the mean of
every measure it is averaged into.
A grade must be greater than zero. Zero is what an unlisted key already means, so a listed zero reads as “this one matters” and scores as “this one does not”.
What each field decides
Section titled “What each field decides”required is a hard expectation and relevant is a score, and they answer
different questions. A corpus that only scored would pass a generation that
ranks the one document the question is about eleventh; a corpus that only
required would say nothing about the order of the ten above it.
Three numbers come out, all averaged over the cases:
| Number | What it measures |
|---|---|
recall |
The share of a case’s relevant keys that appear in its top k. |
MRR |
One over the position of the first relevant key. |
NDCG |
The graded ranking, discounted by position. |
All three read relevant. A case that declares required and no relevant is
read as grading each required key 1, because a case naming a right answer is
naming a right answer. Declaring relevant explicitly overrides that, which is
how an author says one right answer is better than another.
That derivation matters more than it looks. A case contributing no grades
contributes to no ranked measure at all — which is right for a case that
expects nothing, and wrong for one that named its answer under required. Were
such a case ungraded, a corpus made entirely of them would score nothing, and
the mean of nothing is zero: recall 0.000 for a generation answering every
query perfectly, and a --max-recall-drop gate comparing against it.
A complete example
Section titled “A complete example”version: 1name: docs questionsdescription: what support actually gets askeddefault_k: 5cases: - id: pricing query: "How much does it cost per month?" required: ["42"] relevant: "42": 1.0 "43": 0.5 - id: cancellation query: "How do I cancel?" k: 3 required: ["17"]The digest
Section titled “The digest”A corpus has a content address, and evaluate reports it beside the numbers.
It covers the queries, their depths and their expectations — everything a
number depends on — and not name or description, for the same reason a
generation’s display name is outside its identity: renaming a corpus does not
make its measurements incomparable.
Two evaluations are comparable when their corpus digests match. That is what
--baseline rests on.
Writing one
Section titled “Writing one”The keys have to be keys the generation actually holds, so write the corpus against the source table rather than from memory. A query whose answer is a row your filter excludes measures the filter, not the model.
Ten to thirty cases is usually enough to see a regression. What matters more than the count is that they are questions somebody asked: a corpus of questions you invented measures how well the model answers questions you invented.