cd /news/artificial-intelligence/rag-refresher-notebook · home topics artificial-intelligence article
[ARTICLE · art-113644] src=github.com ↗ pub= topic=artificial-intelligence verified=true sentiment=· neutral

RAG Refresher Notebook

A new technical notebook on retrieval-augmented generation (RAG) reports that fusing keyword (BM25) and dense-embedding search lifts recall@5 to 0.94 on 18 hand-labelled questions, and adding a cross-encoder reranker raises MRR to 0.77. The notebook, which runs offline on a laptop with two small models (176 MB total) and no API key, shows that keyword search finds more answers (recall@5 0.89) while dense embeddings rank them better (MRR 0.61), and that the two methods miss different questions.

read5 min views2 publishedAug 27, 2026
RAG Refresher Notebook
Image: Michielbdejong (auto-discovered)

A notebook about retrieval-augmented generation: getting a language model to answer questions from documents it was never trained on.

Search does the work: find the few paragraphs most likely to hold the answer, and paste only those into the prompt. Nearly all the engineering lives in that finding step, and most of it predates the models. This is written as a refresher, and section 1 places each piece against the ML you already know.

Here is the problem in one example. Ask "how do I wipe my local database and start over?" about a project whose README says make reset

. Keyword search misses it, because you did not guess the author's words. Search by meaning finds it, because "wipe" and "reset" sit near each other once words are vectors. Now ask which port local Postgres listens on. The answer is the bare token 5433

, which keyword search puts second and search by meaning never surfaces at all. A real system runs both, and section 4 shows each one failing where the other works.

About a twenty minute read. It is written to be read as much as run: outputs and figures are committed, so it makes sense start to finish without executing a cell.

Everything runs on a laptop with no account and no API key. Two small models download once (176 MB) and after that it works offline. A full run takes under a minute.

Four methods, scored on 18 hand-labelled questions:

method recall@5 MRR
by meaning (dense embeddings) 0.78 0.61
by keyword (BM25) 0.89 0.52
both, fused (RRF) 0.94 0.60
fused + rerank (cross-encoder) 0.94
0.77

recall@5 is how often the right passage appeared in the top five. MRR is how high it landed. Grading is at passage level: a labelled question names the exact string a correct passage has to contain.

Keyword search finds more answers, search by meaning ranks them better, and they miss different questions, which is the reason to run both. Fusing them lifts recall to 0.94. Only the reranker fixes the order.

With 18 questions each one is worth 0.06 recall, so this can say "that change was a bad idea" and cannot separate 0.89 from 0.94. Section 7 says so rather than rounding up, and spends a cell watching the eval catch a plausible chunking change that quietly breaks four of the eighteen questions.

One question has no single right answer. Two of these projects run a dev server on port 5173, so "start the frontend dev server" is ambiguous rather than hard, and no retriever fixes it. A metadata filter does. The ceiling is more instructive than the score.

The problem | why a language model cannot answer from your documents, and what to do about it |

  1. Where this sits | supervised, unsupervised and reinforcement; traditional ML against deep learning; and where RAG lands, which is none of them |
  2. Cutting documents into chunks | why retrieval returns passages rather than files, and what the overlap is for, shown at a real seam |
  3. Text as vectors | what an embedding is and is not, why each row is one chunk, and why cosine similarity is the idea you already know with the coordinates learned rather than chosen |
  4. Two ways to search | by meaning and by keyword, shown failing on different questions |
  5. Measuring retrieval | the 18 labelled questions and the two metrics, before anything is improved, plus which questions each method alone gets right |
  6. Fusing and reranking | fusing two ranked lists, then a slower model fixing the order, measuring after each |
  7. What moves the numbers | the eval catching a change that looks like an improvement, where you cut, questions with no single answer, and keeping embeddings fresh |
  8. Generation, in one prompt | assembling the prompt, and what each instruction prevents. It prints the prompt rather than sending it, which is what keeps the notebook keyless |

Where a piece has an obvious production counterpart, an In production note names it on the spot: LangChain, rank_bm25

, Elasticsearch, Cohere Rerank and the rest.

python -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/jupyter lab rag.ipynb

Run it from the repo root, since all paths are relative.

13 markdown files, about 12,000 words, mostly READMEs and CLAUDE.md files from 8 public repos of mine, plus a SECURITY.md and one guide page. Far too small to need retrieval, which is the point. It is a test bed rather than a use case.

Evaluating retrieval means knowing whether a result is right, and on an unfamiliar corpus that labelling is the expensive part. It is why a lot of RAG demos stop before the evaluation. Here it is cheap, because I wrote every file in the corpus and can check an answer by reading it.

rag.md

is the source and rag.ipynb

is generated from it. The two are paired with jupytext, so editing either updates the other:

.venv/bin/jupytext --sync rag.md                   # after editing either file
.venv/bin/jupyter nbconvert --to notebook --execute --inplace \
    --ExecutePreprocessor.timeout=1800 rag.ipynb   # refresh the committed outputs

rag.md | the source. Plain markdown, code in fences. Edit this | rag.ipynb | generated, with outputs committed | figures.py | matplotlib drawing, kept out of the notebook so cells stay about retrieval | corpus/ | the 13 markdown files | queries.json | 18 hand-labelled questions, each with the exact string a correct passage must contain | check.py | verifies every marker still appears in the corpus. Run it after changing either |

I got help from Claude building this. Agents can be useful not just for coding but for creating and sharing materials that help people understand key concepts.

Sean Helvey. github.com/seanhelvey

── more in #artificial-intelligence 4 stories · sorted by recency
── more on @bm25 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/rag-refresher-notebo…] indexed:0 read:5min 2026-08-27 ·