open source · Apache-2.0 · runs locally

Verify RAG answers against the sources they came from.

Groundlens marks the words your sources don't support, and shows you what each one should have said.

It checks answers for grounding and faithfulness against their retrieved sources, the job people reach for hallucination detection, citation checking or RAG evaluation to do. It differs in what comes back: marks and evidence for a reviewer, not a verdict or a score to threshold.

$pip install groundlens
Add it to Claude or Cursor Read the docs
the answer under check

SOURCE  invoice.pdf#p1

…the total amount due is 10,000 dollars, payable within 30 days…

ANSWER

The invoice total is 1,000 dollars, due in 30 days.

weakest anchor
1,000  support 0.00  checked by arithmetic
nearest in invoice.pdf#p1: ‘10,000’
One word to look at, one document to open. Thirty seconds of attention instead of five minutes.
How Groundlens checks an answer: words are anchored by meaning against the sources, numbers by exact arithmetic, and the weakest anchor is reported rather than the mean.
One answer, its sources, two channels, and a floor instead of a mean.

A wrong number inside a right sentence is invisible to similarity.

A retrieved document says the total due is 10,000 dollars. The answer says 1,000 dollars. A person catches that instantly, without a finance degree.

Embedding similarity does not. Cosine between the right answer and the wrong one is about 0.99. The error dissolves into the vector the way a drop of ink dissolves in a pool. An LLM judge does not either, because it reads for plausibility, and “the total is 1,000 dollars” is a perfectly plausible sentence about an invoice. A trained span detector does not, because single-digit substitutions are rare in its training labels.

Sentence encoders organise text by vocabulary, topic and structure. Never by truth.

on that invoice
0.79 mean support, which looks fine
0.00 weakest anchor, which is a mark in the margin

Every token-similarity metric aggregates by the mean, and the mean is where single-token errors go to die. Groundlens reports the floor.

Two channels. Words by meaning, numbers by arithmetic.

Similarity is not allowed to vote on a number, because a number is the one place it reliably lies.

Words are anchored by meaning

A word's support is the highest cosine similarity it reaches against any word in the sources, using a frozen off-the-shelf encoder pinned by revision sha, the same kind your retrieval already uses.

Numbers are anchored by arithmetic

The numeral is parsed to a value with formatting normalised, so 10,000, 10000, $10,000 and, under a declared locale, 10.000 are one number. Support is exactly 1.0 or exactly 0.0.

Every mark carries its receipt.

Groundlens never tells you the answer is wrong. It tells you which word to look at and which document to open. The word, where it sits, how it was checked, its support, and the nearest evidence, so a reviewer can settle any call in seconds.

The core install pulls in no package at all. Not numpy, not torch. A CI job fails the build if that ever changes.

Determinism is tested on ten operating system and Python combinations. The numeral channel is byte-for-byte identical across machines.

python
from groundlens import proofread, SentenceTransformerEncoder

answer  = "The rate is 4.75% payable within 45 days."
sources = [("policy.pdf#p3",
            "The rate stated is 3.90% and the term is 30 days.")]

marks = proofread(answer, sources,
                  encoder=SentenceTransformerEncoder(), k=2)

print(marks.report())
#  4.75%   support 0.00    nearest in policy.pdf#p3: '3.90%'
#  45      support 0.00    nearest in policy.pdf#p3: '30'

The same check, inside your assistant.

Groundlens ships an MCP server, so Claude Desktop, Claude Code, Cursor, VS Code or any other MCP client can check an answer against its sources without leaving the conversation. It runs locally over stdio. No text goes anywhere.

One tool, find_unsupported_words. It takes the answer and the sources it came from, and returns the weakest anchors with their receipts.

$pip install "groundlens[encoder,mcp]"

Listed on Glama →

claude_desktop_config.json
{
  "mcpServers": {
    "groundlens": {
      "command": "python",
      "args": ["-m", "groundlens.mcp"]
    }
  }
}

There is no default threshold, and that is a measurement rather than modesty.

A threshold is a property of a deployment, not of a method. It depends on the encoder, on your data, and on what a false positive costs you compared with a false negative. None of that is known here.

Across the operating-point grid we ran, the best false positive rate at 95 percent recall was 0.65, for every single-pass detector we tested, this one included. At the recall a regulated review actually needs, no fixed cut in that grid is usable. Shipping one would mean shipping a number already known not to hold.

If your pipeline needs a cut, calibrate() fits one on your own labelled data, and refuses to run on fewer than 200 examples, because below that the cut is noise.

what you get instead
  • A support score per word, lower meaning less supported.
  • Marks with receipts: the word, its span, its support, the nearest evidence.
  • A hash covering the finding, so a review can be reproduced later.

Where it sits next to the tools you already know.

Most RAG evaluation and hallucination detection tools answer “how grounded is this answer?” with a number. Groundlens answers “which words, and against what?” Those are different jobs, and they compose.

Scorers

Faithfulness and groundedness scores rank whole answers, which is what you want for dashboards, regression tests and offline evaluation. They tell you an answer looks weak. They do not tell a reviewer where to look.

Judges

An LLM reading the answer catches reasoning errors nothing here can. It also costs a call per check, disagrees with itself between runs, and reads for plausibility, which is exactly what a wrong number in a right sentence has.

Groundlens

Located evidence for a person who has to sign off. Deterministic, free to run, no model call and no network. Put it in front of a reviewer, or in front of a judge to tell it which words to argue about.

What it cannot do.

It cannot verify computed values. “Revenue tripled” against a source saying revenue went from 5M to 15M is beyond it.

It checks whether a word is supported, not whether it is attached to the right thing. A term belonging to another invoice in the same context passes.

It cannot check reasoning. That belongs to entailment models and to judges.

It inherits your retrieval. If the passage is wrong, so is the answer's grounding.

Check one answer. It takes a minute.

Open source under Apache-2.0, runs on your own machine, and there is nothing to sign up for.

pip install groundlens Add the MCP server Source on GitHub