RAG assistant with citations, as code
A service you wrote yourself that answers questions from four pages of a business's own text, names the page it used, and says it cannot find the answer when the pages do not hold it.
What you'll have at the end
A GitHub repository with a TypeScript service, a Supabase table of embedded chunks, an answer endpoint that cites the page it used, a 30-question evaluation that prints a pass rate, and a public Vercel address where anyone you give the token to can ask.
What it proves
Accounts to create
What it costs: Free tiers on Supabase, Vercel and GitHub, plus a few cents of model usage for ingesting four pages and running the evaluation twice.
Have this ready
- Four pages of made-up text for one business: prices, opening hours, returns and delivery, about 400 words each
- A terminal with Node 20.6 or newer, git, and Claude Code installed
- A GitHub account where you may create a repository
- Your own Anthropic key and your own OpenAI key: Anthropic has no embeddings endpoint, so the vectors come from OpenAI while Claude writes the answers
How it fits together
- docs/ (four pages): made-up text for one business
- scripts/ingest.ts: splits each page into chunks
- OpenAI embeddings: one vector per chunk
- chunks table: Supabase with pgvector
- match_chunks: the four nearest chunks to the question
- Claude: answers from those chunks only
- Vercel: the public address and the page
The steps
1. The repository and the pages it will answer from
A repository holding the four pages you will answer from and the shape of the service around them. Nothing answers anything yet, but every file you will edit exists and you have read it.
2. The table that holds the vectors
One table and one search function. The table stores each chunk beside the numbers that represent its meaning, and the function returns the chunks nearest to a question.
3. Turning four pages into rows
Your pages inside the table, one row per chunk. This is the whole difference between a chatbot that guesses and one that reads: the text is in the database before anyone asks anything.
4. An answer that names its page
The part a client judges you on. The service finds the nearest chunks, answers from them alone, names the page it used, and admits when the pages do not hold the answer.
5. Thirty questions and a pass rate
One number that tells you whether a change made the service better or worse. Twenty questions your pages answer, ten they must refuse, and a script that decides without you reading anything.
6. Live on a public address
The same service on an address that exists without your laptop. From here a client can call it, and so can the page you build next.
7. A page a person can use
The last gap between a service and a deliverable. One page on the production address where somebody who has never opened a terminal asks a question and sees where the answer came from.