Almost every company that asks me for an AI chatbot actually wants the same thing: an assistant that can answer questions from their own documents without making things up. That is what retrieval augmented generation delivers, and knowing how to build a RAG chatbot properly is the difference between a demo that impresses a boardroom and a system your support team actually trusts on a Monday morning.
I have shipped this pattern across support docs, clinical guidance and internal wikis. This is the full pipeline I use, the trade-offs that matter, and the failure modes that quietly wreck a chatbot built for a website when nobody instruments retrieval.
What is retrieval augmented generation, in plain terms?
Retrieval augmented generation is a pattern where the model does not answer from memory. Instead, at query time you search a knowledge base for the passages most relevant to the question, paste those passages into the prompt, and ask the model to answer using only that supplied context. The LLM supplies fluency and reasoning; your documents supply the facts.
This separation is the whole point. The model's weights never see your private data, so you can update the knowledge base by editing a document rather than retraining anything. A policy change that would take a fine-tuning run to absorb becomes a one-line edit that is live on the next query.
Should you use RAG or fine-tuning for a knowledge base chatbot?
For a knowledge base chatbot, RAG wins almost every time. Fine-tuning teaches a model a style, a format or a narrow skill; it is a poor way to teach it facts, because facts change and retraining is slow and expensive. RAG injects fresh facts at inference time, which is exactly what a document-grounded assistant needs.
How the two approaches compare on the axes that decide a knowledge-base build:
| Dimension | RAG | Fine-tuning |
|---|---|---|
| Best at | Answering from a body of documents that changes | Teaching a fixed tone, format or narrow behaviour |
| Updating knowledge | Edit or re-embed a document, live immediately | Assemble a dataset and run another training job |
| Citations | Natural, because you know which chunk was retrieved | Hard, the model cannot point to a source |
| Upfront cost | Low, an embedding pass and a vector store | High, curated data plus GPU training time |
| Hallucination control | Strong, you can refuse when retrieval is empty | Weaker, the model still answers from parametric memory |
The two are not mutually exclusive. Fine-tune when you need the model to reliably speak in a house style or emit a strict format, and layer RAG on top for the facts. But if your only problem is "answer questions about our documents", start with RAG and do not touch fine-tuning until retrieval is measurably solid.
The full RAG pipeline, stage by stage
A production RAG chatbot is a pipeline of seven stages, and each one is a place quality can leak. Treat them as separately testable components, because when the bot answers badly you need to know whether retrieval missed or generation drifted.
The stages, in order:
- 1Ingest: pull documents from their sources, PDFs, HTML, Confluence, Notion, a database, and normalise them to clean text with metadata attached.
- 2Chunk: split each document into passages small enough to be precise but large enough to stand alone as an answer.
- 3Embed: turn each chunk into a vector with an embedding model, so semantic similarity becomes distance in vector space.
- 4Store: write the vectors, the original text and the metadata into a vector database like Qdrant or pgvector.
- 5Retrieve: at query time, embed the question and pull the top candidate chunks, ideally with hybrid semantic-plus-keyword search.
- 6Rerank: pass those candidates through a cross-encoder that scores true relevance and reorders them, keeping only the best few.
- 7Generate: hand the top chunks to the LLM with a prompt that demands citations and a refusal when the context does not support an answer.
Chunking: the decision that quietly determines your quality ceiling
Chunking is the most underrated stage. Chunk too large and you dilute the relevant sentence in a wall of irrelevant text, which wastes tokens and confuses the reranker. Chunk too small and you sever the context a passage needs to make sense, so a retrieved fragment answers half the question. Most of the bad RAG systems I am asked to fix have a chunking problem, not a model problem.
What actually works for chunking:
- Split on structure first, headings, sections and list items, so a chunk maps to a coherent idea rather than an arbitrary character count.
- Target roughly 300 to 600 tokens per chunk for prose, with a small overlap of 40 to 80 tokens so a sentence spanning a boundary is not lost.
- Attach metadata to every chunk: source document, section title, URL and last-updated date. You will filter and cite on these.
- For tables and code, keep the whole structure intact rather than splitting mid-row, because a half table retrieves as noise.
Embeddings and why hybrid search beats pure vectors
An embedding model maps text to a vector so that similar meaning lands close together. Pure vector search is excellent at semantics, it understands that "refund window" and "return period" are related, but it is surprisingly weak at exact matches: product codes, error strings, names and acronyms. Those are precisely the tokens users paste into a support chatbot.
The fix is hybrid search: run a keyword index such as BM25 alongside the vector search and fuse the two result sets, usually with reciprocal rank fusion. You get the semantic recall of embeddings and the exact-match precision of lexical search. Every serious knowledge base chatbot I have built uses hybrid retrieval, because pure vector search misses the SKU and pure keyword search misses the paraphrase.
Reranking: the cheapest quality win in the stack
Retrieval gives you a fast, approximate top 20. A reranker is a cross-encoder that reads the question and each candidate together and scores real relevance, then you keep the top 3 to 5. This second pass is slower per document, which is why you only run it on the shortlist, but it consistently lifts answer quality more than swapping to a bigger generation model. If you measure one thing after chunking, measure the effect of adding a reranker.
A minimal chunk, embed and retrieve example
Here is the core loop in Python: chunk a document, embed the chunks into Qdrant, then retrieve for a query. It uses a LangChain splitter for chunking and the Qdrant client directly so the moving parts stay visible.
from langchain_text_splitters import RecursiveCharacterTextSplitter
from openai import OpenAI
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, PointStruct, VectorParams
client = OpenAI()
qdrant = QdrantClient(url="http://localhost:6333")
EMBED_MODEL = "text-embedding-3-small" # 1536 dims
def embed(texts: list[str]) -> list[list[float]]:
resp = client.embeddings.create(model=EMBED_MODEL, input=texts)
return [d.embedding for d in resp.data]
# 1. Chunk: split on structure, keep a small overlap.
splitter = RecursiveCharacterTextSplitter(chunk_size=1800, chunk_overlap=200)
chunks = splitter.split_text(open("handbook.md").read())
# 2. Embed + store, carrying the source text as payload for citation.
qdrant.recreate_collection(
collection_name="kb",
vectors_config=VectorParams(size=1536, distance=Distance.COSINE),
)
vectors = embed(chunks)
qdrant.upsert(
collection_name="kb",
points=[
PointStruct(id=i, vector=v, payload={"text": c, "source": "handbook.md"})
for i, (c, v) in enumerate(zip(chunks, vectors))
],
)
# 3. Retrieve: embed the question, pull the closest chunks.
query = "How many days do I have to expense a client dinner?"
hits = qdrant.search(
collection_name="kb", query_vector=embed([query])[0], limit=5
)
for h in hits:
print(round(h.score, 3), h.payload["source"], h.payload["text"][:80])In production you would add a BM25 index for hybrid search, a reranking pass over these hits, and batching for the embedding calls. But this is the honest skeleton, and it runs. The choice of LangChain versus LlamaIndex for the orchestration layer matters less than getting chunking and retrieval right underneath it.
Which vector database should you use?
The vector database stores your embeddings and serves nearest-neighbour queries. The honest answer for most teams is: if you are already on Postgres, start with pgvector and only move to a dedicated engine when scale or latency forces it.
The options I actually reach for, and when each fits:
| Option | Strengths | Reach for it when |
|---|---|---|
| pgvector on Postgres | Lives beside your relational data, transactional, no new service | You already run Postgres and want one system of record |
| Qdrant | Fast filtered search, native hybrid, easy self-hosting | You need scale and rich metadata filtering, self or cloud hosted |
| Weaviate | Built-in hybrid search and modules, GraphQL API | You want batteries-included hybrid retrieval out of the box |
| Pinecone | Fully managed, no ops, predictable scaling | You want zero infrastructure work and will pay for it |
I lean towards pgvector on Supabase for early builds because it keeps chunks, metadata and application data in one database you can back up and reason about, and towards Qdrant when metadata filtering and hybrid search at volume become the priority. Do not agonise over this choice up front; the abstraction is thin and migrating an embedding store is a day of work, not a rewrite.
How do you stop a RAG chatbot from hallucinating?
You control hallucination in a RAG chatbot with grounding and refusal, not by hoping the model behaves. Grounding means the answer must be traceable to retrieved chunks, and refusal means the bot says "I could not find that" when retrieval comes back thin rather than inventing a plausible paragraph.
The controls that actually move the hallucination rate:
- Instruct the model to answer only from the provided context and to cite the chunk ID behind every claim, so an uncited sentence is a visible red flag.
- Add a refusal path: if the top reranked score is below a threshold, return a graceful "not found" instead of generating.
- Return citations to the user, linking back to the source document, so a wrong answer is falsifiable in one click.
- Constrain the output to a schema so citations are a required field, not an optional afterthought the model can skip.
A schema-constrained answer makes grounding enforceable. Here is the shape I use, where an answer is invalid unless it carries the chunk IDs it was built from.
from pydantic import BaseModel, Field
class Citation(BaseModel):
chunk_id: int
source: str
quote: str = Field(description="The exact sentence supporting the claim.")
class GroundedAnswer(BaseModel):
answer: str
citations: list[Citation] = Field(
description="Every factual claim must map to at least one citation."
)
answered: bool = Field(
description="False when retrieved context does not support an answer."
)
# If answered is False, show the refusal message, not the model's guess.
# If citations is empty but answered is True, reject and retry: no source, no answer."A RAG answer with no citation is not an answer, it is a confident guess wearing your logo."
How do you evaluate a RAG chatbot before trusting it?
You evaluate retrieval and generation separately, because a bad answer can come from either. Retrieval evaluation asks whether the right chunk was in the results at all; generation evaluation asks whether the model used the retrieved context faithfully. Conflating the two is how teams spend a week tuning prompts when the real problem was chunking.
The baseline metrics worth tracking from day one:
- Retrieval recall at k: for a set of known questions, was the correct chunk in the top k retrieved. This is your retrieval ceiling.
- Faithfulness: does every claim in the answer trace to the retrieved context, or did the model add unsupported detail.
- Answer relevance: does the response actually address the question asked, not just quote a nearby passage.
- Refusal correctness: on questions your docs genuinely cannot answer, does the bot decline instead of confabulating.
Build a golden set of 50 to 100 real questions with known good answers before launch, and rerun it on every change to chunking, retrieval or prompts. I go deeper on the tooling and metrics in how to evaluate RAG systems; the short version is that without an eval set you are tuning blind, and every improvement is a guess.
What does a RAG chatbot cost to run?
Running costs split into three lines: a one-time embedding pass over the corpus, ongoing embedding of queries, and generation tokens per answer. For most internal knowledge bases the generation tokens dominate, and the embedding of the entire corpus is a rounding error you pay once.
The lever that actually controls the bill is how much context you stuff into each generation call. Retrieving and pasting 20 chunks when 4 would do triples your token cost per answer and, counterintuitively, often lowers quality by burying the relevant passage. This is exactly why the reranker earns its place: it lets you send fewer, better chunks. Model prices move constantly, so size a proof of concept against current published rates rather than a number from a blog post, mine included.
A realistic build timeline
A focused internal RAG chatbot is a two-to-four-week build, not a research project, provided the documents are accessible and reasonably clean. The slow part is almost never the model; it is ingestion and evaluation.
How the weeks tend to break down:
- 1Week one: ingestion and chunking. Getting messy PDFs, exports and wiki pages into clean, well-chunked text with metadata is the real work and the part that determines your ceiling.
- 2Week two: retrieval and reranking. Stand up the vector store, add hybrid search and a reranker, and build the golden question set to measure recall.
- 3Week three: generation, grounding and citations. Wire the prompt, the refusal path and the citation schema, then tune against the eval set.
- 4Week four: hardening. Access control per document, logging, latency work, and a review of the questions the bot still gets wrong.
In a regulated setting the timeline stretches, mostly for access control and audit, not for the RAG core. When I built the assistant layer for SynthiCare NHS, grounding and citation were non-negotiable from day one, because a clinical answer a user cannot trace to a source is worse than no answer at all. That constraint is a good default even when nobody is forcing it on you.
If you want this built and evaluated properly rather than demoed and abandoned, that is the scope I run as an AI services engagement: ingestion, hybrid retrieval, reranking, grounded generation with citations, and an eval harness so you can prove it works before it faces real users.
Key takeaways
- RAG grounds an LLM in your own documents at query time, which is why it beats fine-tuning for most knowledge-base chatbots.
- The pipeline is ingest, chunk, embed, store, retrieve, rerank and generate, and each stage is a separately testable place quality can leak.
- Chunking quietly sets your quality ceiling; split on structure and attach metadata to every chunk.
- Hybrid search plus a reranker fixes the retrieval failures behind most hallucinations far more cheaply than a bigger model.
- Enforce grounding with citations and a refusal path, ideally through a schema that makes citations a required field.
- Evaluate retrieval and generation separately against a golden question set, and build one before you launch.
Frequently asked questions
- How do I build a RAG chatbot on my own documents?
- Ingest and clean your documents, split them into structure-aware chunks, embed each chunk and store the vectors in a database like Qdrant or pgvector. At query time, retrieve relevant chunks with hybrid search, rerank them, then have the LLM answer using only that context with inline citations back to the source.
- Is RAG better than fine-tuning for a knowledge base chatbot?
- For answering questions from documents, yes. RAG injects fresh facts at query time, so updating knowledge means editing a document rather than retraining. Fine-tuning is better for teaching a fixed tone or output format, not for facts that change. Many production systems fine-tune for style and use RAG for the underlying facts.
- What is the best vector database for a RAG chatbot?
- There is no single best; it depends on your stack. If you already run Postgres, start with pgvector to keep chunks and application data in one place. Choose Qdrant or Weaviate for fast filtered hybrid search at scale, or Pinecone if you want a fully managed service and will pay to avoid operations work.
- Why does my RAG chatbot still hallucinate?
- Almost always because retrieval failed, not because the model is broken. If the right chunk never reaches the prompt, the model fills the gap by guessing. Add hybrid search and a reranker to fix recall, then instruct the model to cite chunk IDs and refuse when the top retrieval score is below a threshold.
- How big should my chunks be for RAG?
- For prose, target roughly 300 to 600 tokens per chunk with a small overlap of 40 to 80 tokens, and split on structure like headings and sections rather than fixed character counts. Keep tables and code blocks intact. Too large dilutes relevance; too small severs the context a passage needs to answer on its own.
- Do I need LangChain to build a RAG chatbot?
- No. LangChain and LlamaIndex speed up orchestration with ready-made splitters, retrievers and chains, but you can build a solid RAG pipeline with just an embedding API and a vector database client. The framework choice matters far less than getting chunking, hybrid retrieval and reranking right underneath it.
- How do I evaluate whether my RAG chatbot is accurate?
- Evaluate retrieval and generation separately. Measure retrieval recall at k against a golden set of real questions with known answers, then measure faithfulness and answer relevance on the generated responses. Also test refusal on questions your documents cannot answer. Rerun the whole set on every change to chunking, retrieval or prompts.
- How long does it take to build a RAG chatbot?
- A focused internal RAG chatbot is typically two to four weeks of engineering when documents are accessible and reasonably clean. Ingestion and chunking usually take the first week, retrieval and reranking the second, grounded generation the third, and hardening the fourth. Regulated settings extend the timeline for access control and audit, not the RAG core.
Sources
Written by Syed Husnain Haider Bukhari
AI engineer, data scientist, and founder of Revolutionary Technologies LLC. Ships production AI agents, automations, and data platforms for teams in the US, UK, and UAE — including AgentFlow, AI Walay, and ProLeads.
Get in touch →Related pages
Want this built instead of researched?
I build these systems for teams in the US, UK, and UAE. Book a free 30-minute consultation and you get a one-page plan and a fixed-scope quote within 48 hours — or message me directly, whichever is faster for you.
Prefer a form? Send a project brief →