KnowGraph – Rethinking the Way We Practice
While preparing for exams, I realized something important — it’s not about solving more questions, it’s about solving the right questions in the right order.
KnowGraph is a project born out of my own preparation journey. I often found myself spending too much time searching for quality problems instead of actually practicing them. Questions were either too easy, randomly arranged, or not aligned with exam-level difficulty.
So I decided to build a system that organizes preparation in a structured and meaningful way — not just a bank of questions, but something that understands why a question matters.
The Idea
Manually curating a balanced paper across hundreds of syllabus concepts is slow, and most question banks treat every topic as equally important. I wanted something smarter: a system that knows which concepts show up often in past papers, which ones haven’t been asked in a while, and which prerequisites need to be in place before a harder question makes sense.
This platform focuses on:
- Topic-wise structured question sets, generated fresh rather than pulled from a static bank
- Gradual, deliberate difficulty progression instead of random ordering
- A balanced mix of conceptual, numerical, and application-based problems
- Logical ranking of concepts based on how often and how recently they’ve appeared in past exams
Instead of overwhelming students with a large collection of random problems, the goal is to guide them step by step — strengthening fundamentals first, then moving toward advanced and exam-level questions.
How I Built It
The intelligence behind KnowGraph is a Graph-RAG loop. A Neo4j graph stores syllabus concepts and previous-year-question metadata — frequency, recency, and prerequisite relationships between concepts (REQUIRES, ASKED_IN, SIMILAR_TO). A LangGraph workflow uses that graph to decide which concepts actually deserve a question, an LLM (Groq’s Llama 3.3 70B) generates fresh questions around them, and a rule-based validator filters out anything low-quality or off-syllabus before it reaches the student.
The rest of the stack:
- Backend: FastAPI on Python, with Pydantic for request/response validation and typed settings
- Relational data: SQLAlchemy over PostgreSQL (with a SQLite fallback for local dev) for users, generated papers, and cached explanations
- Graph data: Neo4j for the concept/topic/question relationship graph
- Vector search: Pinecone, storing embedded textbook chunks (via a local
sentence-transformersmodel) so answers can be generated from real textbook context instead of the LLM guessing - PDF export: Jinja2 templates rendered to PDF with WeasyPrint
- Auth: JWT bearer tokens with bcrypt-hashed passwords
- Frontend: React with TypeScript and Vite
The generation workflow itself is a small state machine: fetch subjects → fetch topics → retrieve high-value concepts from the graph → build a paper blueprint → generate questions → validate them → retry failed ones → finalize the paper. The blueprint isn’t arbitrary either — it deliberately mixes concepts by weighting roughly half toward high-frequency topics, a third toward concepts with a recency gap (things that haven’t shown up in a while but are due), and the rest toward concepts that have never been asked before. That mix is what makes a generated paper feel like exam-level practice instead of a random quiz.
What the User Sees
From the outside, it’s simple: pick a subject, optionally narrow it down to specific topics, and get back a full exam paper. Behind the scenes, the system can also generate detailed, textbook-backed answers for any question, explain a question step-by-step with the underlying concept and formula, and export the whole paper to PDF.
Because generation is graph-driven, no two papers feel like copies of each other, and the system is built to actively avoid producing near-duplicates of past questions or of anything it has generated before.
Why This Design Worked
The biggest lesson was that a single LLM call is not reliable enough to build a product around. So generation is layered with fallbacks: try the full graph-driven pipeline first, fall back to per-topic generation, fall back further to cleaner reference questions pulled straight from the Neo4j pool, and if all else fails, fall back to templated questions — so the system never just returns nothing. Every generated question also gets checked against a rule-based validator (length, forbidden phrases, domain alignment) and compared against a running history to catch duplicates, before it’s shown to anyone.
Splitting storage across Neo4j and Pinecone instead of forcing everything into one database was also deliberate. The graph captures relationships — which concepts require which, how often something’s been asked — in a way that would need a lot of awkward self-joins in a relational database. Pinecone, on the other hand, is built specifically for the kind of high-dimensional similarity search that textbook-grounded answers need. Structured user and paper data still lives in a normal SQL database, because that’s what it’s good at. Using the right storage engine for each kind of data, rather than reaching for one database everywhere, made the whole system easier to reason about.
What I Learned
Building KnowGraph taught me that “AI-powered” only means something if the AI is grounded in real structure underneath it. The graph is what keeps the questions relevant and non-repetitive; the LLM is just the part that phrases them.
I also learned a lot about reliability engineering for LLM-backed systems specifically — validating LLM output instead of trusting it blindly, designing multi-stage fallbacks so a bad generation doesn’t become a bad user experience, and caching expensive LLM calls (like step-by-step explanations) by hashing the question, topic, and difficulty so repeat requests are instant instead of re-triggering a model call.
And, honestly, I learned some things the harder way — around configuration hygiene, environment secrets, and keeping API versioning consistent as a project grows past its first working version. Those aren’t the exciting parts of the build, but they’re the parts that matter once a project stops being a weekend prototype.
What I Would Add Next
If I keep extending this project, the next steps are clear:
- generalizing the validator so it isn’t tuned to just one subject, and can scale cleanly to new engineering branches
- moving question generation off the request/response path and into an async job queue, so heavier papers don’t block on synchronous LLM calls
- proper LaTeX/math rendering in the PDF export, rather than plain HTML
- structured logging and health checks across the graph, vector store, and LLM provider, so failures are visible instead of silently degrading quality
- tightening auth and rate limiting as more of the API surface goes live
Final Thought
This platform reflects a simple belief: good preparation is structured preparation.
And in many ways, I’m building the tool I wish I had when I started.