examples/ — The Curriculum
Numbered 00–06. Each lesson is example.js + CONCEPT.md + CODE.md. Self-contained — runnable without reading any other lesson. ~6,954 lines of teaching code.
15 lessons 7 top-level dirs ~6,954 LOC Cognitive locality
📚 What is this & why does it exist?
examples/ is the actual product of this repository. src/ exists as a side-effect of stabilising what the lessons have proven. Every numbered directory is a self-contained, runnable lesson. A learner can read one lesson without having read any other — that is the central design contract.
💡 Real-world analogy
A driving instructor's lesson plan: the first lesson teaches turning the wheel without ever explaining gear ratios. The fifth lesson explains the clutch without re-deriving inertia. Each lesson assumes you remember the previous; none assumes you've read the engineering textbook. The textbook (src/) is on the back shelf for the curious; the lessons (examples/) are the road.
🎯 Why This Module Exists
The lessons exist because RAG appears intimidating in the LangChain era but stops being intimidating once you have written the keyword-scorer, the splitter, the embedding cache, and the rank-fusion equation yourself. The author's wager is that direct hands-on contact with each primitive removes the magic; the curriculum is built around that wager.
The lessons are not a thin demo of the src/ library. They are the source of truth for what should eventually live in src/. Lesson 03 re-implements its own splitter; lesson 06/04 ships a multi-query-retrieval.js helper that has not yet been promoted. Reading the lessons in order, you watch the abstractions emerge.
🏗️ Design Decisions
| Decision | Choice Made | Alternatives | Why |
|---|---|---|---|
| Lesson granularity | Numbered 00–06 with sub-numbering for complex topics | Flat 1..N; tagged by topic | Sub-numbering signals "this concept needs multiple passes" without dirtying the top-level. |
| Code/concept split | Three files per lesson (example.js + CONCEPT.md + CODE.md) | Markdown with embedded code; comments only | Lets readers choose their depth: skim concept → read code → run example. |
| First lesson | 00 does keyword scoring with no embeddings | Start with embeddings | Demystifies RAG before introducing vector math. Reader sees retrieve+generate working without any "AI". |
| Self-contained vs imports | Each example.js imports only Document/PDFLoader + helpers | Shared scaffold across lessons | Cognitive locality — the lesson must be readable in one tab without chasing imports. |
| Vector DB | embedded-vector-db (HNSW, in-process) | Qdrant/Chroma/PG-vector (network) | Local-first; no Docker required. Matches "no black boxes". |
| Embedding model | bge-small-en-v1.5 (384-d) | OpenAI text-embedding-3-small | Local, no API keys, runs on CPU. |
| Retrieval lessons | 5 sub-lessons (basic → query-prep → hybrid → multi-query → query-rewriting) | One mega-lesson | Each sub-lesson teaches one technique in isolation, then the next layers complexity. |
Lesson Architecture
flowchart LR
subgraph Foundations["Foundations"]
L0["00
How RAG works
(no embeddings)"]
L1A["01/01
node-llama-cpp basics"]
L1B["01/02
LLM wrapper"]
end
subgraph DataPrep["Data prep"]
L2["02
Data Loading"]
L3["03
Splitting + chunking"]
end
subgraph Vectors["Vectors"]
L4A["04/01
Similarity basics"]
L4B["04/02
Generate embeddings"]
end
subgraph Storage["Storage"]
L5A["05/01
In-memory store"]
L5B["05/02
NN search (HNSW)"]
L5C["05/03
Metadata filtering"]
end
subgraph Retrieval["Retrieval strategies"]
L6A["06/01
Basic retrieval"]
L6B["06/02
Query preprocess"]
L6C["06/03
Hybrid (dense + BM25)"]
L6D["06/04
Multi-query + RRF"]
L6E["06/05
Query rewriting"]
end
L0 --> L1A --> L1B --> L2 --> L3 --> L4A --> L4B
L4B --> L5A --> L5B --> L5C
L5C --> L6A --> L6B --> L6C --> L6D --> L6E
A Lesson's Anatomy
sequenceDiagram
participant R as Reader
participant C as CONCEPT.md
participant E as example.js
participant W as CODE.md
participant Out as Terminal
R->>C: read concepts (the WHY)
Note over R,C: 5-min read; sets mental model
R->>E: open example.js
R->>W: alongside CODE.md (line walk)
Note over R,W: dual-pane reading: code + commentary
R->>E: node example.js
E->>Out: chalk-formatted output
via OutputHelper
Out->>R: visible structure
(headers, spinners, stats)
R->>R: tweak config, re-run
R->>R: graduate to next lesson
🚶 Step-by-Step Narrated Walkthrough
The reader's path from "what is RAG?" to "I can build my own retrieval strategy":
- Lesson 00 (How RAG Works) — 68 LOC of pure JS. Knowledge is an array of strings.
naiveKeywordSearch()scores each doc by word overlap.generateAnswer()concatenates retrieved docs (no LLM). The reader sees retrieve+generate work; the failures (e.g., "What is the weather today?") motivate every later lesson. - Lessons 01/01 + 01/02 (LLM basics) — loads a GGUF model with node-llama-cpp; wraps it in a class. The reader learns that "an LLM" is just a function from string to string.
- Lesson 02 (Data Loading) — uses
PDFLoaderfromsrc/(already promoted). Demonstrates page-split vs single-document modes. - Lesson 03 (Splitting) — re-implements
TextSplitterinline (cognitive locality!). Compares character-window, sentence-aware, and recursive strategies on the same PDF. - Lesson 04/01 (Similarity basics) — embeds 5 sample sentences, computes pairwise cosine similarity, prints a heatmap. Reader sees "these two sentences are 0.82 similar".
- Lesson 04/02 (Generate embeddings) — scales up to a real document set. Introduces caching as a side-channel.
- Lesson 05/01 (In-memory store) — constructs a
VectorDBfromembedded-vector-db. CRUD operations, similarity search, metadata storage. - Lesson 05/02 (Nearest-neighbour search) — introduces HNSW parameters (
ef_construction,ef_search,M). Shows the speed/precision knob. - Lesson 05/03 (Metadata filtering) —
whereclauses on metadata; pre-filter vs post-filter tradeoffs. - Lesson 06/01 (Basic retrieval) — the canonical retrieve-then-generate loop end to end.
- Lesson 06/02 (Query preprocessing) — lowercase, strip stop-words, expand contractions. Heuristic-only; no LLM yet.
- Lesson 06/03 (Hybrid search) — combine dense (cosine) + sparse (BM25) scores. Min-max normalisation. Dynamic weighting based on query patterns (regex SKU detection bumps sparse weight).
- Lesson 06/04 (Multi-query) —
retrieveParallel(queries) → rrfFuse(lists). Reciprocal Rank Fusion atk=60. Promotion candidate forsrc/retrievers/. - Lesson 06/05 (Query rewriting) — LLM rewrites the user query into 1–3 paraphrases; feed to multi-query.
Lesson Catalogue
| # | Topic | Path | LOC | What's Taught |
|---|---|---|---|---|
| 00 | How RAG works | 00_how_rag_works/ | 68 | Retrieve+generate concept with naive keyword overlap. Sets up motivation for embeddings. |
| 01/01 | node-llama-cpp basics | 01_intro_to_llms/01_node_llama_cpp_basics/ | 83 | Load a GGUF, prompt it, dispose. Just enough llama.cpp. |
| 01/02 | LLM wrapper | 01_intro_to_llms/02_building_llm_wrapper/ | 141 | Wrap llama.cpp in a class with retry, timeout, options. Foreshadows src/llms/LlamaCpp. |
| 02 | Data loading | 02_data_loading/ | 98 | PDF parsing via PDFLoader from src/. splitPages tradeoff. |
| 03 | Text splitting + chunking | 03_text_splitting_and_chunking/ | 355 | Inline splitter implementation; comparison of strategies on the same source PDF. |
| 04/01 | Text similarity basics | 04_intro_to_embeddings/01_text_similarity_basics/ | 321 | Cosine similarity intuition with bge-small embeddings; heatmap of pairwise scores. |
| 04/02 | Generate embeddings | 04_intro_to_embeddings/02_generate_embeddings/ | 563 | Batch embedding, cache, persistence; scales to real documents. |
| 05/01 | In-memory vector store | 05_building_vector_store/01_in_memory_store/ | 559 | VectorDB CRUD, similarity search, metadata storage. |
| 05/02 | Nearest-neighbour search | 05_building_vector_store/02_nearest_neighbor_search/ | 677 | HNSW deep-dive; ef_search sweep; speed vs recall. |
| 05/03 | Metadata filtering | 05_building_vector_store/03_metadata_filtering/ | 737 | Pre-filter vs post-filter; where clauses; large catalogue. |
| 06/01 | Basic retrieval | 06_retrieval_strategies/01_basic_retrieval/ | 605 | Canonical retrieve-then-generate loop. Includes a showcase.js demo. |
| 06/02 | Query preprocessing | 06_retrieval_strategies/02_query_preprocessing/ | 736 | Heuristic normalisation; stop-word removal; contractions. |
| 06/03 | Hybrid search | 06_retrieval_strategies/03_hybrid_search/ | 805 | Dense + sparse with min-max normalisation; dynamic weights for SKU/brand queries. |
| 06/04 | Multi-query + RRF | 06_retrieval_strategies/04_multi_query_retrieval/ | 943 | Parallel retrieval, RRF, dedup. Helper file multi-query-retrieval.js. |
| 06/05 | Query rewriting | 06_retrieval_strategies/05_query_rewriting/ | 263 | LLM-driven paraphrase generation; pipes into 06/04. |
⚡ Algorithm Spotlight
| Algorithm | Lesson | Why This One | Complexity |
|---|---|---|---|
Naive keyword retrieval — matches = queryWords.filter(w => docWords.includes(w)).length | 00 | Pedagogical only. Showing "this works for some queries, fails for others" sets up the motivation for embeddings. | O(N·Q) |
HNSW kNN search via embedded-vector-db | 05/02 | Logarithmic-ish lookup vs O(n) brute force. For 10K vectors gives sub-ms search; for 100K still <10ms. | ~O(log n) |
Reciprocal Rank Fusion — score(d) = Σ 1/(k + rank + 1) | 06/04 | Similarity scores from different queries aren't directly comparable. Rank-based fusion sidesteps the calibration problem entirely. | O(N K) |
Deep-Dive Highlights
Lesson 00 — How RAG Works Foundation
Pure-JS keyword search. The 68-line lesson that demystifies RAG before introducing any vector math.
06 — Retrieval Strategies Advanced
Five sub-lessons: basic, query preprocess, hybrid (dense+BM25), multi-query+RRF, LLM query rewriting. The mature retrieval pipeline.
⚠ What Could Go Wrong
| Failure | Detection | Recovery | Cascading Effects |
|---|---|---|---|
Missing GGUF in models/ | node-llama-cpp throws on loadModel | Reader follows DOWNLOAD.md | Lessons 01+ all fail; 00 still works (no model needed). |
| Network unreachable for PDF download | pdf-parse fails on URL fetch | Lesson scripts log error; reader can substitute a local PDF | Lessons 02, 03 fail. |
| VectorDB capacity exceeded | embedded-vector-db raises insert error | Reader bumps MAX_ELEMENTS | Lessons 05, 06 fail to insert beyond 10K docs by default. |
| OPENAI_API_KEY unset | OpenAIClient constructor throws | Optional — only used in 06/05 fallback | Reader can use LlamaPrompter instead. |
📊 Performance Profile
The lessons are interactive demos — cold-start dominates everything. Loading bge-small-en-v1.5.Q8_0.gguf (~33 MB) takes ~500 ms; loading a 1B-parameter generation model takes 1–3 s. Once warm, embedding throughput is ~30 docs/s on a modern CPU; HNSW search over 10K vectors is sub-ms; LLM generation runs at 5–30 tok/s depending on the model and quantisation.
The lessons trade speed for clarity. OutputHelper.withSpinner wraps long ops; OutputHelper.runExample reports total elapsed; chalk/ora ensure the reader sees what's happening. None of this matters for production performance — it matters for educational pacing.
Directory Map
examples/ source tree
examples/
├── 00_how_rag_works/ ← 68 LOC, no embeddings, pure JS
│ ├── example.js CONCEPT.md CODE.md
├── 01_intro_to_llms/
│ ├── 01_node_llama_cpp_basics/ ← 83 LOC
│ └── 02_building_llm_wrapper/ ← 141 LOC
├── 02_data_loading/ ← 98 LOC, uses src/loaders/PDFLoader
├── 03_text_splitting_and_chunking/ ← 355 LOC, inline splitter
├── 04_intro_to_embeddings/
│ ├── 01_text_similarity_basics/ ← 321 LOC
│ └── 02_generate_embeddings/ ← 563 LOC
├── 05_building_vector_store/
│ ├── 01_in_memory_store/ ← 559 LOC
│ ├── 02_nearest_neighbor_search/ ← 677 LOC
│ └── 03_metadata_filtering/ ← 737 LOC
└── 06_retrieval_strategies/
├── 01_basic_retrieval/ ← 605 LOC + showcase.js
├── 02_query_preprocessing/ ← 736 LOC
├── 03_hybrid_search/ ← 805 LOC
├── 04_multi_query_retrieval/ ← 943 LOC + multi-query-retrieval.js + config.js + logger.js
└── 05_query_rewriting/ ← 263 LOC + query-rewriter.js + config.js
Lesson-Level Knobs
| Lesson | Knob | Default | Effect |
|---|---|---|---|
| 00 | topK in naiveKeywordSearch | 2 | Number of returned docs. |
| 03 | chunkSize | 1000 | Max chunk length. |
| 03 | chunkOverlap | 200 | Overlap between adjacent chunks. |
| 04+ | EMBEDDING_MODEL_PATH | bge-small.Q8_0 | Embedding model. |
| 05+ | DIM | 384 | Vector dimensions; must match model. |
| 05+ | MAX_ELEMENTS | 10000 | Vector DB capacity. |
| 05+ | NS namespace | per lesson | VectorDB namespace. |
| 05/02 | ef_search | varies | HNSW search-time precision/speed knob. |
| 06/01 | topK | 5 | Retrieval cardinality before context injection. |
| 06/03 | dense vs sparse weight | 0.5/0.5 | Hybrid blend; auto-tuned for SKU-like queries. |
| 06/04 | k in RRF | 60 | RRF damping constant; industry default. |
Lesson Cards
00 — How RAG Works
The 68-line foundation lesson. Pure JS, no embeddings, no LLM call. Read this first.
01 — LLM basics
node-llama-cpp basics + LLM wrapper. Loads a GGUF, prompts it, disposes.
02 — Data loading
PDF parsing via PDFLoader. splitPages tradeoff.
03 — Text splitting
Compares character-window, sentence-aware, and recursive splitters on the same PDF.
04 — Embeddings
Cosine similarity intuition; batch embedding with cache.
05 — Vector store
HNSW, CRUD, NN search, metadata filtering. Three sub-lessons.
06 — Retrieval strategies
Five sub-lessons culminating in multi-query + RRF + LLM rewriting.