RAG Architecture
What is RAG?
Retrieval-Augmented Generation (RAG) combines the power of large language models with external knowledge retrieval. Instead of relying solely on parametric knowledge learned during training, RAG systems dynamically fetch relevant information to generate more accurate, up-to-date, and verifiable responses.
The key mechanism is vector similarity, not keyword matching. Every chunk of your source corpus is embedded into a high-dimensional vector and stored. When a question arrives, the query itself is embedded into that same vector space using the same embedding model. Retrieval then becomes geometry: the system computes a distance (or similarity) between the query vector and every chunk vector — typically cosine similarity or a dot product — and keeps the top-N nearest chunks. Those chunks, together with their metadata (chunk ID, source document, position) and the original question, are what gets handed to the LLM so it can write a grounded answer. Because matching happens on meaning rather than exact words, a query like “how do I reset my password” can retrieve a passage titled “account recovery steps” even though they share no keywords.
Why RAG is Needed
LLM Limitations
- Knowledge cutoff date
- Can't access private data
- May hallucinate facts
- Can't cite sources
RAG Solutions
- Real-time information access
- Query private knowledge bases
- Grounded in retrieved facts
- Provides source attribution
RAG Pipeline Architecture
Core Components
1. Indexing the corpus (offline, done once)
Documents → Chunks
Split source into passages
Embeddings
Each chunk → a vector
Vector DB
Vectors + metadata stored
2. Answering a query (online, every question)
User Query
The question
Embed Query
Same vector space as chunks
Similarity Search
Cosine / dot product vs. all chunks
Top-N Chunks
Nearest matches + metadata
LLM
Chunks + metadata + query
Grounded Answer
With citations
How retrieval actually works: the query is vectorized with the same embedding model used for the corpus, so the question and the chunks live in one shared space. The retriever then computes a distance/similarity score (cosine similarity, dot product, or Euclidean distance) between the query vector and every stored chunk vector, ranks them, and selects the top-N nearest chunks.
What gets passed to the model is not just the raw text: each retrieved chunk travels with its identifying metadata — chunk ID, source document, and position — alongside the original query. That bundle is assembled into the prompt so the LLM can ground its answer in the passages and cite exactly where each fact came from.
Step-by-Step Process
- 1Document Processing: Split documents into chunks, clean text, extract metadata
- 2Embedding Generation: Convert text chunks into high-dimensional vectors
- 3Indexing: Store embeddings in vector database with efficient search structures
- 4Query Vectorization: When a question arrives, embed it with the same embedding model so the query vector lands in the same space as the chunk vectors
- 5Distance / Similarity Calculation: Score the query vector against every stored chunk vector using cosine similarity, dot product, or Euclidean distance
- 6Top-N Selection: Rank chunks by score and keep the N nearest matches, carrying their metadata (chunk ID, source, position) along with them
- 7Context Assembly: Combine the retrieved chunks and their metadata with the original query into a single prompt
- 8Response Generation: The LLM generates a grounded answer using the retrieved context, able to cite which source each fact came from
Architecture Patterns
Basic RAG
Simple retrieval and generation
Components
- • Embedding Model
- • Vector DB
- • LLM
- • Simple Prompt
Pros
- ✓ Easy to implement
- ✓ Low complexity
- ✓ Quick prototyping
Cons
- ✗ Limited accuracy
- ✗ No query optimization
- ✗ Basic context handling
RAG Implementation Libraries
LangChain
Python/JSKey Features:
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.chains import RetrievalQA
# Initialize vector store
vectorstore = Chroma.from_documents(
documents=docs,
embedding=OpenAIEmbeddings()
)
# Create RAG chain
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
retriever=vectorstore.as_retriever(),
return_source_documents=True
)LlamaIndex
PythonKey Features:
from llama_index import VectorStoreIndex, SimpleDirectoryReader
from llama_index.llms import OpenAI
# Load and index documents
documents = SimpleDirectoryReader('data').load_data()
index = VectorStoreIndex.from_documents(documents)
# Query with RAG
query_engine = index.as_query_engine(
llm=OpenAI(model="gpt-4"),
similarity_top_k=3
)
response = query_engine.query("What is RAG?")Haystack
PythonKey Features:
from haystack import Pipeline
from haystack.nodes import EmbeddingRetriever, PromptNode
# Build RAG pipeline
pipeline = Pipeline()
pipeline.add_node(
component=retriever,
name="Retriever",
inputs=["Query"]
)
pipeline.add_node(
component=prompt_node,
name="PromptNode",
inputs=["Retriever"]
)Production Challenges
Latency
RAG adds retrieval time to generation
Solutions:
- Cache frequent queries
- Optimize embedding dimensions
- Use faster vector indexes
- Parallel retrieval and generation
Context Window Limits
LLMs have token limits for context
Solutions:
- Implement context compression
- Use hierarchical summarization
- Smart chunk selection
- Sliding window approach
Retrieval Quality
Retrieved chunks may not be relevant
Solutions:
- Hybrid search (vector + keyword)
- Query expansion/rewriting
- Cross-encoder re-ranking
- Feedback loops for improvement
Data Freshness
Keeping vector index up-to-date
Solutions:
- Incremental indexing
- Real-time embedding pipeline
- Version control for embeddings
- Scheduled re-indexing
Advanced RAG Patterns
Multi-Query RAG
Generate multiple query variations to improve retrieval coverage
# Generate multiple queries from user input
queries = [
"What is transformer architecture?",
"How do transformers work in NLP?",
"Explain self-attention mechanism",
"Transformer model components"
]
# Retrieve for each query and merge results
all_docs = []
for query in queries:
docs = retriever.get_relevant_documents(query)
all_docs.extend(docs)
# Deduplicate and rank
unique_docs = deduplicate(all_docs)
ranked_docs = rerank(unique_docs, original_query)RAG with Guardrails
Add safety and validation layers to RAG pipeline
- • Input Validation: Check queries for malicious content
- • Source Verification: Ensure retrieved docs are from trusted sources
- • Output Filtering: Remove sensitive information from responses
- • Hallucination Detection: Verify claims against retrieved context
Adaptive RAG
Dynamically adjust retrieval strategy based on query type
Simple Queries
Direct retrieval → Generate
Complex Queries
Decompose → Multi-hop retrieval
Comparison Queries
Parallel retrieval → Synthesize
Evaluation Metrics
Measuring RAG Performance
Retrieval Metrics
- Precision@K: Relevant docs in top K results
- Recall@K: Coverage of all relevant docs
- MRR: Mean Reciprocal Rank of first relevant doc
- NDCG: Normalized Discounted Cumulative Gain
Generation Metrics
- Faithfulness: Answer grounded in retrieved context
- Relevance: Answer addresses the query
- Completeness: All aspects of query covered
- Coherence: Logical flow and clarity
Try It: Build a Retrieval Pipeline
Everything above, made concrete. Paste your own text, watch it get chunked and embedded into real vectors, see those vectors laid out in 3D by t-SNE, then run a query and watch retrieval pick out the nearest chunks. Notice that the lab embeds your query into the same vector space as the chunks — that is exactly the query-vectorization step above. It then computes the similarity between that query vector and every chunk vector and highlights the top-N nearest ones, demonstrating the same distance calculation a production retriever runs. This is the retrieval half of RAG, end to end.
Next Steps
Now that you understand RAG architecture and implementation, explore how modern online search tools like Perplexity combine RAG with web-scale search capabilities.
Continue to Online LLM Search