Learn LangChain - Embeddings & Vector Stores
Episode 10 of 23

Learn LangChain - Embeddings & Vector Stores

Converting text into vectors: OpenAIEmbeddings and HuggingFaceEmbeddings, dimensions and normalization, then storing the index in Chroma, FAISS, pgvector, and Qdrant with add_documents and index persistence.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 9 you successfully turned PDFs, web pages, CSV, and JSON into neat chunks. But those chunks are still text — a search engine has to find them by meaning, not just keywords. That's where embeddings and vector stores come in.

This episode builds the storage layer of the RAG pipeline. First, embeddings: how OpenAIEmbeddings and HuggingFaceEmbeddings convert text into numeric vectors, plus the concepts of dimensions and normalization. Second, vector stores: storing and searching those vectors in Chroma, FAISS, pgvector, and Qdrant, complete with add_documents and index persistence.

What Is an Embedding

An embedding is a representation of text as a vector of real numbers. Sentences with similar meaning produce vectors that sit close together in high-dimensional space, so meaning similarity can be measured by vector similarity — usually via cosine similarity.

PythonConverting text into a vector
from langchain_openai import OpenAIEmbeddings
 
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vektor = embeddings.embed_query("Apa itu retrieval augmented generation?")
 
print(len(vektor))   # vector dimensions
print(vektor[:5])    # part of the vector contents

embeddings.embed_query(...) produces one vector for one text. There's also embed_documents for batches. The length of the list is what's called the dimension — the larger it is, the more nuance it can capture, but the more expensive storage and compute become.

OpenAIEmbeddings and HuggingFaceEmbeddings

Two of the most common embedding families: hosted and locally running.

  • OpenAIEmbeddings — fast and high quality, but paid and sends data to the OpenAI API. Suitable for production already on the OpenAI ecosystem.
  • HuggingFaceEmbeddings — runs locally, free, and keeps data off your server. Suitable for prototypes, sensitive data, or when API costs should be avoided.
PythonTwo different embedders
from langchain_huggingface import HuggingFaceEmbeddings
 
# Local, free, no API key
huggingface = HuggingFaceEmbeddings(
    model_name="sentence-transformers/all-MiniLM-L6-v2"
)
vektor_lokal = huggingface.embed_query("Halo dunia")
print(len(vektor_lokal))

The choice of embedder affects everything: vector dimensions, retrieval quality, cost, and latency. Rule of thumb: prototype fast with a small local embedder, then measure retrieval quality before deciding to move up to a larger paid embedder.

Dimensions and Normalization

Vector dimensions are determined by the embedding model. text-embedding-3-small produces 1536 dimensions, all-MiniLM-L6-v2 produces 384 dimensions. Larger dimensions capture more context but consume more storage — an important consideration when data reaches millions of chunks.

Normalization means converting a vector so its length is one unit. A normalized vector makes dot product comparison equivalent to cosine similarity, and some vector stores leverage that for faster search.

PythonSimple vector normalization
import numpy as np
 
vektor = np.array([0.2, 0.5, -0.3])
normalized = vektor / np.linalg.norm(vektor)
print(normalized)

Many providers return already-normalized vectors by default. The key thing to understand: if you normalize manually and the vector store also normalizes, the result can be changed twice — make sure the embedding side and the index side are consistent.

Vector Store: Chroma

A vector store stores vectors and provides similarity-based search. Chroma is the easiest choice to get started: runs locally, persists to a directory, and requires no separate server.

PythonCreating a Chroma index
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
 
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
 
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db",
)
print(vectorstore._collection.count())

from_documents takes the list of Documents from episode 9, embeds their text, then stores them in ./chroma_db. One step replaces two manual operations: embedding and insert.

Vector Store: FAISS, pgvector, and Qdrant

Each vector store has different strengths. FAISS is Meta's vector search library — fast, lightweight, and storable as a single local index file.

PythonFAISS with persist and load
from langchain_community.vectorstores import FAISS
 
faiss_store = FAISS.from_documents(chunks, embeddings)
faiss_store.save_local("faiss_index")
 
faiss_store = FAISS.load_local(
    "faiss_index",
    embeddings,
    allow_dangerous_deserialization=True,
)

save_local writes the index to a folder, load_local loads it back — practical for applications without a separate vector server. pgvector stores vectors as a column in PostgreSQL, integrated with your relational data; Qdrant offers a dedicated server with strong filtering and scaling for large-scale production.

Warning

FAISS uses pickle when saving the index — always set allow_dangerous_deserialization=True only if the index comes from a trusted source. Don't load an index downloaded from the internet.

add_documents and Index Persistence

In production, data grows continuously. add_documents allows incremental insertion without rebuilding the entire index.

PythonAdding documents to an existing index
dokumen_baru = [{"page_content": "Bab 2: RAG di produksi", "metadata": {"sumber": "buku.md"}}]
vectorstore.add_documents(dokumen_baru)
 
vectorstore.persist()  # for Chroma, save changes to disk

The common pattern: build the initial index via from_documents the first time, then use add_documents for daily updates or new batches. For Chroma, persist() writes changes to the directory; FAISS simply re-saves via save_local when needed.

Conclusion

The storage layer of the RAG pipeline now stands. You can convert the chunks from episode 9 into vectors with OpenAIEmbeddings or HuggingFaceEmbeddings, understand the impact of dimensions and normalization, then store the index in Chroma, FAISS, pgvector, or Qdrant with add_documents and persistence. All that remains is searching — and that's the theme of the next episode.

Key takeaways:

  • Embeddings convert text into vectors; meaning similarity ≈ vector proximity.
  • OpenAIEmbeddings for hosted production, HuggingFaceEmbeddings for local and free.
  • Dimensions are determined by the embedding model; larger brings quality along with storage cost.
  • Normalization makes dot product comparison equivalent to cosine similarity — make sure it's consistent.
  • Chroma to get started, FAISS for local file indexes, pgvector for relational integration, Qdrant for large scale.
  • add_documents for incremental additions; persist the index periodically so data isn't lost.

In episode 11 you'll use everything that's been built: Retrieval & Basic RAGVectorStoreRetriever, similarity_search with top-k and metadata filtering, assembling a retrieve-prompt-generate pipeline with LCEL, and evaluating answer quality. See you there!

Learn LangChain - Embeddings & Vector Stores | Learn LangChain