DS DevShelfHub Projects · AI tools
Cheatsheets / RAG
Cheatsheet · AI frameworks

RAG Cheatsheet: Retrieval-Augmented Generation Pipeline Reference

By DevShelfHub

Load, chunk, embed, store, retrieve, rerank, generate — the full retrieval-augmented-generation pipeline with the choices that matter.

83 items 7 min Chunk Embed Retrieve

Start hereQuick start · 6 you’ll reach for daily

LoadLoader(path).load()
ChunkRecursiveCharacterTextSplitter()
EmbedOpenAIEmbeddings(model=…)
StoreChroma.from_documents(…)
Retrievestore.as_retriever(k=4)
Generateretriever | prompt | llm

scope · stack-neutralVersions

Targets: any modern chat model any vector DB python ≥ 3.10

Concepts on this page transfer across frameworks. Snippets use LangChain because it has the broadest coverage, but every step (load → chunk → embed → store → retrieve → rerank → generate) maps cleanly to LlamaIndex and Haystack.

install · smoke testSetup

bash
# Minimum stack — works for >90% of starter projects
pip install langchain langchain-openai langchain-chroma langchain-text-splitters

# Optional reranker + sparse retriever (hybrid)
pip install sentence-transformers cohere rank-bm25

# Provider keys
export OPENAI_API_KEY=sk-...
export COHERE_API_KEY=co-...

# Sanity check the stack
python - <<'PY'
from langchain_openai import OpenAIEmbeddings
emb = OpenAIEmbeddings(model="text-embedding-3-small")
print(len(emb.embed_query("hello")))  # 1536 for 3-small
PY

the seven stagesPipeline

1. LoadSource → Document. PDF, web, S3, Notion, DB …
2. ChunkDocument → Node / chunk. Drives recall.
3. EmbedChunk → vector. Pick a model that matches your domain.
4. StorePersist vectors + metadata. Chroma / Qdrant / Pinecone / pgvector.
5. RetrieveQuery → top-k chunks. Dense, sparse, or hybrid.
6. RerankCross-encoder pass over the top-k. Highest accuracy lever.
7. GeneratePrompt + chunks → answer. Cite. Refuse when unsure.
Each stage has its own failure mode. Bad answer? Diagnose top-down: was the right chunk retrieved, was it reranked into the prompt, did the model use it?

source → DocumentLoaders

TextLoader("file.txt")Plain text. Always specify encoding.
PyPDFLoader("file.pdf")PDF → one Document per page.
UnstructuredFileLoader("f.docx")100+ formats via unstructured. Heavyweight.
WebBaseLoader([url, …])Scrape with BeautifulSoup.
SitemapLoader("sitemap.xml")Bulk-load from a sitemap.
FireCrawlLoader(url=…, mode="crawl")Managed crawler. Renders JS.
S3DirectoryLoader / GCSDirectoryLoaderCloud buckets.
NotionDBLoader / ConfluenceLoader / SlackDirectoryLoaderSaaS sources.
loader.lazy_load()Iterator. Use for huge corpora.
doc.metadata["source"]Always carry source IDs through — citations later.

drive recallChunking

RecursiveCharacterTextSplitterPreferred default. Tries ¶ → sentence → word.
chunk_size = 600–1200~150–300 tokens. Sweet spot for most QA.
chunk_overlap = 10–20% of sizePreserves cross-boundary context.
MarkdownHeaderTextSplitterKeeps headers in metadata. Use on docs sites.
TokenTextSplitterToken-precise splitting. Use when context budget is tight.
SemanticChunkerSplits on embedding-similarity drop. Slower, smarter.
RecursiveCharacterTextSplitter.from_language(Language.PYTHON)AST-aware code splitting.
parent-child / hierarchicalSmall chunks for retrieval, big chunks for answer context.
propositional chunkingUse an LLM to rewrite text as standalone propositions. Highest recall, most cost.
python
from langchain_text_splitters import (
    RecursiveCharacterTextSplitter, MarkdownHeaderTextSplitter,
)

# Default: recursive, character-based, hierarchical separators
default = RecursiveCharacterTextSplitter(
    chunk_size=900,            # ~ 200-300 tokens
    chunk_overlap=120,         # ~ 15% overlap
    separators=["\n\n", "\n", ". ", " ", ""],
)
chunks = default.split_text(open("doc.md").read())

# Markdown-aware: keep section context as metadata
md = MarkdownHeaderTextSplitter(headers_to_split_on=[("#", "h1"), ("##", "h2")])
sections = md.split_text(open("doc.md").read())

# Token-based when you must respect a context window precisely
from langchain_text_splitters import TokenTextSplitter
tokens = TokenTextSplitter(chunk_size=350, chunk_overlap=50)

# Code-aware (Python AST-respecting)
from langchain_text_splitters import RecursiveCharacterTextSplitter, Language
py = RecursiveCharacterTextSplitter.from_language(
    language=Language.PYTHON, chunk_size=900, chunk_overlap=80,
)

text → vectorEmbeddings

text-embedding-3-small (OpenAI)1536-d. Cheap default for English.
text-embedding-3-large (OpenAI)3072-d. Use when accuracy > cost.
voyage-3 / voyage-3-largeAnthropic-recommended; strong on retrieval benchmarks.
cohere embed-english-v3.0Strong English; matrix with their reranker.
BAAI/bge-small-en-v1.5Open-source. Runs locally on CPU.
BAAI/bge-m3Multilingual + multi-vector. Heavier.
domain-tuned: code, legal, medicalSwitch when generic models are visibly missing.
Matryoshka embeddingsTruncate dims at query time for speed without re-embedding.
Embed query vs document differently?Yes, when the model says so (BGE, Voyage). Pass input_type.

where vectors liveVector stores

ChromaLocal persistent. Best dev experience.
QdrantSelf-host or cloud. Strong filtering + hybrid.
PineconeManaged, serverless tiers. Pay-per-vector.
pgvector / PostgresSQL + vectors in one DB. Easy to start; tune HNSW carefully.
WeaviateHybrid + modules (rerankers, generators).
Milvus / ZillizBuilt for billions of vectors.
FAISSLibrary Not a DB. In-memory; serialise to disk.
Filter on metadata, not textPre-filter by user_id / tenant / date before kNN. Cheaper + correct.
HNSW M / efDefault tuning knobs. Higher = recall up, latency up.

find the right chunksRetrieval

Dense (vector)kNN over embeddings. Semantic. Default.
Sparse / BM25Term-frequency-based. Beats dense on rare keywords / IDs.
HybridPreferred Run both; merge with RRF. Cheap accuracy win.
MMRDiversify results. Use when chunks are near-duplicates.
Multi-query rewritingLLM rewrites the question N ways; union the results.
HyDELLM hallucinates a plausible answer first, embed that. Lifts recall on vague queries.
Step-back questionsAsk a broader question, retrieve, then narrow.
Self-queryLLM extracts metadata filters from natural language.
Parent-document retrievalMatch on small chunks, hand the big parent to the LLM.
top-k = 4–10 before rerank, 3–5 afterStrong starting point.
python
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain.retrievers import EnsembleRetriever, ContextualCompressionRetriever
from langchain_community.retrievers import BM25Retriever
from langchain.retrievers.document_compressors import CohereRerank

emb = OpenAIEmbeddings(model="text-embedding-3-small")
vec = Chroma(collection_name="docs", embedding_function=emb,
             persist_directory="./chroma")

# 1 · Dense (embedding) + sparse (BM25) retrievers in parallel
dense  = vec.as_retriever(search_kwargs={"k": 10})
sparse = BM25Retriever.from_texts(docs)            # docs = list[str] for BM25
sparse.k = 10

hybrid = EnsembleRetriever(retrievers=[dense, sparse], weights=[0.6, 0.4])

# 2 · Rerank the merged candidates with a cross-encoder
reranker = CohereRerank(model="rerank-english-v3.0", top_n=4)
final = ContextualCompressionRetriever(
    base_compressor=reranker, base_retriever=hybrid,
)

hits = final.invoke("What is the WFH policy?")
for h in hits:
    print(round(h.metadata.get("relevance_score", 0), 3), h.page_content[:80])

re-score top-kReranking

Cohere Rerank v3Strong hosted default. Multilingual.
Voyage rerank-2Strong for English; pairs with Voyage embeddings.
BAAI/bge-reranker-v2-m3Open-source cross-encoder. Self-host.
jina-reranker-v2Open-source alternative. Tiny model option.
LLM-as-rerankerUse an LLM to score chunks. Most expensive, most flexible.
k_in >> k_outRetrieve 20-50, rerank to 3-5. The whole point.
ContextualCompressionRetriever(base_compressor=rerank, base_retriever=…)LangChain wrapper.
If you change one thing, add a reranker. Single biggest accuracy lever per dollar — typical lift is 10-20 points NDCG@5 vs raw dense retrieval, at one extra hosted API call.

prompt the answerGeneration

stuffSingle LLM call with all chunks. Cheapest. Default for top-k ≤ 5.
refineWalk chunks one-by-one, refining the answer. Use for long answers.
map_reduceAnswer per chunk → aggregate. Best for summarisation across many docs.
map_rerankAnswer per chunk + self-score → pick highest. Single-answer tasks.
CitationsInject source IDs in the context; ask for them back: [file:page].
Refusal path“If unsure, say ‘I don’t know’.” Reduces hallucination.
Strict-context instruction“Use ONLY the context.” Repeat at the end of the prompt.
StreamGenerate tokens as they arrive. Big UX win for long answers.

measure RAG qualityEvaluation

Retrieval metricsRecall@k, MRR, nDCG@k. Cheap, model-free.
Faithfulness / groundednessDoes the answer follow from the retrieved context?
Answer relevanceDoes the answer match the question intent?
Context precision / recallWere the right chunks retrieved? Did they cover everything?
RAGASLibrary that bundles the above as LLM-judged metrics.
TruLens / Phoenix / LangSmithObservability + eval over real traces.
Golden set: 20–100 Q/AHand-curated, sourced from real questions.
A/B retrieval before A/B promptsTest retrieval changes in isolation; prompts are downstream.

load → chunk → embed → retrieve → generateEnd-to-end · Minimal RAG

A folder of PDFs to an answering chain with citations. Replace the path and you have a working doc-bot.

python
# Load -> split -> embed -> retrieve -> rerank -> generate -> cite.
from pathlib import Path
from langchain_community.document_loaders import PyPDFDirectoryLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_chroma import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

docs = PyPDFDirectoryLoader("./pdfs").load()
chunks = RecursiveCharacterTextSplitter(
    chunk_size=900, chunk_overlap=120,
).split_documents(docs)

emb = OpenAIEmbeddings(model="text-embedding-3-small")
store = Chroma.from_documents(chunks, emb, persist_directory="./chroma")
retriever = store.as_retriever(search_kwargs={"k": 6})

PROMPT = ChatPromptTemplate.from_messages([
    ("system",
     "Answer using ONLY the context. Cite sources as [filename:page]. "
     "If unsure, say 'I don't know'.\n\nContext:\n{context}"),
    ("human", "{question}"),
])

def format_ctx(docs):
    return "\n\n".join(
        f"[{d.metadata.get('source')}:{d.metadata.get('page')}] {d.page_content}"
        for d in docs
    )

chain = (
    {"context": retriever | format_ctx, "question": RunnablePassthrough()}
    | PROMPT
    | ChatOpenAI(model="gpt-4o-mini", temperature=0)
    | StrOutputParser()
)
print(chain.invoke("Summarise the access policy in 3 bullets."))

Best practiceGood to know

Hybrid retrieval + reranker is the strong default. Dense alone misses keyword-heavy queries; sparse alone misses paraphrases. A cross-encoder rerank on top of both reliably beats either branch.
Most “bad RAG” is bad chunking. Before tweaking embeddings or LLMs, inspect what chunks look like. Headers stripped? Tables fragmented? Code snippets cut in half? Fix at the splitter.
Always pass source IDs through. Stitch metadata["source"], page, heading into every chunk. Citations are 80% of user trust.

Common trapsWatch out for

Mismatched embedding model on reload. If you ingest with text-embedding-3-small (1536-d) and query with 3-large (3072-d), the store raises on dim mismatch — or worse, returns garbage with a homemade wrapper. Pin the model name in metadata.
Don’t mix tenant data in one collection. Even with metadata filtering, accidental leakage is a query-rewriter bug away. Use separate collections or partition keys at the store level.
Large top_k dilutes the prompt. Past ~8 chunks, recall stops helping and signal-to-noise drops — the model gets confused. Retrieve wide, rerank narrow.

Go deeperSee also

RAG FAQ

What is RAG (Retrieval-Augmented Generation)?

RAG is a pattern that improves LLM responses by injecting relevant retrieved documents into the prompt at inference time. Instead of relying on the model's training knowledge, RAG fetches up-to-date or domain-specific chunks from a vector store (or BM25 index), reranks them, and passes them as context. This reduces hallucinations and keeps answers grounded in real data.

What are the main steps in a RAG pipeline?

The pipeline has two phases. Indexing: load documents, chunk them (by token count, sentence, or semantic boundary), embed each chunk with an embedding model, and store vectors in a vector database. Retrieval: embed the query, search the vector store for top-k chunks, optionally rerank with a cross-encoder, then pass the chunks and query to an LLM to generate the final answer.

What is the best chunking strategy for RAG?

There is no single best strategy. Fixed-size token chunks (256-512 tokens with 10-20% overlap) are the safe default. Recursive character splitting respects natural text boundaries. Semantic chunking groups sentences by embedding similarity for more coherent chunks. Parent-document retrieval stores small chunks but returns their larger parent for context. Experiment with chunk size — it is one of the highest-impact RAG parameters.

What is reranking in RAG and why does it matter?

A vector search retrieves top-k candidates by embedding similarity, which is fast but imprecise. A reranker (cross-encoder) scores each candidate against the query in a joint forward pass, producing much better relevance ordering. Cohere Rerank, BGE-Reranker, and Jina Reranker are popular options. Retrieve 20-50 candidates from ANN, rerank, then pass only the top 3-5 to the LLM to save tokens.

What is the difference between naive RAG and advanced RAG?

Naive RAG is a single retrieval step: embed query, retrieve top-k, generate. Advanced RAG adds query rewriting (HyDE, step-back prompting), multi-query expansion, hybrid search (dense + sparse), reranking, iterative retrieval (self-RAG, FLARE), and citation grounding. Start naive and add complexity only where evaluation shows it improves answer quality.

Which frameworks support RAG pipelines?

LangChain and LlamaIndex are the two most popular frameworks with built-in RAG abstractions — loaders, splitters, retrievers, and chain/query engine primitives. Haystack offers a pipeline-component model. For production, many teams build custom pipelines directly using embedding model APIs (OpenAI, Cohere, Google) and vector DBs (Qdrant, Chroma, Milvus, Pinecone) to avoid framework lock-in.