ClaimCheck / README.md
NagaYu's picture
Point self-hosting instructions at python/ in the rule pack
1ce6570 verified
|
Raw History Blame Contribute Delete
6.55 kB
metadata
title: ClaimCheck
emoji: πŸ”Ž
colorFrom: yellow
colorTo: red
sdk: static
app_file: index.html
pinned: false
license: apache-2.0
short_description: Verify LLM answers against the context they came from
tags:
  - hallucination-detection
  - groundedness
  - rag
  - guardrails

πŸ”Ž ClaimCheck

Verify LLM answers against their source context before they reach users.

Paste an answer and the context it was supposed to be grounded in. ClaimCheck extracts the claims it can check as strings β€” numbers, dates, quotes, entities, URLs β€” matches each against the context, and returns a verdict (pass / annotate / retry / block), a grounding score, and how much of the answer it did not check.

Everything runs in your browser. No server, no network call, no telemetry β€” the verifier is ~1,700 lines of dependency-free JavaScript executing in this tab. Paste real model output if you want to; it stays on your machine.

πŸ“¦ Rule pack NagaYu/claimcheck-rules
πŸ“Š Evaluation set NagaYu/claimcheck-eval
⚑ Latency ~0.2 ms typical · 15 ms on a 20,000-character worst case
🌏 Languages English and Japanese

Design philosophy

We cannot detect every hallucination. Nobody can, and a tool that claims to is lying to you.

But we do not have to. The observation this is built on:

Dangerous hallucinations are specific.

A model that invents "the contract terminates on 2023-11-04 under Article 12, with a penalty of 3,400,000 JPY" is far more harmful than one that says "the contract may terminate at some point". And the specific one is checkable as a string: numbers, dates, quotes, entities and URLs are literal tokens that either do, or do not, appear in (or follow arithmetically from) the source.

So ClaimCheck verifies only what it can verify deterministically, and declares the rest out of scope β€” loudly.

⚠️ Always read coverage

Two independent numbers, and reporting one without the other is the most dangerous way to misuse this tool.

field question it answers
grounding_score Of the claims I checked, what share held up?
coverage What share of the answer did I check at all?
grounding_score 1.00 + coverage 0.10
  = one sentence checked out and nine were never looked at.
  = NOT "this answer is verified".

An answer of pure hedging produces zero claims and scores 1.00 on any naive groundedness metric.

Claim statuses

status colour meaning
supported 🟩 Appears literally in the context after normalisation
derived 🟦 Computable from context numbers by 2–3 term arithmetic; the formula is shown
approximate 🟨 A near match exists β€” paraphrase, finer-grained date, related URL
unsupported 🟧 Nothing in the context backs it
contradicted πŸŸ₯ The context holds a near but different value

approximate is deliberately not counted as grounded: a paraphrased quote is still an inaccurate quote.

Measured behaviour

On the 158-case bilingual evaluation set:

answer-level recall       56/56   (100%)   cases that should be flagged, and were
clean answers passing     44/46   (96%)    the rest were annotated, never blocked
known false positives     22      (13.9%)
known false negatives     3       (1.9%)
JS/Python parity         158/158  (100%)

The false-positive rate is published deliberately. It is the number that decides whether anyone keeps a verifier switched on, and hiding it helps nobody.

Known limitation: semantic reversal

All three false negatives share a shape. A one-character change that reverses meaning keeps character similarity above 0.9, so a reversed quote reads as a faithful paraphrase:

context answer similarity status
売上高は7%ε’—εŠ γ—γŸ 売上高は7%ζΈ›ε°‘γ—γŸ 0.97 approximate ❌
ζζ‘ˆγ―ε…¨δΌšδΈ€θ‡΄γ§ζ‰Ώθͺγ•γ‚ŒγŸ ζζ‘ˆγ―ε…¨δΌšδΈ€θ‡΄γ§ε¦ζ±Ίγ•γ‚ŒγŸ 0.92 approximate ❌
revenue grew 8% revenue declined 8% 0.97 approximate ❌

Character matching cannot detect negation or antonym substitution. If your domain turns on approve/reject or increase/decrease, do not rely on quote status alone. In all three cases the answer-level verdict was still retry, because the NUMERIC and ENTITY claims inside the quote are the layer that catches it.

False positives

Faithful paraphrase Β· generic noun phrases ("Machine Learning") Β· acronym expansion (WHO ↔ World Health Organization) Β· rounding just outside tolerance Β· fiscal vs calendar years Β· identifier variants (ISO 9001:2015 vs ISO 9001) Β· URL query parameters.

ENTITY is the noisiest check. Set {"enable_entity": false} in the policy box first, and start from annotate, never block.

Policy presets

Paste any of these into the Policy JSON box. Full set in the rule pack:

// observe: nothing blocked but critical safety β€” start here
{"retry_on_contradicted": 9999, "retry_grounding": 0.0, "annotate_grounding": 1.0}

// numeric + date only: the lowest false-positive configuration
{"enable_entity": false, "enable_quote": false, "enable_url": false}

// tolerant of rounding
{"numeric_tolerance": 0.02, "contradiction_rel": 0.10}

Running it yourself

The core is an ES module with no dependencies:

import { gate, verify, buildHighlight } from "./claimcheck.js";

const r = gate(answer, context, systemPrompt, userInput, schemaJson, policyJson, tagsJson);
console.log(r.verdict, r.grounding_score, r.coverage);

A Python implementation with a Gradio UI and a REST API also exists and is behaviourally identical (verified at 100% parity on all 158 evaluation cases); it ships under python/ in the rule pack repository for self-hosting.

Disclaimer

ClaimCheck is one layer of defence, not a correctness guarantee. It verifies numbers, dates, quotes, entities and URLs against a context you supply. It says nothing about claims it could not extract, nothing about reasoning, and nothing about whether the context itself was right. A pass verdict means "the specific things I know how to check, checked out" β€” nothing more.

Apache-2.0.