This is a submission for the Hacktoberfest Weekend Challenge: Build for a Friend
NotesBuddy is a study assistant I built for my friend Alekh. His problem was simple: his notes were scattered across many different files, and finding one small concept took forever.
NotesBuddy fixes that. You drop your notes into a folder, ask a question in plain language, and get a short answer along with the exact passage of your notes it came from. If the notes don't contain the answer, it says so instead of guessing. A study tool that confidently makes things up is worse than no tool at all, so that behavior was non-negotiable.
Live demo: https://notebuddy-lnka.onrender.com/
This hosted version is a retrieval-only demo on sample notes, because the free hosting tier can't run an LLM. It shows the real search and the cited passages. The full version, with Gemma generating the answers, runs locally. (The free tier sleeps when idle, so the first load can take up to a minute.)
Run the full version yourself:
git clone https://github.com/KunalKushwaha1806/NoteBuddy
cd NoteBuddy
ollama pull gemma3:4b
python notesbuddy.py serve # then open http://localhost:8000
Put your own .txt / .md notes in ./notes first.
https://github.com/KunalKushwaha1806/NoteBuddy
The whole project is a single Python file with no dependencies beyond the standard library.
operating-systems.md#1).[file#n], and say plainly when the notes don't cover the question.
The heart of it is one function:
def ask_llm(question, hits):
ctx = "\n\n".join(f"[{l}] {t}" for l, t in hits)
sys_p = ("You are a study buddy. Answer ONLY from the notes below. Cite sources like [file#n]. "
"If the notes don't contain the answer, say so plainly. Keep it short and clear.")
body = {"model": MODEL, "stream": False, "messages": [
{"role": "system", "content": sys_p},
{"role": "user", "content": f"NOTES:\n{ctx}\n\nQUESTION: {question}"}]}
req = urllib.request.Request(f"{OLLAMA}/api/chat", json.dumps(body).encode(),
{"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=120) as r:
return json.load(r)["message"]["content"]
I gave Alekh NotesBuddy and asked him to try it on his own notes. His reaction:
"Honestly, NotesBuddy is exactly what I needed. I've got so many notes scattered across different files that finding one small concept takes forever. Now I can just ask a question and get the answer along with the exact part of my notes it came from.
And the fact that it tells me when something isn't in my notes instead of making up an answer is actually really useful. It feels like having a study assistant that works directly with my own material."
The two things he singled out, seeing the exact source passage and getting an honest "that's not in your notes," are the two design decisions I cared about most.
NOTESBUDDY_MODEL), so swapping Gemma for another Ollama model needs no code change. And because I control the prompt and the retrieval, I could build the "admit when you don't know" behavior directly into the pipeline.
What's next: swapping BM25 for local embeddings to handle paraphrased questions, and a quiz mode that turns notes into practice questions.
Best Use of Gemma: Gemma 3 runs locally via Ollama as the generation model that answers every question.