Skip to content
System One

Citation verification with System One models

An answer with citations is only as good as the citations. Match each quote against the source in code to catch fabrications, then ask Jev one Choice question about how the surrounding section relates to the claim. Confidence decides which verdicts a human reviews.

The problem

A language model answers a question and attaches sources: for each claim, a section of a document and the quote it rests on. Some of those citations don’t hold. The quote can be absent from the document altogether. It can also sit in the document word for word while the paragraph around it says the opposite of what the claim asserts.

Checking one by hand takes minutes. Find the document, find the quote inside it, then read enough of the surrounding text to judge whether it supports the claim. At the volume a generation pipeline produces, nobody does that.

Two stages automate it. A string match handles the quotes that simply aren’t there, which needs no model at all. Then Jev reads the quote’s section against the claim and returns one of a few labelled relationships, with a confidence value that decides which verdicts a person should still see.

What the state looks like

The state is the claim and the section of the source it points to, together in one object so the question is about the relationship between them. Send the section, not the whole document: Jev’s accuracy falls as unrelated content grows around the thing being judged, and a 58,000 character RFC is mostly unrelated content for any single claim.

Your code finds the section first. That’s ordinary parsing, and it also tells you which quotes don’t exist.

{
  "claim": "If a validator does not find itself in a token's audience list, it has to reject the token.",
  "section": "4.1.3.  \"aud\" (Audience) Claim\n\n   The \"aud\" (audience) claim identifies the recipients that the JWT is intended for... If the principal processing the claim does not identify itself with a value in the \"aud\" claim when this claim is present, then the JWT MUST be rejected."
}

The questions you ask

One Choice covers the ways a section can relate to a claim.

from typesafe_sdk import Choice, TypeSafeClient

client = TypeSafeClient()

response = client.system_one(
    state={"claim": claim, "section": section},
    questions={
        "relation": Choice(
            instructions="How does the section relate to the claim?",
            criteria={
                "supports": "The section states the claim or directly implies that it is true",
                "contradicts": "The section states the opposite of the claim or implies it is false",
                "says_nothing": "The section does not address what the claim asserts, either way",
            },
        ),
    },
)

answer = response.answers["relation"]
answer.choice        # supports, contradicts, or says_nothing
answer.confidence    # 0 to 1

A Choice is a pick-one question over labels you define, and it fits because the three relationships are mutually exclusive: a section can’t both state the claim and say nothing about it. You get the winning label, a probability for every label summing to 1, and a confidence value from 0 to 1 that summarises how concentrated that spread is.

The alternative shape would be a Noul, a yes/no question returning one probability from 0 to 1. It would collapse “contradicts” and “says nothing” into a single low number, and those two verdicts mean very different things to whoever reads the report. Keeping them apart is the reason to use a Choice here. The primitives guide sets out the tradeoff.

Decision policy

Four verdicts come out of two stages. Confidence decides which ones stand on their own.

AUTO_ACCEPT = 0.8

RELATION_TO_VERDICT = {
    "supports": "verified",
    "contradicts": "contradicted",
    "says_nothing": "unsupported",
}

def verdict(status, answer):
    if status == "missing":
        return {"verdict": "fabricated", "confidence": None, "auto": True}
    return {
        "verdict": RELATION_TO_VERDICT[answer.choice],
        "confidence": answer.confidence,
        "auto": answer.confidence >= AUTO_ACCEPT,
    }

Above 0.8 the verdict stands. Below it, a person confirms before anything acts on it. Start high and lower the bar as you see how the model does on your own documents, which is the general shape of confidence-gated actions applied to a verification step.

The cookbook’s run over eight citations against RFC 7519, recorded against jev-1.12 rather than the current jev-1.13, shows what the two stages each catch. Four accurate citations came back verified at 0.93 or higher. One quote wasn’t in the RFC at all, so the string match alone marked it fabricated without a model call. One quoted its section word for word while that same section says the claim’s requirement is optional, and Jev returned contradicts at 0.99. Two came back unsupported at 0.27 and 0.56, both under the threshold, so both went to a human. That last pair is the argument for the second stage: one of them quotes the source exactly, and the section it came from says nothing about the claim.

The string match is exact after normalising whitespace and curly quotes, which means a truncated or lightly reworded quote gets marked fabricated. A pipeline that tolerates loose quoting needs fuzzy matching in code instead. citation-verifier is a public project built around this check.

When not to use this

Don’t ask Jev whether the quote appears in the document. That’s a string operation, and counting or locating exact text is a documented weak spot where code is both exact and free. Don’t ask it to compare dates or figures inside the claim either, because dates are read as text rather than as ordered quantities, so a claim like “the rule took effect before the amendment” needs the dates extracted and compared in code.

Two more limits. A claim that depends on several hops through the document, where section 4 defines a term that section 9 then qualifies, is the kind of indirection that costs accuracy: either assemble the relevant sections into one state or split the check into separate literal questions. And if the source document came from the open web, treat it as untrusted, since Jev has no default protection against text written to steer the model, and a document can argue for its own reading. Screening retrieved sources is covered in RAG passage filtering.

FAQ

Why is the string match a separate stage?

A quote that isn’t in the source is a fact code can establish exactly, at no cost. Sending it to a model turns a certain answer into a probable one. The match also returns which section the quote came from, and that section is the text the model reads next, so the cheap stage feeds the expensive one.

What does a low confidence verdict actually mean here?

That the probability spread across the three relationships was flat, which usually means the section touches the claim’s subject without settling it. In the cookbook’s run both low-confidence citations were correctly labelled unsupported at 0.27 and 0.56, and both were still worth a human glance. Low confidence flags ambiguity, not error.

Can I check many citations in one request?

One request per citation, because each one needs its own section in the state. What you can add cheaply is more questions about that same pair, since TypeSafe reports no speed cost for additional questions. Asking whether the quote is used out of context, or whether the section is the best one available, rides along in the same call.

Does this work on documents other than RFCs?

The model half does. The parsing half doesn’t: splitting a source into addressable sections is document-specific code, and an RFC’s numbered headings are unusually tidy. Swap that parser for one that fits your format, keep the question and the thresholds, and the rest of the pipeline is unchanged.

Examples in the wild

Article

What Is Jev? How to Implement TypeSafe AI's Decision Model

Hands-on guide using the Python SDK for support routing, with a timing comparison against Claude (0.35s vs 8.83s) and a defect-detection comparison where Jev found six of seven defects to Claude's seven. Warns that high confidence does not guarantee correctness.

More retrieval and knowledge use cases