diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..2360bcc --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,38 @@ +name: pages + +on: + push: + branches: [main] + paths: ["groundcheck/**", "site/**", ".github/workflows/pages.yml"] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + deploy: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/checkout@v7 + # The playground runs the package itself in the browser, so it ships the + # same source the command line does. + - name: Assemble the site + run: | + mkdir _site + cp site/* _site/ + zip -qr _site/groundcheck.zip groundcheck -x '*/__pycache__/*' + - uses: actions/configure-pages@v6 + - uses: actions/upload-pages-artifact@v5 + with: + path: _site + - id: deployment + uses: actions/deploy-pages@v5 diff --git a/README.md b/README.md index b8b91f1..7eeacdd 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,10 @@ A **deterministic groundedness linter** for RAG and LLM answers. It reads an answer and the sources it was supposed to be based on, and flags the claims that aren't actually supported — above all, **fabricated numbers**. +**[Try it in your browser](https://rootx22.github.io/groundcheck/)**: paste an +answer and its sources and see what it flags. It runs groundcheck itself through +Pyodide, so nothing you paste leaves the page and no model is called. + ```console $ groundcheck --answer answer.txt --source q3-report.txt --fail-on high diff --git a/site/index.html b/site/index.html new file mode 100644 index 0000000..8de7bd3 --- /dev/null +++ b/site/index.html @@ -0,0 +1,390 @@ + + + + + +groundcheck: catch the numbers your RAG answer made up + + + + + + + + + + + + +
+
+ groundcheck + +
+
+ +
+
+

Catch the numbers your RAG answer made up

+

Paste an answer and the sources it was given. groundcheck flags figures that appear in no source, claims nothing supports, citations that point at the wrong source and negations that got flipped. It is deterministic: the same input always gets the same verdict, so it can gate a CI pipeline where a model acting as judge cannot.

+

Everything runs in this page. Nothing you paste is sent anywhere, and no model is called.

+
+ +
+
+
+ +
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+

Loading Python in your browser. About 10 MB the first time, cached after that.

+ +
+
+ + + +
+

The same check in your pipeline

+
pip install git+https://github.com/RootX22/groundcheck.git
+groundcheck --answer answer.txt --source doc1.txt --source doc2.txt
+echo '{"answer": "...", "sources": ["..."]}' | groundcheck --json-input - --fail-on high
+

Standard library only, exit codes for CI, MIT licensed. Source, docs and the limits of the heuristic.

+
+
+ + + + + + + diff --git a/site/og.png b/site/og.png new file mode 100644 index 0000000..ec2ed45 Binary files /dev/null and b/site/og.png differ diff --git a/site/playground.py b/site/playground.py new file mode 100644 index 0000000..e5ae35c --- /dev/null +++ b/site/playground.py @@ -0,0 +1,16 @@ +"""Bridge between the browser page and groundcheck, run inside Pyodide. + +Sources arrive as a JSON list so the page never builds Python objects itself, +and the report goes back as the same dictionary the CLI prints with --json. +""" + +from __future__ import annotations + +import json + +from groundcheck import check + + +def analyse(answer: str, sources_json: str, threshold: float) -> str: + sources = [source for source in json.loads(sources_json) if source.strip()] + return json.dumps(check(answer, sources, support_threshold=threshold).to_dict())