How to Build a RAG Pipeline From Scratch: A Practical 2026 Guide
Retrieval-Augmented Generation (RAG) is the pattern that lets a language model answer questions about data it was never trained on. Instead of relying on the model's frozen parametric memory, you retrieve the most relevant snippets from your own corpus at query time and hand them to the model as context. The model then reasons over facts you control, which dramatically reduces hallucination and lets you cite sources. It is, quietly, the backbone of most production LLM applications shipping today: internal knowledge assistants, customer support bots, documentation search, and legal or medical copilots.
You can wire up a working demo in an afternoon with a framework. But the gap between a weekend demo and a system that survives real users is enormous, and that gap lives in details the frameworks hide from you: how you chunk, which embedding model you pick, whether you rerank, and how you measure quality. This guide walks the entire pipeline from scratch so you understand every moving part and know exactly which knob to turn when retrieval quality disappoints. That understanding is the difference between a RAG system you can debug and one you can only pray to.
What RAG Actually Is
At its core, RAG is a two-stage system. The retriever finds relevant context; the generator (the LLM) writes an answer grounded in that context. Everything else is plumbing that makes those two stages fast, accurate, and cheap.
The canonical flow at query time is:
- Convert the user's question into an embedding vector.
- Search a vector database for the nearest document chunks.
- Optionally rerank those candidates for precision.
- Stuff the top chunks into a prompt template alongside the question.
- Ask the LLM to answer using only the provided context.
The subtle part is that retrieval quality caps everything downstream. If the right chunk never makes it into the prompt, no amount of prompt engineering or model upgrading will save the answer. Garbage retrieval, garbage generation. Most teams over-invest in the generation prompt and under-invest in retrieval; the returns are almost always the other way around.
When RAG Beats Fine-Tuning
A recurring question from clients: should we fine-tune instead? The two solve different problems, and conflating them wastes budget.
- Use RAG when you need the model to know facts that change over time, when you need citations, or when your corpus is large and updates frequently. Adding a document is a one-line upsert, not a training run.
- Use fine-tuning when you need to change the model's behavior — tone, output format, a domain-specific reasoning style, or a niche classification task. Fine-tuning teaches skills, not facts.
- Use both when you want a model that reliably follows your format (fine-tuned) and grounds its claims in fresh data (RAG). This is common in mature deployments.
The practical rule: if the failure is "the model doesn't know X," reach for RAG. If the failure is "the model knows X but says it wrong," reach for fine-tuning.
The Pipeline End-to-End
A production RAG system has two paths that share components. The ingestion path runs offline and prepares your corpus. The query path runs online per request.
Ingestion looks like this:
- Load raw documents (PDFs, HTML, Markdown, database rows).
- Parse and clean — strip boilerplate, extract tables, preserve structure.
- Chunk documents into retrieval-sized units.
- Embed each chunk into a vector.
- Upsert vectors plus metadata into the vector store.
The query path then does embedding, retrieval, reranking, and generation. The rest of this article drills into the decisions that matter most, roughly in the order they bite you.
Chunking Strategies
Chunking is the single most underrated lever in RAG. Chunk too large and you dilute the embedding with irrelevant text and blow your context budget. Chunk too small and you shatter the meaning across fragments so no single chunk answers the question.
Here is how the common strategies trade off:
| Strategy | How it works | Best for | Downside |
|---|---|---|---|
| Fixed-size | Split every N tokens with overlap | Uniform prose, quick baselines | Cuts mid-sentence, ignores structure |
| Recursive | Split on paragraphs, then sentences, then chars | General documents | Needs tuning per corpus |
| Semantic | Split where embedding similarity drops | Dense, topic-shifting text | Slower, more compute at ingest |
| Document-aware | Split on headings, tables, code blocks | Structured docs, Markdown, code | Requires a real parser |
My default recommendation is recursive chunking at roughly 300–500 tokens with 10–15% overlap, then upgrade to document-aware splitting once you know your data has strong structure. Overlap matters: it prevents an answer that straddles a boundary from being lost.
A minimal recursive-style chunker:
def chunk_text(text: str, chunk_size: int = 400, overlap: int = 60) -> list[str]:
words = text.split()
chunks, start = [], 0
while start < len(words):
end = start + chunk_size
chunks.append(" ".join(words[start:end]))
start += chunk_size - overlap
return chunks
One rule that saves projects: store metadata with every chunk — source URI, section title, page number, and a timestamp. You will need it for filtering, citations, and cache invalidation later, and backfilling it is painful.
Choosing an Embedding Model
Your embedding model defines the geometry of your search space. Switching it later means re-embedding your entire corpus, so choose deliberately.
Decision factors, in priority order:
- Retrieval quality on data like yours. Check the MTEB retrieval leaderboard, but validate on your documents — leaderboard rank rarely survives contact with a specialized domain.
- Dimensionality. Higher dimensions can capture more nuance but cost more storage and slower search. Many modern models support Matryoshka embeddings, letting you truncate dimensions and trade a little accuracy for a lot of speed.
- Context window. If chunks run long, ensure the model accepts them without silent truncation.
- Hosted vs. self-hosted. API embeddings are simplest; open-weight models you host yourself win on cost at scale and on data-residency requirements.
- Multilingual needs. Do not assume an English-tuned model handles other languages gracefully.
A critical, easy-to-miss detail: you must embed queries and documents with the same model, and some models expect an asymmetric prompt prefix (for example, a distinct instruction for queries versus passages). Read the model card. Mismatched prefixes quietly tank recall.
Retrieval, Hybrid Search, and Reranking
Pure vector search (dense retrieval) is excellent at semantic matching but weak at exact terms — product SKUs, error codes, rare proper nouns. Keyword search (sparse retrieval, like BM25) is the opposite. Hybrid search fuses both and is the strongest default for most corpora.
A typical embed-and-upsert at ingest time, followed by a hybrid query:
from qdrant_client import QdrantClient, models
client = QdrantClient(url="http://localhost:6333")
# Ingest: embed chunks and upsert with metadata
vectors = embed_model.encode([c.text for c in chunks])
client.upsert(
collection_name="docs",
points=[
models.PointStruct(id=c.id, vector=v.tolist(), payload=c.metadata)
for c, v in zip(chunks, vectors)
],
)
# Query: retrieve top candidates for a question
q_vector = embed_model.encode(["How do I rotate an API key?"])[0]
hits = client.query_points(
collection_name="docs",
query=q_vector.tolist(),
limit=20,
with_payload=True,
).points
Notice we retrieve 20 candidates, not 5. That is deliberate. Dense retrieval is good at recall but noisy on precision, so we over-fetch and then rerank. A cross-encoder reranker reads the query and each candidate together and scores true relevance far more accurately than the bi-encoder embeddings used for the first-stage search.
The pattern is:
- Retrieve 20–50 candidates with fast hybrid search (high recall).
- Rerank them with a cross-encoder (high precision).
- Keep the top 3–5 for the prompt.
Reranking is often the highest-ROI single addition to a mediocre RAG system. It adds latency, but for most applications the accuracy gain is worth it, and you can cache aggressively. If you are choosing infrastructure for these stages, our guide on choosing a vector database compares the leading options on hybrid search, filtering, and cost.
Evaluation
You cannot improve what you cannot measure, and "it looks good in the demo" is not measurement. Evaluate the two stages separately, because they fail differently.
Retrieval metrics answer "did we fetch the right chunks?"
| Metric | What it measures | When to watch it |
|---|---|---|
| Recall@k | Fraction of relevant chunks in top k | Missing answers, incomplete responses |
| Precision@k | Fraction of top k that are relevant | Noisy context, distracted model |
| MRR | Rank of the first relevant chunk | Best chunk buried too low |
| nDCG | Ranking quality weighted by position | Overall retrieval ordering |
Answer metrics answer "was the response good?" The modern approach uses an LLM-as-judge to score dimensions like faithfulness (is every claim supported by the retrieved context?), answer relevance, and context relevance. Faithfulness is the one to guard hardest — it is your direct measure of hallucination.
Build a small golden dataset of 50–200 real question-and-answer pairs early. Run it on every change. Without a regression set, every "improvement" is a guess, and you will regress silently.
Common Failure Modes
Most RAG problems reduce to a handful of patterns:
- The answer isn't in any chunk. A retrieval or chunking problem. Check recall first, then revisit chunk size.
- The right chunk was retrieved but ranked low and fell outside the top k. Add reranking or increase k.
- The model ignores the context and answers from parametric memory. Tighten the prompt to instruct grounding, and instruct it to say "I don't know" when context is insufficient.
- Conflicting or stale chunks. Your corpus contains outdated versions. Add recency metadata and filter or down-weight old content.
- Lost in the middle. With many chunks, models attend less to the middle of the context. Keep the prompt tight — fewer, better chunks beat more, weaker ones.
- Exact-term misses. Dense-only search fails on codes and names. Add hybrid search.
Production Checklist
Before you call a RAG system production-ready, confirm:
- Ingestion is idempotent and re-runnable, with stable chunk IDs.
- Every chunk carries source metadata for citations and filtering.
- Query and document embeddings use the same model and correct prefixes.
- Hybrid search plus reranking is in place, with a tuned candidate count.
- Answers cite their sources and can say "I don't know."
- A golden eval set runs in CI on every change.
- Latency and cost per query are measured and budgeted.
- A re-embedding plan exists for when you upgrade the model.
- Sensitive data is access-controlled at the retrieval layer, not just the UI.
How Vector Pillar Can Help
Building a demo is easy; building a RAG system that stays accurate, fast, and cheap under real load is where teams get stuck. At Vector Pillar we design and ship production RAG systems end to end — from ingestion and chunking strategy to evaluation harnesses that keep quality from regressing. If your bottleneck is the storage and search layer, our vector database consulting helps you select, tune, and scale the right platform for your data and budget.
If you are ready to move from prototype to production, start a project with us and we will help you get retrieval right the first time.