04RAG chat widgetClient work
AI Tutor
A chat assistant built for university course pages that answers students in Italian, and shows its sources.
- Role
- Lead developer, 90 of 95 commits
- Built
- February to July 2026
- Type
- Client work
- Where
- Deployed to University of Ferrara course pages (as of July 2026)
In short
AI Tutor is a retrieval-augmented chat widget built for the University of Ferrara's course pages, where it was deployed as of July 2026. Students ask in plain Italian; it finds passages in official course material and university pages, answers, and cites where each answer came from. A monthly job re-reads the university's sites so the answers stay current.
Students ask the same practical questions, and the answers are scattered across dozens of sites and PDFs.
A generic chatbot makes answers up; a static FAQ goes stale. The university needed answers grounded in its own pages, in Italian, that stay current as those pages change.
What it does
Answers with its sources
Every answer is written from retrieved passages and rendered with citations to the pages they came from.
Knows which course you're on
Each course's material is scoped to that course, and university-wide pages (fees, calls, services) are merged into every scope.
Keeps itself current
A monthly job re-reads every active course's sitemap and the university site, pages and PDFs, and re-embeds only what changed.
Italian by design
Prompt, interface and corpus are Italian only: a teaching choice that also shrinks the room for invented answers.
Lead developer
Built the retrieval pipeline, the backend and the monthly refresh job with its safety rules, and documented each architectural step as a decision record.
Two paths share one store: the question path, which serves students in real time, and the refresh path, which rebuilds the knowledge base once a month so the question path never has to scrape.
Two paths, one knowledge base
The answering path on top, the monthly refresh underneath.
What each part does
- Backend API
- A Fastify and TypeScript backend with validated configuration and rate limiting.
- RAG pipeline
- Retrieval-augmented generation: find the right passages, then answer from them only.
- Vector database: passages, sessions, courses
- One managed database holds the passages with their vectors, the chat sessions and the course registry.
- Firecrawl
- Firecrawl turns pages and linked PDFs into clean Markdown.
- Gemini
- Google Gemini, for both the embeddings and the answers, so the vector space stays consistent.
- Scrape-and-embed job
- Re-reads the sites and updates the knowledge base; it reuses the backend's own image.
- Monthly schedule
- Once a month.
- Course scope plus university-wide pages
- The current course's material plus the university-wide pages.
- Student on a course page
- A student on one of the university's course pages.
- Chat widget
- A React chat widget embedded in the page.
One question, answered with sources
What happens between a student pressing enter and the answer appearing.
1 of 12
A student asks a question in Italian.
Student → Chat widget: A question, in Italian
All 12 steps as text
What each part does
- Student
- The student.
- Chat widget
- The chat widget on the course page.
- Backend
- The backend.
- Vector database
- The managed database: passages, vectors and sessions.
- Gemini
- Google Gemini.
The monthly refresh, failure-safe
How the knowledge base stays current without ever losing what it had.
1 of 9
Once a month the refresh job starts.
Schedule → Refresh job: Monthly trigger
All 9 steps as text
What each part does
- Schedule
- The monthly schedule.
- Refresh job
- The refresh job.
- Firecrawl
- Firecrawl, for pages and PDFs.
- Gemini embeddings
- Gemini embeddings.
- Vector database
- The managed database.
Decisions and trade-offs
Three steps, each forced by a measured cost
An in-memory index cost about 50 seconds of cold start and embedding quota on every start; persisted embeddings keyed by content hash turned that into a load, and retrieval then moved to the database's managed vector search. Each step is an architecture decision record.
Failure can only keep things, never delete them
A site that fails to scrape keeps its previous corpus; cleanup touches only sites that scraped successfully, and the job still exits with an error so the failure is seen.
The wrong-address trap
A mistyped course address returns the site's root links instead of an error. The job treats that as a failure, not as an empty course.
One image, two jobs
The refresh job reuses the backend's container image with a different command, so there is no second artifact to drift.
One vendor for embeddings and answers
Gemini for both keeps the embedding space aligned and the keys simple; vector dimensions are configurable so a model migration is not a breaking change.
Results
- Monthlyself-refresh of its knowledge baseonly new or changed passages are embedded
- 90/95commits are hislead developer
- 4architecture decision records
- 21test filesbackend and frontend
Stack
- Backend
- TypeScript
- Node.js
- Fastify
- Zod
- Retrieval
- Managed vector search
- Gemini embeddings
- LangChain text splitters
- Firecrawl
- Frontend
- React 19
- Vite
- Redux Toolkit
- Tailwind CSS
- Delivery
- Docker
- Vitest