Built a RAG assistant you can interrogate right now
The case in 30 seconds
- Problem
- Anyone can claim on a portfolio that they ship AI in production. Nothing on a page proves it. I wanted something a visitor could test in the browser instead of taking my word for it.
- What I did
- Built a retrieval-augmented generation (RAG) pipeline over documents I wrote and checked. A Cloudflare Worker verifies the visitor and forwards the question to an n8n workflow. Six fixed stages run in one direction, then one of five outcomes.
- Impact
- A full answer lands in 17.2s, at €0 infrastructure cost. One database index moved capacity 170×, and no invented link has reached a visitor.
Try it
Ask Ava AI a question about my work.
Pick one and it goes to the live assistant, which answers from the case studies on this site.
Two or three questions is enough to see how it handles what it does not know.
How it happened
- What it is
It runs on the same infrastructure as the rest of the site. The corpus is twelve documents: nine case studies plus my background, my working method and my career history. I wrote or reviewed all of it. Anything confidential never goes in, which holds better than telling a model to keep quiet.

The whole thing on a phone. The four opening questions are the ones this page offers, and they go to the live pipeline. - The architecture
A Cloudflare Worker verifies the visitor, checks the input, and forwards the question to an n8n workflow. The routes are fixed: a directed acyclic graph (DAG), no loops, no tool selection at runtime. It is not agentic. Every question ends in one of five outcomes: an answer, a deflection, a redirect, a refusal, or an error.
- Six stages, one measurable path
- Input guardrail — is this question safe and in scope?
- Intent classifier — what is being asked, in what language, and which route does it take?
- Query transformer — rewrite the question into something worth searching with
- Retrieval — vector search over the corpus in Supabase pgvector
- Answer generator — write the answer using only the passages that came back
- Output guardrail — read the draft before it ships

The six stages as built. Every arrow runs left to right, and the outcome branches leave the router without ever returning to it. - Decisions worth explaining
- Verification once per session, not once per question
My first version verified each question separately, and it broke intermittently. Verification tokens are single-use and expire in minutes. Whether the second question worked depended on how long the visitor spent reading the first answer. I replaced it with a signed, time-limited clearance issued after one check. That single change cleared four bugs I had been treating as unrelated.
- Progress by polling, not streaming
The pipeline emits about six events over seventeen seconds. Streaming would deliver each one a few hundred milliseconds sooner, which nobody can perceive. The elapsed counter animates in the browser anyway. Polling costs one endpoint and leaves no long-lived connections. Nothing to leak, to be suspended on mobile, or to be buffered by a corporate proxy. It was the duller option and the right one.
- Source links resolved from the retrieved records
Early on, the model wrote out the titles and links it was citing. It eventually invented a plausible-looking one that led to a dead page. Now the model returns document identifiers. The system looks up the title and link from the records it retrieved. An identifier it invents matches nothing and gets dropped. A second check validates every link against the real routes before it reaches anyone.
- The question decides the language
Someone reading the English site who writes in French gets a French answer. The site’s language setting only breaks ties on questions too short to classify.

One question end to end in Langfuse: eight spans, 13.35s on this run, and writing the answer accounting for 6.06s of it. This is where the failures below were found. - What went wrong
- The first progress indicator was making its numbers up
It showed a duration for each stage. Those durations were client-side estimates, and they came out identical on every run whatever actually happened. It looked good, which is roughly the problem. I took the numbers out. Then I built the version that reports each stage as the pipeline finishes it, with durations that were measured.
- An optimisation quietly starved the model
I deduplicated retrieved passages by document, assuming repeated documents were redundant. They were different passages of the same article. Retrieval returned five, four were thrown away, and the model answered from one. It started opening answers by explaining what it could not tell me. That is what a well-behaved model does when you take its evidence away. The traces showed five retrieved and one kept.
- Multi-turn memory had never worked at all
Conversation turns were written with one set of field names and read back with another. The history was always empty. Nothing errored. Follow-up questions were simply worse than they should have been, for weeks, and I had no reason to look.
- A live API key sat in plaintext in an export file
I had built the node from a documentation snippet. It put the key in a node parameter as well as in the credential store. Everything worked, so nothing made me notice the duplicate. Then the parameter left the encrypted store inside an export. I rotated the key that day. In that tool a secret belongs in the credential and nowhere else.
Before

After

Before, the durations were client-side estimates that came out the same on every run. After, each one is the gap between two timestamps the pipeline recorded. - The blueprint that came first
Drawn before any code was written: one classifier at the hub, four routes off it, one shared backbone underneath. The classifier was built for four routes on day one, and they activate by phase. Ask the assistant for a route that is not built and it deflects rather than improvises.

The diagram is the day-one plan, and the phase badges name when each part was scheduled to ship. The lists below track what is running today. Live today
- Experience Q&A — the route this page demonstrates
- Fit Analysis — shipped in V1.7
- Meta, where it explains itself — shipped in V1.5
- Entry guardrail, classifier, shared backbone, output guardrail
- Knowledge layer and Langfuse tracing
Planned
- Point of View and its vetted corpus
- The evaluation harness
- Lead capture
- What it costs to run
The site, the API, the progress store and the vector database all sit inside free tiers. Model inference is the only real cost, and it sits under a monthly spend cap. One number moved capacity by two orders of magnitude. The progress store bills rows scanned rather than rows returned.

The account it all runs on: two products subscribed, both on a free plan, nothing else. The only line that costs anything is model usage, which is billed elsewhere. 290questions a day before the allowance ran outNo index: every progress poll scanned the whole table~50,000questions a day on the same allowanceWith the index: a poll reads six rows - What would change at enterprise scale
- Evaluation has to become systematic
Today I judge answers by reading them. That works for one person and stops working immediately after that. The next step is a set of graded reference questions, run as a regression suite. It includes adversarial ones, and ones the corpus deliberately cannot answer, so a prompt change has to prove itself.
- The corpus needs an owner
Twelve documents I wrote myself can be reviewed by eye. Ten thousand from a dozen teams need ownership, review and freshness rules. They also need a route for corrections. Retrieval quality is usually a content problem well before it becomes a model problem.
- Someone has to be watching the failures
Here, a failed question shows a retry button and I find out when I look. In an operational system, someone needs to know it failed and why. They also need to know whether it is happening more than yesterday. Tracing is the foundation for that; alerting and ownership are the rest of it.
- Cost becomes a design input
At this scale the free tier absorbs my mistakes. At enterprise volume, retrieval depth, context size and model choice all carry a number. Somebody has to be accountable for that number.
What I took from it
Building this took a few days. Knowing which corners are safe to cut came from the ten years before it. So did knowing what to measure, and which failures stay quiet.