sourced
Checks an LLM's output against its own source context, claim by claim --
grounded, contradicted, or unverified, never one blended trust score.
A RAG pipeline retrieves the right documents and still generates a sentence
those documents don't support -- a wrong number, an entity the source never
mentions, a stat close enough to sound plausible. Most RAG evaluation
scores retrieval quality: did the right chunks come back. Almost nothing
checks the output sentence by sentence against what was actually
retrieved. sourced does that second, narrower thing.
$ sourced check output.txt source.txt
[OK] Microsoft reported revenue of $56 billion.
[XX] Microsoft grew 40% year over year.
claims 40% near 'Microsoft', but the source's own text near that
entity says ['56 billion', '8%']
[??] The outlook remains uncertain.
no checkable numbers, quoted text, or capitalized entities in this claim
1 grounded, 1 contradicted, 1 unverified
(That transcript is real output, not illustrative -- source.txt says growth
was 8%; note the second claim repeats "Microsoft" by name rather than
saying "it," because pronoun coreference isn't resolved -- see "What this
does NOT do.")
Install
pip install sourced-evidence # the command it installs is `sourced`
(sourced was already taken on PyPI -- same story as receipt-evidence,
providence-evidence, and custody-evidence in this portfolio.)
Use
sourced check <output-file> <source-file> [source-file ...] [--json]
output-file is the LLM's generated text. source-file(s) are the
context it was supposed to be grounded in -- the retrieved chunks, the
document it summarized, the transcript it's answering questions about.
Multiple source files are concatenated before checking.
Exit code is 1 only if any claim is contradicted -- same convention as
receipt and
invariant: unverified is a
legitimate "can't tell," not a failure.
How a claim gets checked
- Split into claims. Every sentence in the output is one claim
candidate -- no attempt to tell a factual assertion from an opinion or
a hedge (
sourced/claims.py). - Extract signals. Numbers, quoted substrings, and capitalized
entity-shaped phrases (
sourced/signals.py) -- the concrete, matchable facts a source text either does or doesn't contain. Numbers are compared by value, not as text:1,000=1000,$56 billion=56B=56bn,8%=8 percent, and5is never "found" inside a source's56. - Classify against the source (
sourced/check.py):grounded-- every number, quote, and entity in the claim appears in the source, and at least one of them is a real anchor: a number, a quote, or a name. A claim whose only match is its own capitalized first word ("Operating costs rose...") staysunverified-- finding "operating" in the source says nothing about whether costs rose.contradicted-- a claim's number doesn't appear in the source at all, but an entity from the same claim does, near a different number. Narrow on purpose: this only fires when there's a real shared anchor pinning the comparison to the same subject, never "two different numbers exist somewhere in a long document."unverified-- everything else: no checkable signals in the claim at all, or some signal simply isn't found anywhere in the source.
--judge: let Claude decide what string matching can't
pip install 'sourced-evidence[judge]'
sourced check output.txt source.txt --judge
String matching can't see paraphrase ("costs went down a lot" vs
"operating costs fell sharply") or a flipped verb, so those claims come
back unverified. --judge sends only those claims -- never ones
already decided -- to Claude (claude-opus-5 by default, --judge-model
to change it) with the source, and applies its grounded / contradicted
verdicts.
The judge is held to the same standard as the rest of the tool: every
verdict must quote its evidence verbatim from the source, and sourced
checks that quote is really there. A verdict backed by a quote that isn't
in the source is discarded and the claim stays unverified -- the model
can't talk a claim into grounded. Its reasons show up in the report:
[OK] Costs went down a lot after the reorganization.
[XX] Operating costs rose after the restructuring.
judged contradicted by claude-opus-5: the source says "Operating costs fell sharply"
(Shape of the output, not a recorded run.) One request per check: the
source goes in a cached system prompt, so re-checking against the same
source -- the usual CI loop -- reads it at cache prices. A safety refusal
is re-run server-side on Anthropic's recommended fallback model
(fallbacks: "default"); if the chain still refuses, the claims stay
unverified and the report says so. Credentials are whatever the
anthropic SDK finds (ANTHROPIC_API_KEY, or ant auth login). Without
--judge, sourced never makes a network call.
What this does NOT do
This is the part worth reading before trusting a result.
- No semantic understanding, without
--judge. "Revenue was $56 billion" and "the company made fifty-six billion dollars" are the same fact, and the string pass will not see it that way -- it matches strings and numbers, not meaning.--judge(above) is the optional meaning-level pass; nothing requires it. - No real named-entity recognition.
signals.proper_nouns()is a capitalization heuristic, not a trained model. "Bank of America" splits intoBankandAmericabecause a lowercase joiner breaks the capitalized-word chain. Documented insignals.py, not hidden. - No real sentence tokenizer.
claims.split_claims()is a regex. "Dr. Smith signed the report." reads as two sentences. Ordinary prose splits correctly; abbreviation-heavy text won't always. - No pronoun or coreference resolution. "Microsoft reported $56B. It
grew 40%." -- the second sentence's "It" is correctly excluded as an
entity (it's a pronoun, not a name), so there's no anchor to check that
claim's number against, and it comes back
unverifiedrather thancontradicted. Each claim is checked entirely on its own; a real fix needs coreference resolution, real NLP work of its own, not attempted here. - Contradiction detection is deliberately conservative, and that cuts
both ways: a real contradiction with no shared entity to anchor on comes
back
unverified, notcontradicted-- this will under-report contradictions before it will over-report them. That's the intended trade for a tool whose whole point is not manufacturing false accusations against a source.
Tests
pip install -e .
python tests/test_claims.py # sentence splitting
python tests/test_signals.py # number/quote/entity extraction
python tests/test_check.py # the grounded/contradicted/unverified decision
python tests/test_cli.py # the real CLI entry point, real files, real argv
python tests/test_judge.py # --judge, against a stand-in client (no network)
Several tests exist specifically because a first pass got something
wrong and testing against ordinary prose (not synthetic numbers-only
input) caught it -- e.g. a number regex that swallowed a following
sentence period ("42." parsed as the number 42.), a sentence-
initial entity ("Microsoft" opening a sentence) needing case-insensitive
matching against a source that uses the same word lowercase mid-sentence,
without that leniency being used for anything else, and (found live
during a later audit) American-style closing punctuation putting the
period inside a quote ("four hours." not "four hours".) silently
merging two sentences into one claim -- the sentence-end regex only ever
checked for [.!?] immediately before the split point, never a quote
mark sitting in front of it.
MIT licensed.
Release files for sourced-evidence 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sourced_evidence-0.2.0.tar.gz | 22.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sourced_evidence-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 39.0 kB
Release files / sourced_evidence-0.2.0.tar.gz
| Download URL | sourced_evidence-0.2.0.tar.gz |
|---|---|
| Size | 22.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
db2f5daf6e260af6b2a36497c49fa708dfa35905f3c396f110c8044ea3d55d1b
|
|
BLAKE2b-256 checksum How to use checksums |
62cd48ab0c0e24b8cbee371d1a790464c920a13a64533e59e373d12fe336fef5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / sourced_evidence-0.2.0-py3-none-any.whl
| Download URL | sourced_evidence-0.2.0-py3-none-any.whl |
|---|---|
| Size | 16.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9fddc943cbd4b871248b6c74e48c2f72f9f050c2ef09fc1030adff6dc904cc0e
|
|
BLAKE2b-256 checksum How to use checksums |
008419805333c5ead118df5145989962626575dd8b3a772eafb0a5fcb8793550
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency log