Intro to Vector Stores (Chroma, FAISS, Pinecone) with LangChain
Posted on Wed 19 August 2026 in GenAI
A vector store is a database that finds things by meaning, not exact match. You convert text into a numerical vector (an embedding), store it, and later search for semantically similar vectors. That's the retrieval half of RAG. Which store you pick depends on scale, whether you want persistence, and whether you're staying local or going cloud.
How similarity search works
When you embed text, you map it into a high-dimensional space where similar meanings land close to each other. "How do I reset my password?" and "Forgot password steps" produce vectors that are geometrically near each other, even though they share no words.
At query time:
- Embed the user's question into the same space.
- Find the
kstored vectors closest to it (cosine similarity or Euclidean distance). - Return the corresponding text chunks.
That's what every vector store does. The differences are in speed, persistence, and infrastructure.
Chroma — local, zero-config
Chroma runs in-process. No server, no Docker, just install and go. The right choice for development, prototypes, and small production apps where you control the machine.
pip install chromadb
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
embeddings = OpenAIEmbeddings()
# Create from documents
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db" # omit for in-memory only
)
# Load an existing store
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings
)
# Search
results = vectorstore.similarity_search("What is the refund policy?", k=4)
persist_directory writes the index to disk. Without it, everything lives in memory and resets when the process ends. Chroma handles up to a few hundred thousand documents comfortably on a standard laptop.
Metadata filtering in Chroma:
results = vectorstore.similarity_search(
query="security protocols",
k=3,
filter={"source": "handbook.pdf"}
)
Filter keys must match what you set in .metadata at index time.
FAISS — fast, in-memory, CPU-native
FAISS (Facebook AI Similarity Search) is a C++ library optimized for large-scale nearest-neighbor search. It's faster than Chroma for big indexes and doesn't need a server, but it's in-memory — persistence requires manually serializing and deserializing the index.
pip install faiss-cpu # or faiss-gpu if you have CUDA
from langchain_community.vectorstores import FAISS
# Create
vectorstore = FAISS.from_documents(chunks, embeddings)
# Save to disk
vectorstore.save_local("faiss_index")
# Load from disk
vectorstore = FAISS.load_local(
"faiss_index",
embeddings,
allow_dangerous_deserialization=True
)
# Search
results = vectorstore.similarity_search("explain the leave policy", k=5)
FAISS doesn't have built-in metadata filtering the way Chroma does. You filter post-retrieval by inspecting doc.metadata yourself. For production use cases needing filter logic, Chroma or Pinecone are easier.
FAISS shines when you have millions of vectors and want fast retrieval on your own hardware — local LLM setups, air-gapped environments, research workloads.
Pinecone — managed, cloud-native
Pinecone is a hosted vector database. You push vectors to their servers; they handle indexing, scaling, and replication. No infra to manage, but it's a paid service and your data leaves your machine.
pip install pinecone-client
from langchain_pinecone import PineconeVectorStore
import pinecone
pinecone.init(
api_key="your-api-key",
environment="us-east-1-aws"
)
# Create index (one-time setup, not per run)
pinecone.create_index(
name="my-index",
dimension=1536, # OpenAI ada-002 output dimension
metric="cosine"
)
# Upsert documents
vectorstore = PineconeVectorStore.from_documents(
documents=chunks,
embedding=embeddings,
index_name="my-index"
)
# Connect to existing index
vectorstore = PineconeVectorStore(
index_name="my-index",
embedding=embeddings
)
# Search with metadata filter
results = vectorstore.similarity_search(
"quarterly revenue targets",
k=4,
filter={"department": "finance"}
)
Pinecone's main advantage is that it scales horizontally without any work on your side. Billions of vectors, concurrent queries, namespace isolation — all handled. The tradeoff is cost and the fact that your data lives in their infrastructure.
Comparing the three
| Chroma | FAISS | Pinecone | |
|---|---|---|---|
| Hosting | Local | Local | Cloud |
| Persistence | Built-in | Manual (save/load) | Always on |
| Metadata filtering | Yes | Post-retrieval only | Yes |
| Scale | ~100k docs | Millions | Billions |
| Cost | Free | Free | Paid |
| Best for | Dev / small prod | Large local indexes | Production at scale |
Which retriever mode to use
All three expose .as_retriever(), which is what you pass into LangChain chains:
retriever = vectorstore.as_retriever(
search_type="mmr", # or "similarity" (default)
search_kwargs={"k": 5, "fetch_k": 20}
)
similarity — Returns the top-k most similar chunks. Simple, fast, but can return redundant results if your docs have repeated content.
mmr (Maximal Marginal Relevance) — Balances similarity with diversity. Fetches a larger candidate set (fetch_k), then picks k results that are relevant but not too similar to each other. Better for documents with repeated sections or when you want varied context.
Swapping stores without rewriting your chain
LangChain's interfaces are consistent across stores. Switch from Chroma to Pinecone by changing one line:
# Before
retriever = chroma_store.as_retriever(search_kwargs={"k": 4})
# After
retriever = pinecone_store.as_retriever(search_kwargs={"k": 4})
The chain, prompts, and LLM stay the same. This makes it easy to start local with Chroma and migrate to Pinecone when you need to scale.