A small, honest retrieval-augmented generation system built as a comparison lab: upload text, ask questions, and see four retrievers race head-to-head with real latency, real recall, and real receipts.
Most portfolio RAGs ship as "look, it answers questions." This one ships as "look at the tradeoffs." The whole point is to make the cost of retrieval choices visible.
- Upload
.txt/.mdfiles (or seed the Paul Graham essay corpus). - Ask a question. The system runs the same query through four retrievers in parallel:
- Naive cosine —
numpyloop over every chunk (exact, O(n)) - FAISS Flat — exact, SIMD-accelerated
- FAISS IVF — approximate nearest neighbour, tunable
nlist/nprobe - Hybrid (BM25 + dense, RRF fusion) — sparse + dense reranked
- Naive cosine —
- Each retriever reports latency, retrieved chunks, and similarity scores.
- A latency chart accumulates per-query points across methods. Slide the corpus size from 100 → 50 000 chunks and watch the crossover point where naive collapses and FAISS pulls away.
- Every answer cites its sources. No hidden chunks, no vibes.
Three things separate this from a weekend tutorial:
- Comparison is the product, not a footnote — the homepage is a side-by-side benchmark.
- An eval harness (
make eval) reportsRecall@5,Recall@10,MRR, andp50/p95latency against a hand-written gold set. Numbers, not vibes. - Local-only embeddings (
bge-small-en-v1.5) — no API key required to demo retrieval. Anthropic key is only needed for answer synthesis.
cp .env.example .env
# (optional) put your ANTHROPIC_API_KEY *or* OPENROUTER_API_KEY in .env
make bootstrap-uv # one-time: installs uv (Astral's Python manager)
make install # uv sync for API, pnpm install for web
make seed # loads the eval corpus as the starter document
make dev-api # FastAPI on :8000 (one terminal)
make dev-web # Next.js on :3000 (another terminal)Open http://localhost:3000.
No
python3-venvor system pip needed.uvships its own Python and managesapps/api/.venvfor you. Every Python target usesuv run ….
Or with Docker:
make docker-build
make docker-upmake evalRuns all retrievers against apps/api/src/eval/gold.jsonl. Writes JSON to eval_results/ and prints a comparison table.
apps/
api/ FastAPI + SQLite + FAISS + sentence-transformers
src/
api/ thin routers
services/ ingestion, retrieval, answer, benchmark
retrievers/ naive | faiss_flat | faiss_ivf | bm25 | hybrid
chunking/ recursive char split with overlap
embeddings/ bge-small wrapper
repositories/ document, chunk, eval_run
eval/ harness + gold set
core/ config, logger, errors, db
tests/ mirrors src/
web/ Next.js + Tailwind + shadcn
app/ comparison lab + eval results page
components/ upload, query box, answer columns, latency chart
lib/api/ repository pattern, no raw fetch in components
bge-smallover MiniLM — slightly slower, meaningfully better retrieval.- FAISS IVF over HNSW — IVF has the cleaner
nlist/nprobestory to explain in a portfolio piece. - Synthetic corpus expansion for the slider — chunks are duplicated and lightly perturbed to hit 50 k for the crossover demo. Honest about it in the UI.
- No reranker in v1 — adds a model download and complicates the comparison story. Easy to bolt on as v2.
See rules.md. All work goes through feat/* branches into dev. dev is always green.
MIT.