Choice to read each surviving quote’s context and decide
whether it supports the claim.
Below, eight citations from an LLM’s answer about RFC 7519 (JSON Web Token) go through the
check. The four accurate ones came back verified at confidence 0.93 or higher. All four
planted failures were caught: a fabricated quote, a contradicted claim, and two unsupported
citations sent to a human.
By the end you will have a check_citation() function. Give it a source document and one
citation, and it returns a verdict — verified, unsupported, contradicted, or
fabricated — and a confidence that flags the ones a human should look at.
Setup
TYPESAFE_API_KEY. Every API call is cached in json_cache.json, which ships
with the cookbook, so re-running replays the published numbers instead of calling the
API. Delete that file to run everything live.
Numbers below came from jev-1.12 on 2026-08-16.
Load the source and the citations
The source is RFC 7519 (JSON Web Token), fetched from rfc-editor.org and committed next to this cookbook asrfc7519.txt. The code
below strips the page headers and footers, then splits the text into numbered sections.
The eight citations in citations.json were written by an LLM against the RFC. Four are
accurate; we edited the other four to fail the check.
Find each quote in the source
A quote that is not in the source is fabricated, and no model is needed to find that out. Normalize whitespace and curly quotes so a quote still matches across the RFC’s line wraps, then look for it as a substring. A match also says which section the quote came from, and that section is the text the model reads in the next step. A citation can name a section without quoting anything from it. There is nothing to match in that case, so take the section the citation names and go straight to the model.Verify whether the source supports the claim
A citation that still has a quote at this point matches the source word for word. That is not enough: the quote can be accurate and the claim built on top of it still wrong. Deciding that takes the quote’s context — the section step 1 found. OneChoice per surviving citation covers the three ways a section can relate
to a claim.
The option with the highest probability is the verdict, and AUTO_ACCEPT — 0.8 in the
code above — decides what happens to it:
- confidence at or above 0.8: the verdict stands on its own;
- below 0.8: a human confirms the verdict before anything acts on it.
Check every citation
All eight citations through the same check:verified, one fabricated, one contradicted, and two
unsupported.
epoch_seconds,aud_reject,clock_skew, andduplicate_namesare the accurate four. All of them came backverifiedat confidence 0.93 or higher, well aboveAUTO_ACCEPT.sig_reportingnever reached the model. Its quote is not in the RFC, so the string match alone marks itfabricated.exp_requiredquotes section 4.1.4 word for word, and the same section says “Use of this claim is OPTIONAL” —contradicted, at confidence 0.99.pii_encryptionandiat_futurecame backunsupportedat 0.27 and 0.56, both under the threshold, so both went to a human.pii_encryptionshows why the string match is not enough on its own: its quote is in the source word for word, and the section it came from says nothing about the claim.
rfc7519.txt and citations.json.
load_source() and split_sections() are written for an RFC’s layout, so a document of
another shape needs its own parsing.
The string match is exact after normalization: a quote that is truncated or lightly
reworded comes back as fabricated. A production system that tolerates sloppy quoting
would need fuzzy matching instead.

