local-ai·lab

Documentation

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.

How it runs

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.

FAQ

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.

Install

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.

Linux / macOS

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.

Windows

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
Running on Windows. ./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).

Run a lesson

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+)

Switching the AI provider

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?"

On native Windows PowerShell / cmd

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

Test

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.

Experiment - the interactive GUIs

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./runWhat you can tune
1 · RAG./run -l 1Drag-and-drop documents, ask questions, see cited [file:page] sources
2 · MCP./run -l 2Call the MCP tools and see exactly what a host receives
3 · Hybrid retrieval./run -l 3BM25 k1/b, the RRF k, a synonyms toggle - with a per-document score breakdown
4 · RAG safety./run -l 4Toggle quarantine / isolation / output-filter and watch an answer flip between hijacked and safe
5 · RAG evaluation./run -l 5top_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.

How everything works

The RAG pipeline (Lesson 1)

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.

Providers & retrieval

The provider and retriever are swappable with environment variables:

ProviderRAG_PROVIDERNeedsEmbeddings?
Claude Code CLI (default)claudeclaude on PATH (your login)no
Ollama (local)ollamaOllama running + a pulled modelyes
GeminigeminiGEMINI_API_KEYyes
OpenAI / compatibleopenaiOPENAI_API_KEYyes

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.

The lesson engine (Lessons 3+)

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 published site & PDFs

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.

Repository layout

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.

Start Lesson 1: RAG → Source on GitHub