Chapter 44 min read

Chapter 4: Embeddings, Vectors, and Dimensionality

Computers do not understand language. They do not know what a "dog" is, nor do they understand that "puppy" and "canine" mean roughly the same thing. Computers only understand math.

To build a RAG pipeline, we must translate our human-readable text chunks (from Chapter 3) into mathematical representations. We do this using an Embedding Model, which converts text into an array of thousands of floating-point numbers called a Vector.

If two chunks of text have a similar meaning, their vectors will sit very close together in multi-dimensional space. If they are unrelated, they will be far apart.

In this chapter, we will move beyond basic API calls to understand model selection, the necessity of combining Dense and Sparse vectors, and managing production API limits.


4.1 Comparing Embedding Models

Not all embeddings are created equal. Different models yield different dimensionalities (the size of the array). Larger dimensions generally capture deeper semantic nuance but cost more to store and query.

  • OpenAI (text-embedding-3-large): The industry standard. Highly capable, multi-lingual, and supports variable dimensions (you can truncate the vectors to save database costs without losing much accuracy).
  • Cohere (embed-english-v3.0): Exceptional enterprise performance, particularly adept at handling messy business documents.
  • Local / Open Source (nomic-embed-text or bge-large-en-v1.5): If your data is highly sensitive and cannot leave your servers, you can run state-of-the-art open-source embedding models locally using Ollama or HuggingFace. They are completely free and often rival OpenAI in standard benchmarks.

Initializing Models in LangChain

# Option 1: OpenAI (Cloud)
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# Option 2: Nomic via Ollama (100% Offline and Free)
from langchain_ollama import OllamaEmbeddings
embeddings = OllamaEmbeddings(model="nomic-embed-text")

4.2 The Reality of Production: Dense vs. Sparse Vectors

Embedding models (like OpenAI or Nomic) produce Dense Vectors. They are incredible at semantic search. If you search for "Where do people sit?", a dense vector search will successfully retrieve a document about a "chair", even if the word "sit" is never explicitly written.

However, Dense Vectors fail miserably at exact keyword matching.

If you are searching a database for an exact serial number like "AX-90210", a dense embedding model might retrieve "BX-90210" because they are semantically similar (both look like serial numbers). This is a disaster in production.

The Hybrid Solution: Adding Sparse Vectors (BM25)

To fix this, production RAG systems use a Hybrid Search approach. They combine Dense semantic vectors with Sparse Vectors (like the classic BM25 algorithm, which underpins Elasticsearch).

Sparse vectors look for exact keyword overlap. By running both searches simultaneously and merging the results, you get the best of both worlds: deep semantic understanding and precise keyword matching.

(Note: We will implement the code for Hybrid Retrievers and Ensembles in Chapter 7).


4.3 Handling Rate Limits and Batching

When you are ingesting a massive dataset (e.g., 500,000 chunks), you cannot simply loop through them and hit the OpenAI embedding API 500,000 times sequentially.

  1. It would take days.
  2. You will instantly trigger HTTP 429 "Too Many Requests" rate limits.

LangChain embedding models have a hidden superpower: .embed_documents(). This method accepts an array of strings and handles batching automatically. Instead of making 1,000 API calls with 1 string each, it makes 1 API call carrying 1,000 strings, executing exponentially faster.

# Do NOT do this (Extremely slow and prone to rate limits):
# for chunk in chunks:
#     vector = embeddings.embed_query(chunk.page_content)
#     database.save(vector)

# DO this:
texts = [chunk.page_content for chunk in chunks]

# This sends the entire list to the API in optimized batches
vectors = embeddings.embed_documents(texts)

Dealing with Extreme Rate Limits

Even with batching, large enterprise ingestions will eventually hit the API's Tokens-Per-Minute limit. Advanced developers wrap the embedding calls in a retry mechanism using libraries like tenacity, or use LangChain's built-in max_retries parameters.

# The model will automatically pause and retry with exponential backoff if it hits a rate limit
embeddings = OpenAIEmbeddings(
    model="text-embedding-3-small", 
    max_retries=5
)

4.4 What's Next?

We have successfully converted our text chunks into mathematical vectors.

But storing hundreds of thousands of massive floating-point arrays requires specialized infrastructure. Standard relational databases (like MySQL) are not designed to calculate cosine distances between high-dimensional arrays in milliseconds.

In Chapter 5, we will explore the architecture of Vector Databases, covering indexing strategies, in-memory vs. cloud deployment, and the absolute necessity of Metadata Filtering.

    Chapter 4: Embeddings, Vectors, and Dimensionality — Complete RAG In Python | Krishna Tiwari