video.mp4
Piste (French for "trail") β every claim leaves a complete, replayable forensic audit trail.
A multilingual fact-checking platform that combines LLMs, blind web retrieval, and an append-only audit ledger. Built with DSPy, FastAPI, PostgreSQL, Redis, and vanilla JavaScript.
Most fact-checking tools search with the claim itself β baking in confirmation bias. Piste is different:
- πΆοΈ Blind by design β the retrieval engine never sees the original claim. No confirmation bias. Period.
- π Immutable audit trail β every LLM call, every source, every vote is INSERT-only. Replay any verdict.
- π¨π¦ Built for Canada & QuΓ©bec β bilingual EN/FR pipeline pre-configured for federal + provincial political discourse.
- βοΈ Configurable β currently tuned for political fact-checking, but the pipeline accepts any domain. Swap the search domains and criticality keywords, and you're checking science, health, or finance claims.
Designed and implemented by Jinan Kordab, 2026.
The pipeline runs in four stages, each leaving an immutable record:
Claim β Stage 1 (Check-Worthiness + Atomic Decomposition)
β Stage 2 (Blind Web Retrieval β Tavily + Serper + Google CSE)
β Stage 3 (Per-Source Classification β asyncio parallel)
β Stage 4 (Verdict Aggregation β 7-way PolitiFact scale)
β Append-Only Audit Ledger (PostgreSQL)
Key architectural properties:
- Blind retrieval β Stage 2 never sees the original claim. Prevents confirmation bias at the architecture level.
- Immutable audit trail β Every LLM call, every source, every classification is INSERT-only. Replay any historical run.
- Multi-provider search β Tavily (AI search) + Serper (Google proxy) aggregated concurrently with graceful fallback.
- Bilingual β Full EN/FR support across UI, pipeline stages, verdict labels, and LLM-generated explanations.
flowchart LR
A[Claim] --> B[Stage 1<br/>Check-Worthiness]
B --> C[Stage 2<br/>Blind Retrieval]
C --> D[Stage 3<br/>Classification]
D --> E[Stage 4<br/>Verdict]
E --> F[(Audit Ledger)]
Claim verdict β French (QuΓ©bec political fact-checking):
Replay & Audit β comparing pipeline versions:
- Docker Desktop
- Python 3.12+ (for the frontend HTTP server)
- API keys (see API Keys Required below)
git clone https://github.com/YOUR_USERNAME/piste.git
cd piste
cp .env.example .envEdit .env and add your API keys:
DEEPSEEK_API_KEYβ required (LLM)TAVILY_API_KEYorSERPER_API_KEYβ at least one required (web search)
docker compose -f docker/docker-compose.yml up -dThis starts three containers:
- PostgreSQL 16 β append-only audit ledger (port 5432)
- Redis 7.2 β idempotency guard + verdict cache (port 6379)
- FastAPI backend β pipeline + API (port 8000)
The backend automatically runs alembic upgrade head on startup, so the
schema is provisioned on first boot. If you ever need to apply migrations
manually (e.g. after editing a revision), run:
docker compose -f docker/docker-compose.yml exec backend alembic upgrade headVerify:
curl http://localhost:8000/health
# {"status":"ok","version":"0.1.0"}cd frontend
python -m http.server 3000Open http://localhost:3000 β the entire UI is a single index.html file. No build step, no npm, no node_modules.
Type a claim in the textarea (English or French) and click Fact-Check Claim. The pipeline runs in ~40 seconds β watch the SSE progress bar as it moves through each stage.
| Key | Where to get it | Required? |
|---|---|---|
DEEPSEEK_API_KEY |
https://platform.deepseek.com/api_keys | β Required |
TAVILY_API_KEY |
https://app.tavily.com/home | |
SERPER_API_KEY |
https://serper.dev/ | |
GOOGLE_CSE_API_KEY + GOOGLE_CSE_ID |
https://console.cloud.google.com/apis/library/customsearch.googleapis.com | Optional fallback |
Minimum setup: DeepSeek + either Tavily or Serper. Without these, the pipeline cannot function.
piste/
βββ README.md
βββ LICENSE # MIT License
βββ FINAL.mermaid # Architecture diagram
βββ .gitignore
βββ .dockerignore
βββ .env.example # Template β copy to .env
βββ docker/
β βββ docker-compose.yml # PostgreSQL + Redis + Backend + Frontend
β βββ Dockerfile.backend
β βββ Dockerfile.frontend
βββ frontend/
β βββ index.html # Single-page vanilla JS UI
βββ backend/
β βββ requirements.txt
β βββ alembic.ini
β βββ alembic/ # Database migrations
β βββ app/
β βββ main.py # FastAPI entry point
β βββ api/ # REST endpoints (claims, verdicts, audit, replay, etc.)
β βββ core/ # Config, middleware, debug logging
β βββ db/ # SQLAlchemy models, session, base
β βββ models/ # Pydantic schemas
β βββ services/ # Pipeline service, SSE, caching, observability
βββ pipeline/
βββ compiler.py # DSPy configuration + compiler
βββ replay_engine.py # Replay historical claims
βββ stage1/ # Check-worthiness + atomic decomposition
βββ stage2/ # Blind web retrieval (multi-provider)
βββ stage3/ # Per-source classification
βββ stage4/ # Verdict aggregation + criticality gate
βββ signatures/ # DSPy typed signatures
βββ offline/ # VERIFAID offline dataset pipeline
βββ replay.py # Replay utility
- 1a: Check-Worthiness β 3-vote LLM consensus classifies the claim as CFC (Check-worthy Factual Claim), UFC (Unimportant), or NFC (Non-Factual). Non-factual claims stop here.
- 1b: Atomic Decomposition β Breaks compound claims into independent sub-claims. "Poilievre plans to abolish foreign aid and cut taxes" β two separate verifiable claims.
- 2a: Search Decision β LLM decides if web search is needed and generates neutral queries. The retriever NEVER sees the original claim.
- 2b: Web Search β Queries run concurrently across Tavily, Serper, and Google CSE. Results are merged and deduplicated by URL. French claims get French-language sources.
Each source is independently classified as SUPPORTS, REFUTES, or UNRELATED to the claim. Classifications run in parallel via asyncio.gather. This prevents cross-contamination β one source's rating doesn't influence another's.
- 4a: Criticality Gate β High-stakes claims are flagged for human review.
- 4b: Verdict Aggregator β Synthesizes all classifications into a 7-way PolitiFact-aligned verdict (True β Pants on Fire) with a probability distribution and natural-language explanation.
Every pipeline run leaves an immutable forensic trail in PostgreSQL. Click Audit Trail to see every stage's input/output snapshots, every source retrieved, and every classification decision. Click Replay to re-run the claim through the current pipeline and see a side-by-side comparison.
| Layer | Technology |
|---|---|
| LLM Framework | DSPy 2.6 over LiteLLM |
| Model | DeepSeek (deepseek-chat) |
| Backend | FastAPI 0.115 + Uvicorn |
| Database | PostgreSQL 16 (append-only audit ledger) |
| Cache | Redis 7.2 (idempotency + verdict cache) |
| Search | Tavily + Serper + Google CSE (aggregated) |
| Frontend | Single-file vanilla JS, served by Python http.server |
| Containerization | Docker Compose (3 services) |
MIT License. See LICENSE.
Copyright (c) 2026 Jinan Kordab.
Piste β every verdict leaves a trail.

