Everything you need to install, run, test, and experiment with the course locally - on Linux, macOS, and Windows - plus a tour of how the whole thing works. Each lesson also has its own step-by-step page; this is the project-wide reference.
For the full, printable setup guide see INSTALL.md (PDF). Provider/API-key setup and common errors live on the Troubleshooting page. The internals are in ARCHITECTURE.md.
Everything runs with the language toolchains directly - there is no Docker. Install the tools once, then run a lesson with one command. The course is polyglot: Python is the reference implementation, and most lessons also ship Node.js and C# / .NET ports that produce byte-identical results. The default AI is the Claude Code CLI (no API key - it uses your existing Claude Code login); you can switch providers with one environment variable.
Why build from scratch? Frameworks are great once you know what they hide. Writing a retriever, a chunker, a provider adapter, and an evaluation gate by hand - once - means every framework that wraps them stops looking like magic: you can read it, judge it, and decide when it is worth the dependency.
Do I need Docker? No. Everything runs with the language toolchains directly - see How it runs above.
Can I use my own model? Yes. The default is the Claude Code CLI (no API key), and you can
switch to Ollama, Gemini, or OpenAI with the RAG_PROVIDER environment variable -
see Run a lesson for the options.
What do I need to know first? Comfort writing and running code in one language (Python is the reference). No machine-learning background is assumed; each lesson builds the concepts it needs.
Does it work on Windows? Yes - the Install section has first-class PowerShell and cmd instructions alongside Linux and macOS.
You need Python 3.10+ for every lesson. Add Node.js 22+ only for the Node ports and the .NET 8 SDK only for the C# ports.
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
That is all the Python setup any lesson needs. ./run also creates the virtualenv
automatically on first use, so this step is optional if you only use ./run.
Install the toolchains (PowerShell, one-time) with winget:
winget install Git.Git Python.Python.3.12
winget install OpenJS.NodeJS.LTS # only for the Node.js ports
winget install Microsoft.DotNet.SDK.8 # only for the C# / .NET ports
Create and activate the virtualenv, then install the Python dependencies:
# PowerShell
python -m venv venv
.\venv\Scripts\Activate.ps1
pip install -r requirements.txt
# Command Prompt (cmd.exe)
python -m venv venv
venv\Scripts\activate.bat
pip install -r requirements.txt
./run, ./start,
./stop, and ./status are shell scripts - run them from Git Bash
or WSL (both come with, or alongside, Git for Windows). If you prefer native
PowerShell or cmd, call the toolchains directly instead (the
Run a lesson section shows the exact commands). The virtualenv layout differs
on Windows (venv\Scripts\ instead of venv/bin/); the project handles
both automatically.Full per-OS prerequisites, optional providers (Ollama, Gemini, OpenAI), and a per-lesson dependency table are in INSTALL.md (PDF).
One command runs any lesson locally. It sets up the virtualenv on first use and, by default, uses the Claude Code CLI as the AI.
There are two ways into every lesson: read it, or run it.
# TRAINING - read the lesson, click through the steps, served from your machine
./run -l <N> lesson # opens in a browser; no GitHub Pages, works offline
# RUNNING - run the code and tinker with it
./run -l <N> [--lang python|node|csharp] [action] # run a lesson
./run -l <N> demo # one-shot, print and exit: no model, no network
./run -l <N> test # the lesson's offline test
./run list # list every lesson
./run -h # full help
Each lesson adds its own extras on top of those. The common ones:
./run -l 1 # Lesson 1: launch the RAG web UI (auto-picks a free port)
./run -l 1 demo # the whole pipeline printed, with no AI at all
./run -l 1 ask "How do I reset the device?" # one-shot grounded answer in the terminal
./run -l 1 repl # interactive Q&A loop
./run -l 1 index [--reindex] # build/refresh the index from documents/
./run -l 2 # Lesson 2: launch the MCP tool GUI
./run -l 2 demo # call the MCP tools over stdio (no LLM needed)
./run -l 2 register # register the MCP server with Claude Code
./run -l 3 # Lesson 3: hybrid-retrieval experiment GUI
./run -l 4 # Lesson 4: RAG-safety defence playground
./run -l 5 # Lesson 5: evaluation scorecard
./run -l 6 # Lesson 6: repo-aware assistant playground
./run -l 7 # Lesson 7: LangChain vs hand-rolled playground
./run -l <N> --lang node demo # the Node.js port (byte-identical output)
./run -l <N> --lang csharp demo # the C# / .NET port (Lessons 1-6)
./run -l <N> show # walk a lesson's code and configs (Lessons 3+)
The default provider is Claude Code (no key). Override it with RAG_PROVIDER
(claude · ollama · gemini · openai); set keys
in a .env file (copy .env.example first). See
Troubleshooting for per-provider setup.
cp .env.example .env # then edit .env
RAG_PROVIDER=ollama ./run -l 1 ask "How do I connect to Wi-Fi?"
If you are not using Git Bash or WSL, call each toolchain directly - these do exactly what
./run does:
# Python (Lesson 1 RAG app)
python -m localrag web --port 5000
python -m localrag ask "How do I reset the device?"
# Python (Lesson 2 MCP server)
python mcp_server.py
# Node.js port
cd node\lesson-1
npm install
node src\cli.js web --port 5000
# C# / .NET port
cd dotnet\lesson-1
dotnet run -c Release -- web --port 5000
Every lesson ships an offline test (no network, no model) that pins its claims. Run a
lesson's test with the test action, or run the whole Python suite with
pytest:
./run -l 1 test # Lesson 1: extract + chunk + BM25 retrieval
./run -l 2 test # Lesson 2: spawn the MCP server over stdio and call a tool
./run -l 3 test # Lesson 3: BM25 / semantic / RRF rankings
./run -l 4 test # Lesson 4: the defences neutralise a poisoned document
./run -l 5 test # Lesson 5: the gate passes for the baseline, fails for the candidate
pytest -q # the full offline suite (from the repo root, venv active)
What success looks like: tests pass with no network access, the demos print their
expected output, and the polyglot lessons produce byte-identical output across Python,
Node.js, and C#. On native Windows PowerShell, run the same tests with
python -m pytest -q.
Most lessons open a local, dark-themed web GUI where you tune parameters and watch the results (and the numbers behind them) recompute live - no code editing. The published site stays static; the experimenting happens on your machine.
| Lesson | ./run | What you can tune |
|---|---|---|
| 1 · RAG | ./run -l 1 | Drag-and-drop documents, ask questions, see cited [file:page] sources |
| 2 · MCP | ./run -l 2 | Call the MCP tools and see exactly what a host receives |
| 3 · Hybrid retrieval | ./run -l 3 | BM25 k1/b, the RRF k, a synonyms toggle - with a per-document score breakdown |
| 4 · RAG safety | ./run -l 4 | Toggle quarantine / isolation / output-filter and watch an answer flip between hijacked and safe |
| 5 · RAG evaluation | ./run -l 5 | top_k, the groundedness/correctness gates, unsupported-padding - watch the gate flip |
The GUIs are a thin Flask app, so unlike the byte-checked demo they need one
dependency; ./run installs it into the project virtualenv on first use. The GUI is
Python-only by convention; the cross-language parity lives in the demo ports.
The working app is a small, readable pipeline: extract text from PDF/DOCX/TXT/MD →
chunk it → retrieve the most relevant chunks (BM25 by default, or embeddings) →
ask the model to answer grounded in those chunks, citing each claim as
[file:page]. The system prompt forces the model to answer from the retrieved context
first, say plainly when something is not in the documents, and label any general knowledge it
adds - every answer ends with a Sources: line.
The provider and retriever are swappable with environment variables:
| Provider | RAG_PROVIDER | Needs | Embeddings? |
|---|---|---|---|
| Claude Code CLI (default) | claude | claude on PATH (your login) | no |
| Ollama (local) | ollama | Ollama running + a pulled model | yes |
| Gemini | gemini | GEMINI_API_KEY | yes |
| OpenAI / compatible | openai | OPENAI_API_KEY | yes |
RAG_RETRIEVER=bm25 (default) is pure-Python keyword ranking with zero setup;
RAG_RETRIEVER=embeddings is semantic vector search and needs an embedding provider,
falling back to BM25 with a clear message if none is reachable. Full provider setup is on the
Troubleshooting page.
From Lesson 3 onward each lesson is config-driven: a single
lessons/NN-slug/lesson.json describes both how to run it and how to present it. The
engine (tools/lesson.py) reads that file to run actions, render the terminal
walkthrough (show), serve the lesson locally (./run -l N lesson), and build the
published page (build). Adding code or steps is just dropping files and referencing
them in lesson.json - no engine changes.
The course site under docs/ is plain static HTML served by GitHub Pages.
Lesson pages 3+ are generated from lesson.json with ./run -l <N> build;
Lessons 1-2 and pages like this one are hand-authored. The per-lesson and install PDFs in
docs/pdf/ are generated from the Markdown sources with
python3 tools/build_lesson_pdfs.py.
local-ai-lab/
├── docs/ # GitHub Pages course site (this page lives here)
│ ├── index.html # landing + curriculum
│ ├── lesson-1-rag.html # Lesson 1 (RAG) - hand-authored interactive lesson
│ ├── lesson-2-mcp.html # Lesson 2 (MCP) - hand-authored interactive lesson
│ ├── lesson-N-*.html # Lessons 3+ - generated from lessons/NN-*/lesson.json
│ ├── documentation.html troubleshooting.html about.html
│ ├── assets/ # shared styles + slider.js
│ └── pdf/ # generated per-lesson and install PDFs
├── documents/ # the RAG corpus - drop your files here
├── localrag/ # Lesson 1 source (extract · chunk · retriever · prompts · engine · web)
│ └── providers/ # claude_code · ollama · gemini · openai
├── mcp_server.py # Lesson 2 - MCP server (search_docs, list_documents)
├── lessons/ # config-driven lessons (3+): one lesson.json per lesson
├── tools/ # lesson engine (lesson.py) + shared GUI scaffold + PDF builder
├── node/ dotnet/ # Node.js and C# / .NET ports of the lessons
├── run # ./run -l - run any lesson locally
├── start stop status # manage the Lesson 1 web app in the background
├── tests/ # offline smoke tests
└── INSTALL.md ARCHITECTURE.md CHANGELOG.md AGENTS.md
See ARCHITECTURE.md for the data flow and module map.