Chapter 110 · Evaluation
Measuring groundedness and catching confident invention
Turning a judgement into an enumeration. Claim extraction, per-claim verification, the four kinds of invention, and why the fluent wrong answer is the one that reaches a customer.
8 min read
Chapter 106 found the suite's worst-calibrated criterion: no-invention, raw agreement 0.95, kappa 0.48. A judge barely better than always saying nothing was invented, scoring the property the whole system exists to control.
The fix was structural rather than a better prompt, and this chapter is that fix.
Why asking directly fails
// The question that produces 0.48.
const BAD = `Does this response contain any information not supported by
the sources? Answer yes or no.`It asks for a judgement over the whole response at once. The response is twelve sentences, eleven of which are perfectly grounded, and the model's overall impression is of a grounded answer — which it then reports. Invention is rare, local and surrounded by correct material, and a global judgement averages it away.
A rare local defect cannot be detected by a global impression. The instrument has to look at the same granularity as the defect.
Claim extraction, then per-claim verification
// Two stages. The first produces a list; the second checks each item.
// Neither stage is asked to form an overall opinion.
interface Claim {
text: string // one verifiable assertion, quoted or paraphrased minimally
kind: 'figure' | 'date' | 'name' | 'policy' | 'relation'
citation?: number
}const EXTRACT = `
List every factual claim in the RESPONSE as a separate item. A factual claim
states something about the world that could be checked: a figure, a date, a
name, a policy, or a relationship between two things.
Do not include hedges, offers to help, or restatements of the question.
Split compound sentences into one claim each.
RESPONSE: {{response}}
Output JSON: {"claims": [{"text": "...", "kind": "...", "citation": 2}]}
`// Then each claim, independently, against only its cited source. Running
// it against all sources lets a claim be "supported" by a document the
// answer never pointed at, which is a different and weaker property.
const VERIFY = `
Does the SOURCE state the CLAIM? Answer exactly "stated", "contradicted",
"absent", or "partial".
"stated" requires the source to assert the claim, not merely to be about
the same subject. A source discussing the topic without asserting the claim
is "absent".
SOURCE: {{source}}
CLAIM: {{claim}}
`Kappa went from 0.48 to 0.76, against a human ceiling of 0.79. The criterion went from uninformative to near the limit of what two people agree on, and nothing about the model or the prompt wording carried that — it was the decomposition.
// The score is now a ratio with a denominator, which is far more useful
// than a boolean: "9 of 11 claims stated, 1 absent, 1 partial".
function groundedness(claims: Verdict[]): number {
return claims.filter((v) => v === 'stated').length / Math.max(claims.length, 1)
}Four kinds of invention
Enumerating claims made the failure modes visible and countable. Over 600 Atlas answers:
| Kind | Share of invented claims | Example |
|---|---|---|
| Plausible specifics | 46% | A figure the source does not give |
| Bridging inference | 27% | Two true facts joined by an untrue "therefore" |
| Confident generalisation | 18% | "Customers in this tier typically..." |
| Fabricated provenance | 9% | A citation to a document that exists, for a claim it does not make |
Plausible specifics dominate and are the hardest to notice by reading. The source says "returns are assessed individually after the standard window"; the answer says "returns are assessed individually after 30 days". Thirty is correct elsewhere in the corpus, the sentence is fluent, and nothing signals that the number was supplied rather than found.
// The cheapest high-yield check in the whole suite, and it needs no model.
// Every number, date and proper noun in the response must appear in the
// cited sources. It catches most of the 46%.
export function unsourcedSpecifics(response: string, sources: string[]): string[] {
const joined = sources.join(' ')
return extractSpecifics(response).filter((s) => !joined.includes(s))
}
function extractSpecifics(text: string): string[] {
return [
...text.matchAll(/\b\d[\d,.]*%?\b/g), // figures
...text.matchAll(/\b(?:\d{4}-\d{2}-\d{2}|[A-Z][a-z]+ \d{1,2}, \d{4})\b/g), // dates
].map((m) => m[0])
}Atlas runs this on every production answer, not only in the suite. It costs nothing, it fires on 0.9% of answers, and a firing answer is flagged for review rather than blocked — chapter 62 made the same choice about entailment, for the same reason.
Bridging inference is the one no mechanical check reaches. Both facts are in the sources; the connective is not. It needs the per-claim judge, and relation claims are the kind it scores worst on.
Groundedness against correctness
They are different properties and conflating them hides a whole failure class.
// Four quadrants, and two of them are bad in different ways.
//
// grounded not grounded
// correct ✅ the goal ⚠️ right by luck
// incorrect ⚠️ the source ❌ invention
// is wrong| Quadrant | Atlas | What it means |
|---|---|---|
| grounded + correct | 91.2% | |
| grounded + incorrect | 4.8% | The corpus is wrong, not the model |
| not grounded + correct | 2.6% | Right from training data, not from your data |
| not grounded + incorrect | 1.4% | Invention |
The 4.8% is the row teams do not expect and it is the larger problem. The system faithfully reported a superseded document, an out-of-date price list, a policy page nobody had updated. No amount of model work fixes it; it is chapter 19's problem, surfaced by an eval suite that distinguishes the two.
The 2.6% is quietly dangerous. The answer is right and came from the model's training rather than from the company's documents, which means it will be wrong the moment the company's answer differs from the general one — and it has already demonstrated that it will answer without a source.
Reporting it
// Three numbers, never one. A single "groundedness: 0.96" collapses the
// quadrant table into something unactionable.
interface GroundednessReport {
claimLevel: number // share of claims stated by their cited source
answerLevel: number // share of answers with every claim stated
inventionRate: number // share of answers with >=1 'absent' claim
bySource: Record<string, number> // which sources produce ungrounded answers
}bySource earns its place. Atlas's ungrounded answers concentrated heavily in two sources — the scanned PDF archive and an email export — at four times the rate of the rest. That is a parsing and chunking finding arriving through the eval suite, and it redirected work away from the prompt entirely.
Key points
- Asking a judge whether a response contains invention scores kappa 0.48. A rare local defect cannot be found by a global impression.
- Extract claims, then verify each against its own cited source. Kappa 0.76 against a human ceiling of 0.79, from decomposition alone.
- Verify against the cited source, not all sources. Supported-by-something-else is a weaker property.
- Four kinds of invention: plausible specifics (46%), bridging inference (27%), confident generalisation (18%), fabricated provenance (9%).
- Check that every number and date in a response appears in the cited sources. It is free, catches most of the 46%, and runs on every production answer.
- Bridging inference needs the per-claim judge. No mechanical check reaches a false connective between two true facts.
- Separate groundedness from correctness. Atlas's 4.8% grounded-and-wrong is a corpus freshness problem no model work fixes.
- The 2.6% ungrounded-and-right came from training data. It is right today and has shown it will answer without a source.
- Report claim-level, answer-level, invention rate and a by-source breakdown. The by-source column sent Atlas's work to parsing rather than prompting.
Next: measuring refusal — the property with the lowest human agreement in the whole rubric.