Imoran ai LogoImoran ai Logo

Building a RAG Chatbot That Won't Hallucinate (Next.js + Gemini)

How I built a RAG chatbot that answers only from uploaded documents and says "I don’t know" otherwise, using Next.js, Gemini, and Vercel Blob. The retrieval and prompt guardrails that keep it honest.

Published: June 27, 2026

Last updated: June 30, 2026

Building a RAG Chatbot That Won't Hallucinate (Next.js + Gemini)

Large language models will confidently make things up. For a chatbot answering questions about a company's HR policy, a confident wrong answer is worse than no answer at all. When I built Quick Onboard Doc, the entire job was getting a model to answer from a specific set of documents and to admit when the answer isn't there.

That problem has a standard solution, RAG, but the part that keeps it honest is mostly in how you wire it up. Here's how I did it with Next.js, Gemini, and Vercel Blob.

What RAG actually is

RAG stands for retrieval-augmented generation. Instead of asking the model to answer from its training, you first retrieve the most relevant pieces of your own documents, then hand those pieces to the model and ask it to answer using only them. The model becomes a reader and a writer, not a source of facts.

So the system has two jobs. Get the right chunks of text in front of the model, and constrain the model to stick to them.

Ingestion: turning documents into something searchable

When a user uploads a PDF, none of the useful work happens at chat time. It happens here:

  • Store the raw file. I put uploads in Vercel Blob and keep a record of them in Postgres.
  • Split the text into chunks. A whole document is too big to search well, so I break it into overlapping passages of a few hundred words.
  • Embed each chunk. An embedding model turns each passage into a vector, a list of numbers that captures its meaning. I store those vectors so I can search them later.

By the time someone asks a question, every document is already chunked and embedded and waiting.

Retrieval: finding the right passages

When a question comes in, I embed the question with the same model, then find the chunks whose vectors are closest to it. Closeness in vector space roughly means "talks about the same thing," so this pulls back the passages most likely to contain the answer, even when the wording is different from the question.

Those top chunks become the context for the next step. The model never sees the whole document library, just the handful of passages that matched.

Generation: the guardrail that prevents hallucination

This is where most RAG tutorials stop too early. Retrieving good context isn't enough. You also have to tell the model what to do when the context doesn't contain the answer.

The instruction I give Gemini is blunt:

const prompt = `You are an assistant that answers ONLY from the context below.
If the answer is not in the context, say "I don't have that in the documents."
Do not use outside knowledge. Quote the relevant part when you can.

Context:
${retrievedChunks.join("\n\n")}

Question: ${userQuestion}`

That last rule, say you don't know rather than guess, is the difference between a tool people trust and a liability. A chatbot that invents a parental-leave policy will get someone in trouble. One that says "I don't have that in the documents" just sends them to a human, which is the correct outcome.

Test it on the gaps, not the hits

When you check a RAG system, don't only ask questions you know are answered in the docs. Ask things that aren't there, and confirm it refuses instead of improvising. The failure you care about isn't a wrong fact. It's a made-up one delivered with confidence. Most of my testing was deliberately trying to bait it into answering questions the documents never covered.

Get retrieval and that one guardrail right, and the rest is interface. The model stops being a know-it-all and becomes something more useful: a careful reader of the only documents that matter.

Frequently asked questions

What is RAG?

Retrieval-augmented generation. Instead of answering from training data, the model is handed the most relevant chunks of your own documents and answers using only those.

How do you stop a RAG chatbot from hallucinating?

Two things: good retrieval so the right context is actually present, and a prompt that tells the model to say it doesn't know when the answer isn't in that context.

What stack did you use?

Next.js for the app and API routes, Gemini for the generation step, Vercel Blob for the uploaded files, and Postgres for the relational data.

How should I test it?

Ask questions the documents don't answer and confirm it refuses instead of improvising. The failure you care about is a made-up answer delivered with confidence.

Let's Work
Together

Hand Reaching

Have a project in mind? We'd love to hear about it. Let's create something great together!

PRODUCT DEVELOPERUI ENGINEERMVP DEVELOPMENTFRONTEND DEVELOPMENTPRODUCT DEVELOPERUI ENGINEERMVP DEVELOPMENTFRONTEND DEVELOPMENTPRODUCT DEVELOPERUI ENGINEERMVP DEVELOPMENTFRONTEND DEVELOPMENT