AI & Machine Learning Cheatsheet

RAG and Vector Search

Use this AI & Machine Learning reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

What is RAG?

Retrieval-Augmented Generation (RAG) solves a key LLM limitation: hallucination and knowledge cutoffs. Instead of relying solely on parameters baked during pre-training, the model retrieves relevant documents at query time and uses them as context.

Basic RAG flow:

Query → Embed query → Vector search → Top-k relevant chunks
      → Augment prompt with chunks → LLM generates answer grounded in retrieved context

Benefits: - Up-to-date knowledge without retraining - Citations and verifiability - Reduced hallucination on factual questions - Domain customization without fine-tuning - Cheaper than full fine-tuning for knowledge updates

Vector Embeddings

A vector embedding maps text (or images, audio) to a dense vector in a latent space where semantic similarity ≈ geometric closeness.

from sentence_transformers import SentenceTransformer
import numpy as np

model = SentenceTransformer("BAAI/bge-large-en-v1.5")  # 1024-dim embeddings

# Embed documents
docs = [
    "Python is a high-level programming language",
    "Photosynthesis converts sunlight to glucose",
    "Neural networks are inspired by the brain"
]
embeddings = model.encode(docs, normalize_embeddings=True)
# shape: (3, 1024)

# Semantic similarity via dot product (after L2 normalization = cosine sim)
query_emb = model.encode(["What is Python?"], normalize_embeddings=True)
scores = (query_emb @ embeddings.T)[0]
print(f"Similarities: {scores}")

Choosing an Embedding Model

ModelDimUse CaseNotes
all-MiniLM-L6-v2384Fast, generalGood baseline
BAAI/bge-large-en-v1.51024English retrievalStrong MTEB score
text-embedding-3-small1536OpenAI APIGood quality/cost
text-embedding-3-large3072OpenAI APIBest OpenAI embedding
voyage-large-21536Voyage AI APITop retrieval benchmarks
nomic-embed-text-v1.5768Open, long context8192 token window
multilingual-e5-large1024Multilingual100+ languages

MTEB (Massive Text Embedding Benchmark) is the standard leaderboard for embedding models.

Vector Databases

Purpose-built for storing, indexing, and querying embedding vectors at scale.

DatabaseTypeKey Features
ChromaOpen-source, embeddedZero-setup, great for prototypes
FAISSLibraryBest raw performance, no persistence
PineconeManaged cloudServerless, fully managed
WeaviateOpen-source + cloudHybrid search, multi-modal
QdrantOpen-source + cloudRust-based, fast, filtering
MilvusOpen-sourceDistributed, production-scale
pgvectorPostgres extensionFamiliar SQL + vector
Redis VectorRedis extensionIn-memory, low latency
# Chroma (minimal setup)
import chromadb

client = chromadb.PersistentClient(path="./chroma_db")
collection = client.get_or_create_collection("documents", metadata={"hnsw:space": "cosine"})

# Add documents
collection.add(
    documents=docs,
    embeddings=embeddings.tolist(),
    ids=[f"doc_{i}" for i in range(len(docs))],
    metadatas=[{"source": "wiki", "year": 2024} for _ in docs]
)

# Query
results = collection.query(
    query_embeddings=query_emb.tolist(),
    n_results=5,
    where={"year": {"$gte": 2023}},   # metadata filtering
    include=["documents", "distances", "metadatas"]
)

Building a RAG System

1. Indexing Pipeline

from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyPDFLoader
from sentence_transformers import SentenceTransformer
import chromadb

# Load documents
loader = PyPDFLoader("document.pdf")
pages  = loader.load()

# Chunk text (important: chunk size matches embedding model context)
splitter = RecursiveCharacterTextSplitter(
    chunk_size=512,          # chars per chunk
    chunk_overlap=50,        # overlap for continuity
    separators=["\n\n", "\n", ". ", " ", ""]
)
chunks = splitter.split_documents(pages)

# Embed and store
embed_model = SentenceTransformer("BAAI/bge-large-en-v1.5")
texts = [c.page_content for c in chunks]
embeddings = embed_model.encode(texts, normalize_embeddings=True)

collection = client.get_or_create_collection("docs")
collection.add(
    documents=texts,
    embeddings=embeddings.tolist(),
    ids=[f"chunk_{i}" for i in range(len(texts))],
    metadatas=[{"page": c.metadata.get("page", 0)} for c in chunks]
)

2. Retrieval and Generation

from openai import OpenAI

openai_client = OpenAI()

def rag_query(question: str, n_results: int = 5) -> str:
    # Embed the question
    q_emb = embed_model.encode([question], normalize_embeddings=True)

    # Retrieve relevant chunks
    results = collection.query(query_embeddings=q_emb.tolist(), n_results=n_results)
    context = "\n\n---\n\n".join(results["documents"][0])

    # Generate answer
    response = openai_client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content":
             "Answer the question using only the provided context. "
             "If the context doesn't contain the answer, say 'I don't know'."},
            {"role": "user", "content":
             f"Context:\n{context}\n\nQuestion: {question}"}
        ],
        temperature=0
    )
    return response.choices[0].message.content

answer = rag_query("What is the company's revenue growth target?")

Advanced RAG Techniques

Query Rewriting / HyDE

HyDE (Hypothetical Document Embeddings): generate a hypothetical answer to the query, embed it, search with the embedding of the hypothetical answer (often closer to real documents).

def hyde_retrieval(query: str) -> list:
    # Generate hypothetical answer
    hyp_answer = openai_client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content":
                   f"Write a short paragraph that would answer: {query}"}]
    ).choices[0].message.content

    # Embed the hypothetical answer
    hyp_emb = embed_model.encode([hyp_answer], normalize_embeddings=True)
    return collection.query(query_embeddings=hyp_emb.tolist(), n_results=5)

Hybrid Search

Combine dense (vector) and sparse (BM25 keyword) search via reciprocal rank fusion or a learned re-ranker:

from rank_bm25 import BM25Okapi

# Sparse BM25 index
tokenized = [doc.split() for doc in texts]
bm25 = BM25Okapi(tokenized)

def hybrid_search(query, alpha=0.5, k=10):
    # Dense retrieval
    q_emb = embed_model.encode([query], normalize_embeddings=True)
    dense_res = collection.query(query_embeddings=q_emb.tolist(), n_results=k)

    # Sparse BM25 retrieval
    sparse_scores = bm25.get_scores(query.split())
    sparse_top = np.argsort(sparse_scores)[::-1][:k]

    # Reciprocal Rank Fusion
    scores = {}
    for rank, idx in enumerate(dense_res["ids"][0]):
        scores[idx] = scores.get(idx, 0) + alpha / (rank + 60)
    for rank, idx in enumerate(sparse_top):
        doc_id = f"chunk_{idx}"
        scores[doc_id] = scores.get(doc_id, 0) + (1-alpha) / (rank + 60)
    return sorted(scores, key=scores.get, reverse=True)[:k]

Re-ranking

After initial retrieval, re-rank top-k results with a more expensive cross-encoder model:

from sentence_transformers import CrossEncoder

reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")

query = "What is photosynthesis?"
candidates = retrieved_docs[:20]   # initial retrieval

pairs  = [[query, doc] for doc in candidates]
scores = reranker.predict(pairs)
ranked = sorted(zip(scores, candidates), reverse=True)
top5   = [doc for _, doc in ranked[:5]]

Chunking Strategies

StrategyDescriptionWhen to Use
Fixed-sizeSplit every N charactersSimple baseline
Recursive text splitterSplit on paragraphs → sentences → wordsGeneral text
Sentence splitterSplit on sentence boundariesCoherent context
Semantic chunkingSplit when embedding similarity dropsBest coherence
Document-specificTables, code blocks, sectionsStructured docs
Small-to-bigStore small chunks, retrieve parentPrecision + context

Contextual Compression

Extract only the relevant parts of retrieved chunks for the final prompt, reducing noise and token cost.

RAG Evaluation

MetricMeasuresTool
Context RecallAre relevant docs retrieved?RAGAS
Context PrecisionAre retrieved docs relevant?RAGAS
Answer FaithfulnessIs answer grounded in context?RAGAS
Answer RelevancyDoes answer address the question?RAGAS
MRR / nDCG@kRanking quality of retrievalManual
from ragas import evaluate
from ragas.metrics import (faithfulness, answer_relevancy,
                            context_recall, context_precision)
from datasets import Dataset

# Prepare evaluation dataset
data = Dataset.from_dict({
    "question": questions,
    "answer": generated_answers,
    "contexts": retrieved_contexts,
    "ground_truth": reference_answers
})

result = evaluate(data, metrics=[faithfulness, answer_relevancy,
                                   context_recall, context_precision])
print(result)

Agentic RAG

Move beyond single-retrieval to agents that can decide when and what to retrieve:

  • Iterative RAG: retrieve → answer → decide if more retrieval needed → retrieve again
  • Self-RAG: model generates special tokens to trigger retrieval, critique retrieved docs
  • FLARE: generate tentative next sentence; if uncertain, retrieve to improve it
  • Tool-use RAG: LLM decides to call search, calculator, code interpreter as needed
# Simple iterative RAG with OpenAI tools
tools = [{
    "type": "function",
    "function": {
        "name": "search_documents",
        "description": "Search the knowledge base for relevant information",
        "parameters": {
            "type": "object",
            "properties": {"query": {"type": "string"}},
            "required": ["query"]
        }
    }
}]

response = openai_client.chat.completions.create(
    model="gpt-4o",
    messages=messages,
    tools=tools,
    tool_choice="auto"
)