All projects

AI & intelligence / PROJECT NOTES

Local RAG Over My Coursework

A fully local retrieval-augmented generation system over four years of BYU coursework — 24,778 chunks across 2,157 files, answered by an on-device LLM with citations.

01 / OVERVIEW

The idea behind it.

A private question-answering system built over my entire BYU course archive: lecture PDFs, textbooks, lab writeups, Obsidian notes, essays, and source code across 33 courses. Documents are parsed, chunked, and embedded with nomic-embed-text through Ollama, then stored in a NumPy vector index. At query time the system embeds the question, retrieves the most relevant passages using hybrid dense-plus-lexical scoring, and feeds them to a local qwen2.5-coder model that answers with inline citations back to the exact source files. Deliberately built without LangChain, FAISS, or PyTorch — the runtime depends only on NumPy and Ollama, which keeps it fast, portable, and fully offline. Nothing ever leaves the machine.

02 / FEATURES

What it does.

  • 01Indexes 2,157 files into 24,778 searchable chunks across 33 courses
  • 02Multi-format ingestion: PDF, DOCX, Markdown, plain text, and source code
  • 03Fully local inference — no API keys, no network calls, complete privacy
  • 04Hybrid retrieval combining cosine similarity with lexical overlap scoring
  • 05Inline citations mapping every claim back to the originating file and chunk
  • 06Course-scoped and file-type-scoped filtering of the search space
  • 07Incremental re-indexing that fingerprints files and skips unchanged ones
  • 08Interactive chat mode with conversation memory and runtime commands
  • 09Retrieval-only search mode for locating source material without invoking the LLM

03 / CHALLENGES

The problems worth solving.

  • 01Extracting usable text from 358 PDFs including scanned lecture slides and full textbooks
  • 02Malformed PDF math glyphs decoding to unpaired UTF-16 surrogates that crashed serialization after 20 minutes of embedding work
  • 03A garbage-collection stall where building tens of thousands of records with the full corpus resident sent the cyclic collector into repeated full passes, hanging the save indefinitely at 100% CPU
  • 04Duplicate files across the archive crowding out distinct sources in the retrieval results
  • 05Course-specific queries retrieving poorly because note bodies rarely name their own course
  • 06Building a workable stack on Python 3.14, where PyTorch and FAISS wheels were unavailable

04 / LEARNINGS

What I took away.

  • 01RAG architecture end to end: ingestion, chunking, embedding, retrieval, and generation
  • 02Why chunk overlap and paragraph-aware splitting matter for retrieval quality
  • 03Injecting document metadata into chunk text so embeddings capture context the body omits
  • 04Hybrid search — dense vectors miss exact identifiers like course codes that lexical matching catches
  • 05Cosine similarity over L2-normalized vectors reduces to a single matrix multiply
  • 06Profiling a hung Python process with native stack sampling to find a GC bottleneck
  • 07How dataclasses.asdict deep-copies records and becomes a scaling hazard at volume
  • 08Content-hash deduplication to prevent near-identical sources dominating top-k results
  • 09Choosing a dependency-light design when the runtime environment constrains the options

KEEP EXPLORING

Task-Specific LLM Distillation (7B → 1.5B)