Vector Databases & Embeddings
Vector databases store and search embeddings efficiently. They're the backbone of RAG systems, enabling fast semantic search across millions of documents.
Understanding Embeddings
An embedding is a list of numbers (a vector) that represents the meaning of text. The key property: semantically similar texts have similar vectors.
python
from openai import OpenAI
client = OpenAI()
# Generate embeddings
response = client.embeddings.create(
model="text-embedding-3-small",
input=["Python programming", "JavaScript coding", "Cooking recipes"]
)
# Each embedding is a list of 1536 floats
emb_python = response.data[0].embedding # [0.023, -0.041, ...]
emb_javascript = response.data[1].embedding # [0.025, -0.038, ...]
emb_cooking = response.data[2].embedding # [-0.015, 0.067, ...]
# Python and JavaScript embeddings will be close
# Cooking embedding will be far from bothEmbedding Models
| Model | Provider | Dimensions | Best For |
|---|---|---|---|
| text-embedding-3-small | OpenAI | 1536 | Cost-effective, general purpose |
| text-embedding-3-large | OpenAI | 3072 | Higher accuracy |
| voyage-3 | Voyage AI | 1024 | Code and technical text |
| embed-v4 | Cohere | 1024 | Multilingual |
| all-MiniLM-L6-v2 | Open-source | 384 | Free, runs locally |
Measuring Similarity
Cosine Similarity
The standard metric for comparing embeddings:
python
import numpy as np
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# Score ranges from -1 (opposite) to 1 (identical)
# > 0.8: Very similar
# 0.5-0.8: Somewhat related
# < 0.5: Different topicsVector Databases
A vector database is optimized for storing embeddings and performing fast similarity search.
Popular Vector Databases
| Database | Type | Free Tier | Best For |
|---|---|---|---|
| Pinecone | Managed cloud | Yes | Easiest to start |
| ChromaDB | Open-source | Yes (local) | Prototyping |
| Weaviate | Open-source/Cloud | Yes | Hybrid search |
| Qdrant | Open-source/Cloud | Yes | Performance |
| pgvector | PostgreSQL extension | Yes | Existing Postgres users |
| FAISS | Library (Meta) | Yes (local) | Pure Python, no server |
ChromaDB — Getting Started
The easiest vector database for development:
bash
pip install chromadbpython
import chromadb
# Create a client (in-memory for development)
client = chromadb.Client()
# Create a collection
collection = client.create_collection(
name="company_docs",
metadata={"description": "Company documentation"}
)
# Add documents (ChromaDB auto-generates embeddings)
collection.add(
documents=[
"Our refund policy allows returns within 14 days.",
"Premium plans include priority support and custom integrations.",
"Free shipping on orders over $50 in the US.",
"Office hours are Monday to Friday, 9 AM to 6 PM.",
],
ids=["doc1", "doc2", "doc3", "doc4"],
metadatas=[
{"category": "policy"},
{"category": "pricing"},
{"category": "shipping"},
{"category": "support"},
]
)
# Query
results = collection.query(
query_texts=["How can I get my money back?"],
n_results=2
)
print(results["documents"])
# → [["Our refund policy allows returns within 14 days.",
# "Premium plans include priority support..."]]Pinecone — Production-Ready
bash
pip install pinecone-clientpython
from pinecone import Pinecone
from openai import OpenAI
pc = Pinecone(api_key="your-pinecone-key")
openai_client = OpenAI()
# Create an index
pc.create_index(
name="company-docs",
dimension=1536, # Match your embedding model
metric="cosine"
)
index = pc.Index("company-docs")
# Embed and upsert documents
def embed_and_store(documents):
for i, doc in enumerate(documents):
embedding = openai_client.embeddings.create(
model="text-embedding-3-small",
input=doc
).data[0].embedding
index.upsert(vectors=[{
"id": f"doc_{i}",
"values": embedding,
"metadata": {"text": doc}
}])
# Query
def search_docs(query, top_k=3):
query_embedding = openai_client.embeddings.create(
model="text-embedding-3-small",
input=query
).data[0].embedding
results = index.query(
vector=query_embedding,
top_k=top_k,
include_metadata=True
)
return [match.metadata["text"] for match in results.matches]pgvector — Using PostgreSQL
If you already use PostgreSQL:
sql
-- Enable the extension
CREATE EXTENSION vector;
-- Create a table with vector column
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
content TEXT,
embedding vector(1536),
metadata JSONB
);
-- Create an index for fast search
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- Insert a document
INSERT INTO documents (content, embedding, metadata)
VALUES ('Refund policy: 14 days', '[0.023, -0.041, ...]', '{"category": "policy"}');
-- Search for similar documents
SELECT content, 1 - (embedding <=> '[query_embedding]') AS similarity
FROM documents
ORDER BY embedding <=> '[query_embedding]'
LIMIT 5;Building a Complete Indexing Pipeline
python
import os
from openai import OpenAI
import chromadb
client = OpenAI()
chroma = chromadb.PersistentClient(path="./chroma_db")
def load_documents(directory):
"""Load all .txt and .md files from a directory."""
docs = []
for filename in os.listdir(directory):
if filename.endswith(('.txt', '.md')):
with open(os.path.join(directory, filename)) as f:
docs.append({
"content": f.read(),
"filename": filename
})
return docs
def chunk_document(doc, chunk_size=500, overlap=50):
"""Split a document into overlapping chunks."""
words = doc["content"].split()
chunks = []
start = 0
while start < len(words):
end = min(start + chunk_size, len(words))
chunk_text = " ".join(words[start:end])
chunks.append({
"content": chunk_text,
"source": doc["filename"],
"chunk_index": len(chunks)
})
start = end - overlap
return chunks
def build_index(directory):
"""Build a complete vector index from a directory."""
collection = chroma.get_or_create_collection("knowledge_base")
docs = load_documents(directory)
all_chunks = []
for doc in docs:
all_chunks.extend(chunk_document(doc))
# Batch process
batch_size = 100
for i in range(0, len(all_chunks), batch_size):
batch = all_chunks[i:i + batch_size]
collection.add(
documents=[c["content"] for c in batch],
ids=[f"chunk_{i+j}" for j in range(len(batch))],
metadatas=[{
"source": c["source"],
"chunk_index": c["chunk_index"]
} for c in batch]
)
print(f"Indexed {len(all_chunks)} chunks from {len(docs)} documents")
# Usage
build_index("./company_docs")Embedding Best Practices
- Match dimensions — Embedding model dimensions must match your vector DB index
- Batch embed — Send multiple texts per API call to reduce latency
- Normalize — Some databases require normalized vectors
- Choose chunk size carefully — Too small loses context, too large reduces precision
- Include metadata — Store source file, page number, date for filtering
- Use the same model for indexing and querying
Key Takeaways
- Embeddings capture semantic meaning — similar texts have similar vectors
- Vector databases enable fast similarity search over millions of documents
- Start with ChromaDB for development, move to Pinecone/pgvector for production
- Chunking strategy significantly impacts retrieval quality
- Metadata filtering adds precision to semantic search
Next, we'll combine everything to build complete RAG pipelines.