Skip to content

Repository files navigation

anchor

A runtime grounding gate for LLM outputs - catch hallucinated claims before they ship, with exact citations.

Spell-check, but for facts. Give anchor an AI-generated answer and the source documents it should have drawn from: it splits the answer into atomic claims, verifies each against the sources, attaches an inline citation to every grounded claim (pointing to the exact doc_id:start-end), and flags the ones that aren't supported. 0.93 F1 on the FEVER public benchmark, fully local, no API.

Live demo: https://anchor-production-44a7.up.railway.app/

Screenshots

Two-pane evidence viewer: the verified answer on the left (blue = cited, red = needs review); click a citation number to jump to its source chunk on the right, which flashes.

Verified answer + source evidence, two-pane

Input - sources + AI answer

Usage in your code

pip install -e .          # anchor library + NLI core
from anchor import Source, annotate, render_markdown

sources = [Source(id="policy", text="...the document the AI should have used...")]
answer  = "...the AI-generated answer..."

result = annotate(answer, sources)        # verifies each claim + cites
print(render_markdown(result))
# result.text       -> answer with inline [n] citations and warning flags
# result.citations  -> [{n, doc_id, start, end, snippet}, ...]
# result.flags      -> [claims not found in any source]

See examples/usage.py for a runnable version.

Does it work?

anchor is tested on two benchmarks:

benchmark what it tests accuracy F1
FEVER (public dataset) real fact-checking: does a claim follow from its evidence? 0.93 0.93
Stress set (31 edge cases) paraphrases, wrong numbers, entity swaps, negations, compound sentences 0.94 0.95

FEVER is the standard academic benchmark for fact-checking. The stress set tests edge cases that matter in production - hallucinations, number swaps, "do NOT" vs "must", and sentences with mixed right/wrong facts.

Reproduce: python benchmarks/eval.py (stress set) - python benchmarks/bench_fever.py (FEVER)

Examples & benchmarks

python examples/usage.py           # how to use anchor in your code
python examples/cite.py            # verify an AI answer against a source doc
python benchmarks/eval.py          # the stress-test eval (precision / recall / latency)
python benchmarks/bench_fever.py   # the FEVER public benchmark
python benchmarks/bench.py         # benchmark on benchmarks/bench.json - or YOUR own dataset
python benchmarks/distractors.py   # retrieval robustness vs decoy docs (lexical vs semantic)

Playground UI

pip install -e .[app]              # adds fastapi + uvicorn
cd playground && uvicorn app:app --reload      # http://localhost:8000

Already live: https://anchor-production-44a7.up.railway.app/

Deploy your own to any container PaaS - see Dockerfile / render.yaml. The NLI model needs ~2 GB RAM; for a low-traffic demo, enable scale-to-zero so you don't pay while idle.

Why

Telling an LLM "only answer from the sources" helps - but it doesn't guarantee or prove anything. The model still invents, and you can't show an auditor "this was grounded." anchor is the independent check plus the citation/audit trail.

  • vs RAGAS - RAGAS grades your RAG pipeline offline over a test set. anchor checks a live output at runtime, per claim, with citations.
  • vs NeMo Guardrails - NeMo is a broad behavioral-rails framework (Colang, an LLM call per rail). anchor is a focused, cheap-first grounding layer: local models, an audit trail, and the LLM judge only on the residue.

How it works

anchor verifies each claim against your source documents through a layered pipeline.

NLI verification (the core)

NLI (Natural Language Inference) judges the logical relationship between a source and a claim - entailment, contradiction, or neutral. The model uses cross-attention to compare them word-by-word, which is why it catches contradictions that similarity models miss.

source sentence (retrieved)  ->  PREMISE
AI's claim                  ->  HYPOTHESIS
                                  |
                        NLI model (DeBERTa)
                                  |
                    +-------------+-------------+
                 entailment   contradiction    neutral
                    |             |              |
               grounded       flagged      -> confirmers
               (cite source)                (lex + negation gate)

The three-way signal drives anchor's routing:

  • Entailment -> grounded. Cite the source. Done.
  • Contradiction -> flagged. The source disproves the claim. Reliable catch.
  • Neutral -> the model is unsure. The confirmers decide: lexical check (is it a paraphrase?) + negation gate (is there a "not" flip?). If neither fires -> keep flagged (conservative).

The full stack

layer what it does cost
semantic retrieval finds the right source by meaning (embeddings) local, ~15 ms
NLI (DeBERTa) entailment / contradiction / neutral judgment local, ~40 ms
lex confirmer clears paraphrase false-alarms (high overlap = paraphrase) pure Python, free
negation gate keeps "do not need vs must" flagged pure Python, free
decomposition splits compound sentences ("A and B") into atomic claims pure Python, free
LLM judge (optional) clears the last false alarms (precision confirmer) your key, ~c per call

Semantic retrieval is the default (handles vague phrasings like "10 to 3"); lexical retrieval is available as a faster fallback. When a sentence has multiple facts joined by "and", anchor splits them and checks each separately - so "tokens expire after 24h and the pro plan costs $99" becomes two independent checks, each with its own citation or flag. Every result is logged to a permanent audit trail.

Project layout

anchor/                # the library package (importable: from anchor import ...)
examples/              # usage + citation demos
benchmarks/            # eval, bench, FEVER runner, sample cases
playground/            # FastAPI app + UI (separate [app] deps, thin layer over the library)
docs/                  # UI screenshots
pyproject.toml         # installable; deps + [app] extra
Dockerfile / render.yaml   # one-command container deploy

anchor is the verification layer, not a retriever - point it at your existing corpus (RAG retriever, Confluence, SharePoint, vector DB) and it verifies + cites.

Roadmap

  • Citation layer - inline citations with exact doc:start-end
  • Local NLI core (DeBERTa) + lexical & negation confirmers
  • Public benchmark on FEVER (0.93 F1)
  • Provider-agnostic + local judge (Ollama) for air-gapped deployments
  • Human-in-the-loop gate (block / strip / flag-for-review)

License

MIT

About

A runtime grounding gate for LLM outputs - catch hallucinated claims with exact citations. Local NLI core + optional judge confirmer.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages