19

Rolo Tutor

AI teaching assistant for introductory Python at VU Amsterdam

Rolo Tutor landing page with VU branding, the Rolo mascot, and VU-email sign-in

Rolo Tutor is an AI teaching assistant built for Vrije Universiteit Amsterdam. It teaches introductory Python instead of handing out answers — the tagline we settled on was "Designed to Teach, not Answer." Students reach it outside class hours, either on the web or through the course Discord server, and we presented it at a VU educational-technology conference.

Teaching, not answering

Rolo Tutor answering how to reverse a list in Python with three methods and runnable examples

Asked how to reverse a list in Python, the tutor walks through three methods with runnable examples and closes with a short summary of which one to reach for, rather than dropping a single snippet. Every reply carries thumbs up / down controls, and the verdict goes straight to the database where the dashboard picks it up.

Architecture

Four services in one monorepo.

rolo-frontend — React 18 (CRA with CRACO path aliases) and React Router 6. Sign-in is Microsoft Entra ID / OAuth 2.0 via @azure/msal-browser and @azure/msal-react, restricted to @vu.nl and @student.vu.nl addresses. The chat renders with a typewriter effect and full Markdown + GFM through react-markdown and remark-gfm, so code blocks, tables and lists come out properly. Bootstrap 5, MUI and Emotion for the shell, Quicksand for type.

rolo-backend — a Node/Express API on Google Cloud Run.

  • POST /api/google-chat — Gemini 2.5 Flash via @google/generative-ai, replaying the user's full prior conversation as context so the tutor remembers the thread.
  • POST /api/chat — the earlier OpenAI Assistants implementation, behind the same request shape.
  • POST /api/cluster — topic clustering. A 3072-dimension embedding per student question, cached in a question_embeddings table, then k-means (k=5) via ml-kmeans with drill-down into sub-clusters. Around 200 questions cluster in about 8 seconds from cache.
  • POST /api/executeQuery — a parameterised query layer.

Storage is Google BigQuery: web conversations, Discord conversations, and the embedding cache.

rolo-discord — a Discord.js bot with slash commands, automatic threading per question, button interactions, and a sweep for unanswered questions. It writes to the same database, so web and Discord traffic sit side by side in the dashboard.

rolo-tutor — the original Flask prototype the Node stack grew out of: OpenAI Assistants API, syntax highlighting, MongoDB persistence, and an optional local Ollama backend.

The dashboard

Full Rolo Tutor developer dashboard including the topic-clustering bubble chart

The dashboard is the part I care most about, because it is what turns a chat log into something a course team can act on.

The feedback table lists every message with its source (Web App or Discord), the thumbs verdict and a timestamp — sortable, paginated, and exportable to CSV. Message Count Over Time and Traffic Over Time are Chart.js with chartjs-plugin-zoom, splitting web-app against Discord-bot volume and showing unique users per day.

Dashboard above the fold: feedback table with CSV export and both traffic charts

Topic clustering, two levels deep

The bubble chart groups questions by embedding similarity, with bubble area standing in for cluster size. Each cluster is labelled by a representative question, so hovering gives you the topic and its volume at a glance.

Level one bubble chart, tooltip reading Cluster How to implement security in web applications: 67 questions

Clicking a bubble asks what you want from it: read the questions, or go a level deeper.

Clicking a cluster offers View Questions or Go to Subclusters

View Questions lists everything in that cluster verbatim — the raw student phrasing, not a summary of it.

Modal listing the individual questions inside one cluster

Go to Subclusters re-runs k-means inside the cluster alone and redraws the chart against its members, so a broad topic breaks into the specific things students got stuck on. From there you can open questions per sub-cluster, or step back up.

Level two bubble chart of sub-clusters with a Back to Previous Level button

The point is that "what is the cohort actually stuck on this week" is a couple of clicks, not a query.

Delivery

The frontend (behind nginx) and the backend are both Dockerised, with CI/CD to GCP: a push to main builds and deploys both services to Cloud Run automatically.

My role

I built the front end end-to-end — auth, chat UI, feedback capture, and the whole dashboard — along with the Node backend endpoints behind it, the CI/CD pipeline, and the GCP deployment.

Empty chat state in Rolo Tutor

The live deployment sits behind VU single sign-on, so the screenshots here come from a local run.