Skip to content

All work

Lemma 4

Local AI Study Assistant

A study tool that turns lecture PDFs into summaries, flashcards, quizzes and sourced answers, using a language model that runs entirely on the student’s own machine.

Context
Solo project
Period
August 2026
Repository
abdeljalilsennaoui/local-ai-study-assistant(opens in a new tab)
The study assistant in a browser. A sidebar shows “Connected to Ollama”, the selected model and retrieval settings. The main panel has one lecture PDF loaded, a notice that about 13,277 characters of text were extracted, four study modes with Summary selected, and the start of a generated summary.
Figure 1. A lecture PDF loaded and summarised on the device. The interface says how much text was extracted and warns when a long document is truncated.

Overview

The assistant takes lecture PDFs or pasted notes and produces four things: a structured summary, flashcards, a multiple-choice quiz, and answers to questions about the material with the passages they were drawn from. The language model is served by Ollama on the same machine, so no document leaves it. There are no API keys, no cloud service, no database and no tracking.

I wrote the retrieval pipeline myself instead of using a framework — extraction, cleaning, chunking, ranking — so that I can explain and test every stage.

Problem

The tools students use to revise from lecture notes generally require uploading those notes to someone else’s server. For course material that is often not permitted, and it is not free at scale.

A model small enough to run on a laptop brings its own problems: a whole chapter does not fit in its context window, and its structured output is unreliable.

Goals

  • Keep every document on the user’s machine: process in memory, write nothing to disk, contact only the local model server.
  • Answer questions only from the uploaded material, and show the passages an answer came from.
  • Never show a broken result: validate what the model returns and fall back to something readable.
  • Make the whole pipeline testable without the interface and without a model running.

Technologies

ApplicationPython 3.11+, Streamlit, Ollama (local HTTP API), requests, pypdf, scikit-learn
Testing and CIpytest, ruff, GitHub Actions, Make

Architecture

The Streamlit file does presentation only. The logic lives in a package of small modules — document loading, text processing, retrieval, prompts, the Ollama client and response parsing — coordinated by a service layer.

For question answering, documents are split into overlapping chunks of about 1,500 characters that end on paragraph or sentence boundaries where possible. A TF-IDF matrix is built over the chunks, the question is scored against each one by cosine similarity, and only the top few are sent to the model with instructions to answer from them alone. If nothing matches, the “not found” answer is returned without calling the model at all.

  1. Presentation

    • app.pyStreamlit interface
  2. Ingestion

    • document_loaderPDF and pasted text, in memory
    • text_processingCleaning and chunking
  3. Orchestration

    • study_service
    • retrievalTF-IDF, cosine similarity
    • prompts
  4. Model

    • ollama_clientHTTP to localhost
    • response_parserJSON extraction and validation
Figure 2. The pipeline, from the repository’s architecture document. The local Ollama server is the only network endpoint the application contacts.

My responsibilities

This is a solo project. I designed it, wrote it and tested it.

  • The ingestion and retrieval pipeline: in-memory PDF extraction, text cleaning, overlapping chunking and TF-IDF ranking.
  • The Ollama HTTP client, with an exception hierarchy that turns each failure into a readable message.
  • Prompt construction and defensive parsing of the model’s JSON output.
  • The Streamlit interface, the test suite and the CI workflow.

Every commit in the repository is mine.

Engineering decisions

TF-IDF instead of embeddings

TF-IDF needs no model download, no vector database and no cost, and its scores can be inspected and debugged. What it gives up is semantic matching: it compares words, not meaning, so a question phrased in different vocabulary than the source retrieves worse. The interface and the documentation say so.

No framework for a four-step pipeline

Chunk, vectorise, rank, ground: four steps. Writing them directly keeps the dependency list short and means I can account for each one. The cost is a few convenience helpers I would otherwise have had for free.

An injected HTTP session

The Ollama client takes its requests.Session through the constructor. Tests pass in a fake that returns canned responses or raises the exact exception under test, so nothing is patched and no fake server runs. The service layer takes a client in the same way, which lets a test assert on the prompt that would have been sent.

Validate the model’s JSON, then fall back

Local models often wrap JSON in Markdown fences or put a sentence before it. The parser strips fences, slices from the first bracket to the last, then checks the shape: a quiz question needs exactly four non-empty choices and a correct answer that matches one of them. If that fails, the user sees the error and the raw response, not a broken quiz.

Uploaded documents are untrusted input

A PDF can contain text that reads like an instruction. Document text is wrapped in explicit delimiters and the system message declares everything inside to be reference material, never instructions. This reduces the risk; it does not remove it, and the documentation does not claim otherwise.

Testing and quality

Six test modules with 71 test functions. Every HTTP call is replaced by an injected fake, so the suite needs no Ollama, no network and no external PDF, and it runs in GitHub Actions together with the ruff linter on every push and pull request.

The tests cover the code around the model, not the model. There is no end-to-end test against a live model, because that would make the pipeline depend on Ollama being installed on the runner.

Challenges

A timeout reported as “not running”

In the requests library, ConnectTimeout inherits from both ConnectionError and Timeout. With the except blocks in the wrong order, every connect timeout was reported as “Ollama is not running”, which sends a user looking in the wrong place. The order is fixed and a test now pins it.

A chunking loop that must terminate

With overlap, the next chunk starts before the previous one ends, and a careless configuration could leave the cursor standing still. The next start is always at least one character further on, and an overlap as large as the chunk size is rejected up front. Both rules are covered by tests.

Results

MeasureResultSource
Study modes4: summary, flashcards, quiz, question answeringRepository documentation
Automated tests71 test functions in 6 modulesCounted in the repository
Continuous integrationLint and tests; latest run succeededGitHub Actions, checked 12 October 2026
Network endpoints contacted1, on localhostRepository documentation

Table 1. There are no benchmark or accuracy figures here because none were measured.

What this does not show

  • No OCR: scanned PDFs with no extractable text are rejected with a clear message.
  • Summary, flashcard and quiz modes use only the first 12,000 characters or so of a long document, and the interface says when that happens.
  • Output quality is bounded by whichever local model the user has installed.

End of case study