DEV Community

Cover image for Building a RAG System: How Retrieval, Embeddings, and LLMs Work Together
Synfinity Dynamics Pvt Ltd
Synfinity Dynamics Pvt Ltd

Posted on

Building a RAG System: How Retrieval, Embeddings, and LLMs Work Together

1. Why RAG Is More Than "Ask an LLM a Question"

When most people start building with LLMs, the mental model looks like this:

User Question
     ↓
LLM
     ↓
Generated Answer
Enter fullscreen mode Exit fullscreen mode

That works fine for general knowledge questions, but it has a hard ceiling. The model can only draw on what it learned during training, plus whatever you happen to paste into the prompt. Ask it about your company's internal wiki, a document that was updated yesterday, or a PDF sitting on your laptop, and it simply has no way to know.

Retrieval-Augmented Generation (RAG) fixes this by inserting a step before generation:

User Question
     ↓
Retriever
     ↓
Relevant Knowledge
     ↓
LLM
     ↓
Grounded Answer
Enter fullscreen mode Exit fullscreen mode

The core idea is simple to state, even if the engineering behind it isn't:

RAG connects an LLM to external knowledge so the model can retrieve relevant information before generating an answer.

Instead of relying purely on frozen training data, the model is handed exactly the information it needs, right when it needs it.

2. What Is a RAG System?

At a technical level, a RAG system is a pipeline that combines a search mechanism with a language model so that generation is grounded in retrieved, up-to-date information rather than memory alone.

It's built from three moving parts:

  1. Retrieval - finds the pieces of information most relevant to the user's question.
  2. Augmentation - inserts that information into the model's context window.
  3. Generation - the LLM uses the augmented context to produce the final answer.

The basic architecture behind all of this looks like:

Documents
   ↓
Chunking
   ↓
Embeddings
   ↓
Vector Database
   ↓
Retriever
   ↓
Relevant Context
   ↓
LLM
   ↓
Answer
Enter fullscreen mode Exit fullscreen mode

Everything from here on is really just a deeper look at each stage of that diagram.

3. The RAG Pipeline: From Documents to Answers

Before breaking down individual components, it helps to see the full lifecycle of a request, from raw documents all the way to a generated answer:

Documents
   ↓
Document Processing
   ↓
Chunking
   ↓
Embedding Generation
   ↓
Vector Storage
   ↓
User Query
   ↓
Query Embedding
   ↓
Similarity Search
   ↓
Context Retrieval
   ↓
Prompt Construction
   ↓
LLM Generation
Enter fullscreen mode Exit fullscreen mode

Notice that half of this pipeline runs offline, ahead of time (processing and indexing documents), while the other half runs online, in response to a live user query. That distinction matters a lot for performance and cost. RAG isn't a single model call it's a pipeline, and like any pipeline, quality is determined by its weakest stage.

4. Step 1: Preparing and Chunking Documents

You can't just dump a 200-page PDF into an LLM's context window and call it a day. Even with today's larger context windows, stuffing entire documents into every prompt is expensive, slow, and often counterproductive the model tends to lose focus when the signal-to-noise ratio drops.

Instead, documents are broken into smaller chunks:

Large PDF
   ↓
Thousands of words
   ↓
Smaller chunks
   ↓
Embeddings
Enter fullscreen mode Exit fullscreen mode

There are several common chunking strategies:

  • Fixed-size chunking - split text every N tokens or characters. Simple, predictable, but can cut sentences or ideas in half.
  • Recursive chunking - split along natural boundaries (paragraphs, then sentences, then words) until chunks fit a target size.
  • Semantic chunking - group text based on meaning, so a chunk represents one coherent idea rather than an arbitrary slice.
  • Chunk overlap - repeat a small amount of text between consecutive chunks so context isn't lost at the boundaries.
  • Metadata - attach source, page number, section, or date information to each chunk so it can be traced back and filtered later.

A single processed chunk might look like this:

{
  "text": "Annual subscriptions can be cancelled within...",
  "source": "billing-policy.pdf",
  "page": 12,
  "section": "Refund Policy"
}
Enter fullscreen mode Exit fullscreen mode

This step matters more than it looks. If a chunk splits a policy in half, or bundles three unrelated topics together, no amount of clever retrieval later will fully recover from it. Garbage chunks in, garbage answers out.

5. Step 2: Turning Text Into Embeddings

Once you have chunks, you need a way to compare them against a user's question. That's what embeddings are for.

An embedding model takes text like:

"How can I reset my password?"
Enter fullscreen mode Exit fullscreen mode

and converts it into a numerical vector:

[0.021, -0.184, 0.773, ...]
Enter fullscreen mode Exit fullscreen mode

That vector isn't random it's positioned in a high-dimensional space such that semantically similar pieces of text end up close together, and unrelated text ends up far apart. In other words, embeddings represent meaning, not just words.

"Reset password"
        ↓
Embedding Model
        ↓
[0.21, 0.74, -0.18, ...]
Enter fullscreen mode Exit fullscreen mode

A few things worth understanding here:

  • Embedding models vary in size, cost, and quality some are optimized for speed, others for accuracy on domain-specific text.
  • Dimensions refer to the length of the vector; more dimensions can capture more nuance but cost more to store and search.
  • Semantic similarity is what lets "reset my password" and "I forgot my login credentials" match, even though they share almost no words.
  • Query embeddings vs. document embeddings ideally both are produced by the same (or a compatible) embedding model, so they live in the same vector space and can be meaningfully compared.

6. Step 3: Storing Embeddings in a Vector Database

Once chunks are embedded, they need somewhere to live that supports fast similarity search across potentially millions of vectors. That's the job of a vector database.

Popular options include:

  • Pinecone
  • Weaviate
  • Chroma
  • FAISS
  • pgvector

The basic structure being stored is straightforward:

Document Chunk
      +
Embedding
      +
Metadata
      ↓
Vector Database
Enter fullscreen mode Exit fullscreen mode

For each entry, the database typically needs to store:

  • The vector itself
  • The original text or chunk
  • Metadata (source, page, section, timestamps, etc.)
  • Source information for citation or filtering

Choosing between these tools usually comes down to scale, hosting preferences, filtering capabilities, and how tightly you want it integrated with the rest of your stack.

7. Step 4: Embedding the User's Query

When a user asks a question like:

"What is the refund policy for annual plans?"

that question goes through the same or a compatible embedding model used to index the documents:

User Query
    ↓
Embedding Model
    ↓
Query Vector
Enter fullscreen mode Exit fullscreen mode

Once you have a query vector, the system can compare it against every stored document vector to find the closest matches.

8. Step 5: Retrieving Relevant Documents

This comparison is done through similarity search:

Query Vector
     ↓
Vector Search
     ↓
Top K Results
Enter fullscreen mode Exit fullscreen mode

There are a few common ways to measure "closeness" between vectors:

  • Cosine similarity - measures the angle between vectors, ignoring magnitude.
  • Euclidean distance - measures straight-line distance between two points.
  • Dot product - combines direction and magnitude.

The top_k parameter controls how many results come back for example, retrieving the 5 most relevant chunks:

results = vector_db.search(
    query_embedding,
    top_k=5
)
Enter fullscreen mode Exit fullscreen mode

It's tempting to just crank up top_k to "retrieve more and be safe," but that has a cost: pulling in too many chunks introduces noise, dilutes the truly relevant information, and can actually make the LLM's answer worse.

9. Step 6: Improving Retrieval With Hybrid Search and Re-Ranking

This is where a basic RAG setup starts to become a production-grade one.

Pure vector similarity search is powerful, but it isn't always enough on its own it can miss exact keyword matches (product codes, error messages, names) that a simpler search would catch instantly.

Hybrid Search

Hybrid search combines both approaches:

Keyword Search
      +
Vector Search
      ↓
Better Candidate Retrieval
Enter fullscreen mode Exit fullscreen mode

A common keyword-matching algorithm here is BM25, and the two result sets are often merged using a technique like reciprocal rank fusion, which blends rankings from multiple search methods into one.

Re-Ranking

Even after hybrid search, the top candidates aren't always ordered by true relevance. A re-ranking step can fix that:

Query
 ↓
Retrieve Top 20
 ↓
Re-Ranker
 ↓
Best 5
 ↓
LLM
Enter fullscreen mode Exit fullscreen mode

A reranker is typically a more expensive, more precise model that looks specifically at query-document pairs and scores relevance more carefully than the initial retrieval step. You cast a wide net first, then narrow it down with a sharper tool.

10. Step 7: Building the LLM Context

Retrieved chunks aren't useful on their own — they need to be assembled into a prompt the LLM can actually work with:

System Instructions

+

Retrieved Documents

+

User Question

↓

LLM Prompt
Enter fullscreen mode Exit fullscreen mode

In practice, that often looks like:

Context:
Annual subscriptions can be cancelled within 30 days...

Question:
What is the refund policy for annual plans?

Answer using only the provided context.
Enter fullscreen mode Exit fullscreen mode

A few details matter a lot at this stage:

  • Context formatting - clear separation between context and question helps the model stay grounded.
  • Source metadata - including where a chunk came from supports citations and trust.
  • Context limits - you can only fit so much into a prompt before cost and quality both suffer.
  • Ordering retrieved chunks - placement can affect how much attention the model pays to each piece.
  • Removing irrelevant information - trimming chunks that don't actually help keeps the signal clean.

11. Step 8: Generating the Final Answer With an LLM

With context assembled, the LLM finally does what it does best - generate a response:

User Question
+
Retrieved Context
Enter fullscreen mode Exit fullscreen mode
response = llm.generate(
    question=query,
    context=retrieved_documents
)
Enter fullscreen mode Exit fullscreen mode

It's worth being precise about the division of labor here:

The LLM is not performing the retrieval. It is generating an answer using the retrieved context.

Retrieval quality is a search problem. Generation quality is a language modeling problem. Conflating the two makes debugging RAG systems much harder than it needs to be.

12. A Simple End-to-End RAG Implementation

Putting it all together, a minimal RAG pipeline looks something like this:

documents = load_documents()

chunks = split_documents(documents)

embeddings = embedding_model.embed(chunks)

vector_db.add(
    chunks,
    embeddings
)

query_embedding = embedding_model.embed(query)

results = vector_db.search(
    query_embedding,
    top_k=5
)

context = "\n".join(results)

answer = llm.generate(
    query=query,
    context=context
)
Enter fullscreen mode Exit fullscreen mode

Walking through it:

  1. Load and chunk documents into manageable pieces.
  2. Embed each chunk into a vector.
  3. Store those vectors (plus text and metadata) in a vector database.
  4. Embed the incoming query using the same model.
  5. Search the vector database for the most similar chunks.
  6. Assemble context from the retrieved results.
  7. Generate the final answer using the query and context together.

This example is intentionally framework-agnostic. Whether you're using LangChain, LlamaIndex, a custom pipeline, or something built from scratch, the underlying concepts stay the same - only the implementation details change.

13. Where RAG Systems Commonly Fail

Building a RAG demo is easy. Keeping one reliable in production is where most of the real engineering work happens. Some of the most common failure modes:

Poor chunking - relevant information gets split across chunk boundaries, so no single chunk contains the full answer.

Weak retrieval - the system surfaces documents that are similar to the query but not actually correct.

Stale knowledge - the vector database wasn't updated when the source documents changed, so answers reflect outdated information.

Too much context - irrelevant chunks get pulled in alongside good ones, diluting the signal the LLM needs.

Hallucination - the model generates claims that aren't actually supported by the retrieved context.

Missing evaluation - without metrics, teams have no way to tell whether a change made retrieval better or worse.

14. How to Evaluate a RAG System

A useful principle: evaluate retrieval and generation separately. If you only look at the final answer, you can't tell whether a bad response came from bad search results or bad reasoning over good ones.

Retrieval Metrics

  • Recall@K - did the relevant document appear anywhere in the top K results?
  • Precision@K - how many of the top K results were actually relevant?
  • MRR (Mean Reciprocal Rank) - how high up did the first relevant result appear?

The question these answer:

Did the correct document actually reach the model?

Generation Metrics

  • Faithfulness - does the answer stay consistent with the retrieved context, without inventing facts?
  • Answer relevance - does the answer actually address the question asked?
  • Context relevance - was the retrieved context actually useful for answering the question?

The question these answer:

Did the model produce an answer grounded in the retrieved information?

Tools like RAGAS offer one option for automating a lot of this evaluation, rather than relying purely on manual spot-checks.

15. Production Best Practices for RAG

A practical checklist for taking RAG from prototype to production:

  • Clean documents before indexing
  • Choose chunking based on document structure
  • Store useful metadata
  • Use appropriate embedding models
  • Tune top_k
  • Consider hybrid search
  • Add reranking where useful
  • Keep the knowledge base synchronized
  • Track document versions
  • Evaluate retrieval separately from generation
  • Monitor latency and token usage
  • Log failed or low-confidence queries

The underlying principle behind all of it:

Treat RAG as a continuously maintained system, not a one-time vector database setup.

Documents change, user questions evolve, and models get updated. A RAG system that isn't maintained will quietly degrade.

16. RAG vs Fine-Tuning: When Should You Use Which?

These two approaches are often framed as competitors, but they solve different problems.

RAG Fine-Tuning
Adds external knowledge Changes model behavior
Knowledge can be updated Updating knowledge requires another training process
Good for private documents Good for specialized behavior/style
Retrieves information at runtime Knowledge becomes part of model parameters
Easier to update knowledge base Training can be more involved

In practice, these aren't mutually exclusive. Many production systems use fine-tuning to shape how a model behaves (tone, format, following instructions) while relying on RAG to supply what the model actually knows about a specific domain.

17. Where RAG Fits Into Modern AI Applications

RAG shows up across a wide range of real-world applications:

  • Internal knowledge assistants
  • Customer support
  • Document Q&A
  • Enterprise search
  • Legal document analysis
  • Technical documentation assistants
  • Financial knowledge systems
  • Research assistants

At a high level, the architecture behind most of these looks the same:

User
 ↓
Application
 ↓
RAG Pipeline
 ↓
Knowledge Base
 ↓
LLM
 ↓
Response
Enter fullscreen mode Exit fullscreen mode

What changes between use cases is mostly the knowledge base and the guardrails around it the underlying pattern stays remarkably consistent.

18. Final Thoughts: RAG Is an Engineering Pipeline

It's easy to think of RAG as "just add a vector database," but the reality is closer to this chain:

Good Documents
      ↓
Good Chunking
      ↓
Good Embeddings
      ↓
Good Retrieval
      ↓
Good Context
      ↓
Good Generation
      ↓
Reliable RAG Application
Enter fullscreen mode Exit fullscreen mode

The LLM is only one link in that chain and often not even the weakest one.

If retrieval is poor, a powerful model can still produce a poor answer. If the knowledge base is outdated, the answer can be confidently wrong. If the context is noisy, generation quality suffers even when the model itself is excellent.

The real engineering challenge isn't picking the "best" LLM it's connecting retrieval, context, and generation into a pipeline you can trust, measure, and maintain over time.

Top comments (0)