3.4 Vector Stores en Profunditat¶
Objectiu
Entendre el funcionament intern dels vector stores: com s'emmagatzemen els embeddings, quins algorismes de cerca s'utilitzen i com escollir i configurar el vector store adequat per a cada cas d'ús en sistemes RAG i agents.
🧲 Què és un Vector Store?¶
Un vector store (o base de dades vectorial) és un sistema d'emmagatzematge i cerca especialitzat per a vectors d'alta dimensió (embeddings). A diferència d'una base de dades relacional que cerca per valors exactes, un vector store cerca per similitud semàntica.
Base de dades relacional:
SELECT * FROM docs WHERE text = "xarxes neuronals"
→ Coincidència EXACTA de paraules
Vector store:
Cerca: "com funcionen les IA modernes"
→ Retorna els 3 documents semànticament MÉS SIMILARS:
1. "Arquitectura de les xarxes neuronals profundes" (similitud: 0.91)
2. "Transformers i atenció en LLM" (similitud: 0.88)
3. "Backpropagation i aprenentatge profund" (similitud: 0.84)
→ Funciona fins i tot si cap paraula coincideix exactament!
📐 Embeddings: La Base dels Vector Stores¶
Abans d'emmagatzemar, cada fragment de text es converteix en un vector numèric (embedding) amb un model específic:
from langchain_openai import OpenAIEmbeddings
from langchain_community.embeddings import HuggingFaceEmbeddings
# Model d'embedding d'OpenAI (propietari, 1536 dimensions)
embeddings_openai = OpenAIEmbeddings(model="text-embedding-3-small")
# Model d'embedding local gratuït (768 dimensions)
embeddings_local = HuggingFaceEmbeddings(
model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
# Suporta català, castellà, anglès i 50+ idiomes
)
# Com funciona internament:
text = "Els transformers van revolucionar el NLP el 2017"
vector = embeddings_openai.embed_query(text)
print(f"Dimensió del vector: {len(vector)}") # → 1536
print(f"Primeres 5 dimensions: {vector[:5]}") # → [0.021, -0.143, 0.089, ...]
# El vector captura el "significat" del text en un espai multidimensional
Comparació de Models d'Embedding¶
| Model | Proveïdor | Dimensions | Cost | Idiomes | Recomanat per a |
|---|---|---|---|---|---|
text-embedding-3-small |
OpenAI | 1.536 | $0.02/1M tokens | Multi | RAG general, bon equilibri |
text-embedding-3-large |
OpenAI | 3.072 | $0.13/1M tokens | Multi | Màxima qualitat |
voyage-3 |
Anthropic/Voyage | 1.024 | $0.06/1M tokens | Multi | Amb Claude |
multilingual-e5-large |
Microsoft | 1.024 | Gratuït (local) | 100+ | Producció multilingüe |
paraphrase-multilingual-MiniLM-L12 |
HuggingFace | 384 | Gratuït (local) | 50+ | Prototips, poc VRAM |
nomic-embed-text |
Nomic | 768 | Gratuït (Ollama) | Multi | Local, sense API key |
📏 Mètriques de Similitud¶
Un vector store compara vectors usant una mètrica de distància. La més usada és la similitud cosinus:
import numpy as np
def similitud_cosinus(vec_a: list, vec_b: list) -> float:
"""
Mesura l'angle entre dos vectors.
1.0 = idèntics semànticament
0.0 = cap relació
-1.0 = significat oposat (poc freqüent en embeddings de text)
"""
a, b = np.array(vec_a), np.array(vec_b)
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# Exemple intuïtiu
v_gat = embeddings.embed_query("el gat menja peix")
v_felin = embeddings.embed_query("el felí s'alimenta de peixos")
v_cotxe = embeddings.embed_query("el vehicle circula per l'autopista")
print(similitud_cosinus(v_gat, v_felin)) # → ~0.92 (molt similar)
print(similitud_cosinus(v_gat, v_cotxe)) # → ~0.21 (poc relacionat)
Comparació de Mètriques¶
| Mètrica | Fórmula simplificada | Quan usar |
|---|---|---|
| Cosinus | angle entre vectors | Textos (longitud variable), la més usada |
| Euclidiana (L2) | distància en línia recta | Vectors normalitzats, imatges |
| Producte escalar | cos × magnitud | Quan la magnitud és rellevant |
| Manhattan (L1) | suma de diferències absolutes | Vectors dispersos |
🗂️ Vector Stores Principals¶
ChromaDB — El Vector Store Local¶
ChromaDB és la opció per defecte per a prototips i projectes locals. No requereix servidor extern.
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain_core.documents import Document
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# --- INDEXACIÓ: guardar documents ---
documents = [
Document(
page_content="El protocol TCP/IP és la base d'internet. Usa 4 capes: aplicació, transport, internet i accés a xarxa.",
metadata={"font": "manual_xarxes.pdf", "pagina": 12, "tema": "TCP/IP"}
),
Document(
page_content="DNS (Domain Name System) tradueix noms de domini a adreces IP. Els servidors arrel gestionen els dominis de nivell superior.",
metadata={"font": "manual_xarxes.pdf", "pagina": 45, "tema": "DNS"}
),
Document(
page_content="DHCP assigna adreces IP automàticament als dispositius d'una xarxa. Usa el procés DORA: Discover, Offer, Request, Acknowledge.",
metadata={"font": "manual_xarxes.pdf", "pagina": 78, "tema": "DHCP"}
),
]
# Crear i persistir la base de dades
db = Chroma.from_documents(
documents=documents,
embedding=embeddings,
persist_directory="./chroma_db", # Guarda al disc
collection_name="manual_xarxes"
)
print(f"Documents indexats: {db._collection.count()}") # → 3
# --- CERCA: recuperar documents rellevants ---
resultats = db.similarity_search_with_score(
query="com funciona la resolució de noms de domini",
k=2 # Retorna els 2 més similars
)
for doc, score in resultats:
print(f"Similitud: {score:.3f}")
print(f"Contingut: {doc.page_content[:80]}...")
print(f"Font: {doc.metadata['font']}, pàg. {doc.metadata['pagina']}")
print()
# → Similitud: 0.891 → DNS...
# → Similitud: 0.743 → TCP/IP...
# --- REUTILITZAR la base existent ---
db_existent = Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings,
collection_name="manual_xarxes"
)
Filtres per Metadades¶
Una funcionalitat clau dels vector stores és poder combinar cerca semàntica amb filtres exactes sobre les metadades:
# Cerca semàntica + filtre per metadades
resultats = db.similarity_search(
query="assignació automàtica d'adreces",
k=3,
filter={"tema": "DHCP"} # Filtra per metadada exacta
)
# Filtres complexos (suport varia per vector store)
resultats = db.similarity_search(
query="protocols de xarxa",
k=5,
filter={
"$and": [
{"font": {"$eq": "manual_xarxes.pdf"}},
{"pagina": {"$gte": 40}} # Pàgines >= 40
]
}
)
FAISS — Alta Performance Local¶
FAISS (Facebook AI Similarity Search) és una biblioteca optimitzada per a cerques de similitud molt ràpides en grans conjunts de dades:
from langchain_community.vectorstores import FAISS
# Crear des de documents
db_faiss = FAISS.from_documents(documents, embeddings)
# Guardar i carregar (format binari eficient)
db_faiss.save_local("./faiss_index")
db_faiss = FAISS.load_local(
"./faiss_index",
embeddings,
allow_dangerous_deserialization=True
)
# Merge de múltiples índexs (útil per a actualitzacions incrementals)
db_nous = FAISS.from_documents(nous_documents, embeddings)
db_faiss.merge_from(db_nous)
Quan usar FAISS vs ChromaDB:
| ChromaDB | FAISS | |
|---|---|---|
| Facilitat | Alta (API simple) | Mitja |
| Persistència | Automàtica | Manual (save/load) |
| Filtres metadades | Sí | Limitat |
| Velocitat (>1M docs) | Moderada | Molt alta |
| Casos d'ús | Prototips, <500K docs | Producció, >500K docs |
Pinecone — Vector Store al Núvol¶
Per a aplicacions de producció que requereixen escalabilitat i alta disponibilitat:
from langchain_pinecone import PineconeVectorStore
from pinecone import Pinecone, ServerlessSpec
import os
# Inicialitzar Pinecone
pc = Pinecone(api_key=os.environ["PINECONE_API_KEY"])
# Crear índex (una sola vegada)
pc.create_index(
name="manual-xarxes",
dimension=1536, # Ha de coincidir amb el model d'embedding
metric="cosine",
spec=ServerlessSpec(cloud="aws", region="us-east-1")
)
# Connectar i usar igual que ChromaDB
db_pinecone = PineconeVectorStore(
index_name="manual-xarxes",
embedding=embeddings,
namespace="v1" # Permet múltiples col·leccions al mateix índex
)
# Upsert (inserir o actualitzar)
db_pinecone.add_documents(documents)
# Cerca (mateixa interfície que ChromaDB/FAISS)
resultats = db_pinecone.similarity_search("protocols TCP", k=3)
pgvector — Vectors dins PostgreSQL¶
Ideal quan ja tens una infraestructura PostgreSQL i vols evitar una base de dades addicional:
from langchain_postgres import PGVector
CONNECTION_STRING = "postgresql+psycopg://user:pass@localhost:5432/empresa_db"
db_pg = PGVector(
embeddings=embeddings,
collection_name="documents_empresa",
connection=CONNECTION_STRING,
use_jsonb=True # Metadades com a JSONB per a filtres eficients
)
# Compatible amb totes les operacions estàndard de LangChain
db_pg.add_documents(documents)
resultats = db_pg.similarity_search("consulta", k=5)
Avantatge clau: pots fer JOINs entre la taula de vectors i les teves taules relacionals en una sola consulta SQL.
⚙️ Configuració Avançada del Retriever¶
Convertir un vector store en un retriever LangChain obre moltes opcions de configuració:
# Retriever bàsic
retriever_basic = db.as_retriever(
search_type="similarity", # "similarity", "mmr", "similarity_score_threshold"
search_kwargs={"k": 4}
)
# MMR (Maximal Marginal Relevance): evita resultats repetitius
retriever_mmr = db.as_retriever(
search_type="mmr",
search_kwargs={
"k": 4, # Nombre de resultats finals
"fetch_k": 20, # Candidats inicials a considerar
"lambda_mult": 0.5 # 0=màxima diversitat, 1=màxima similitud
}
)
# Threshold: només retorna si supera cert llindar de similitud
retriever_threshold = db.as_retriever(
search_type="similarity_score_threshold",
search_kwargs={"score_threshold": 0.75} # Descarta resultats poc rellevants
)
Retriever amb Compressió Contextual¶
Filtra i comprimeix els documents recuperats per eliminar informació irrellevant:
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(model="claude-haiku-4-5-20251001") # Model lleuger per a compressió
# El compressor extreu NOMÉS la part del document rellevant per a la pregunta
compressor = LLMChainExtractor.from_llm(llm)
retriever_comprimit = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=retriever_basic
)
# Exemple: document original té 500 paraules, el compressor extreu les 30 rellevants
docs = retriever_comprimit.invoke("Quants ports usa TCP?")
# → Retorna fragment comprimit, no el document sencer
🏗️ Vector Store en un Agent RAG Complet¶
import os
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.chains import RetrievalQA
from langchain_core.prompts import ChatPromptTemplate
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 1. Carregar i dividir el document
loader = PyPDFLoader("manual_xarxes.pdf")
documents_raw = loader.load()
splitter = RecursiveCharacterTextSplitter(
chunk_size=512,
chunk_overlap=64,
separators=["\n\n", "\n", ".", " "]
)
chunks = splitter.split_documents(documents_raw)
print(f"Document dividit en {len(chunks)} fragments")
# 2. Crear el vector store
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
db = Chroma.from_documents(
chunks,
embeddings,
persist_directory="./chroma_manual"
)
# 3. Crear el retriever amb MMR per diversitat
retriever = db.as_retriever(
search_type="mmr",
search_kwargs={"k": 4, "fetch_k": 15}
)
# 4. Cadena RAG
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
prompt_rag = ChatPromptTemplate.from_template("""
Respon la pregunta basant-te ÚNICAMENT en el context proporcionat.
Si la informació no és al context, digues "No trobo aquesta informació al manual."
Context:
{context}
Pregunta: {question}
Resposta en català:
""")
cadena_rag = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
chain_type_kwargs={"prompt": prompt_rag},
return_source_documents=True
)
# 5. Consultar
resultat = cadena_rag.invoke({"query": "Explica el procés DORA de DHCP"})
print(resultat["result"])
print("\nFonts consultades:")
for doc in resultat["source_documents"]:
print(f" - {doc.metadata.get('source', 'desconegut')}, pàg. {doc.metadata.get('page', '?')}")
📊 Comparativa de Vector Stores¶
| ChromaDB | FAISS | Pinecone | pgvector | Weaviate | |
|---|---|---|---|---|---|
| Tipus | Local/núvol | Local | Núvol | Local/núvol | Local/núvol |
| Escalabilitat | Fins a ~1M docs | Milions | Il·limitada | Depèn de PG | Alta |
| Filtres metadata | ✅ | Limitat | ✅ | ✅ (SQL) | ✅ |
| Cerca híbrida | ❌ | ❌ | ✅ | ✅ | ✅ |
| Cost | Gratuït | Gratuït | Des de $0.096/h | Gratuït | Gratuït/SaaS |
| Integració LangChain | ✅ Nativa | ✅ Nativa | ✅ Nativa | ✅ Nativa | ✅ Nativa |
| Ideal per a | Prototips | Prod. local | Prod. SaaS | Ja tens PG | Multimodal |
Recomanació per al curs
Usa ChromaDB per a totes les pràctiques. Si necessites producció real sense infraestructura pròpia, considera Pinecone. Si ja tens PostgreSQL, pgvector evita afegir una nova base de dades al sistema.
🔑 Conceptes Clau d'aquesta Secció¶
| Terme | Definició |
|---|---|
| Embedding | Representació vectorial densa d'un text que captura el seu significat semàntic. |
| Similitud cosinus | Mètrica que mesura l'angle entre dos vectors; la més usada per a similitud textual. |
| Vector store | Base de dades especialitzada en emmagatzemar i cercar vectors per similitud. |
| MMR | Maximal Marginal Relevance. Algorisme que retorna resultats rellevants i diversos alhora. |
| Score threshold | Llindar de similitud mínima per retornar un resultat (evita recuperar fragments irrellevants). |
| Compressió contextual | Tècnica que usa un LLM per extreure únicament el fragment rellevant d'un document recuperat. |
| Cerca híbrida | Combinació de cerca semàntica (vectors) i cerca per paraules clau (BM25) per millors resultats. |
| Upsert | Operació que insereix un document si no existeix o l'actualitza si ja hi és. |
✅ Activitats de Consolidació¶
Exercici 3.4.1 — Explorar Similituds
Crea un script que:
- Generi embeddings de 10 frases (5 sobre xarxes, 5 sobre cuina)
- Calculi la matriu de similituds cosinus 10×10
- Visualitzi la matriu com a mapa de calor (
matplotlib.pyplot.imshow) - Identifica quins parells de frases de temes diferents tenen similitud > 0.5
Exercici 3.4.2 — RAG sobre Documentació Tècnica
Construeix un sistema RAG que indexi la documentació oficial de Python (o qualsevol manual en PDF):
- Carrega i divideix el document en chunks de 512 tokens amb 64 de solapament
- Indexa a ChromaDB amb
text-embedding-3-small - Implementa un retriever MMR (k=4, fetch_k=20)
- Prova les consultes amb i sense threshold i compara la qualitat
Exercici 3.4.3 — Benchmark de Retrievers
Compara tres configuracions de retriever sobre el mateix vector store:
similarityamb k=3mmramb k=3, lambda=0.5similarity_score_thresholdamb threshold=0.75
Per a cada configuració: mesura el temps de resposta, el nombre de documents retornats i la qualitat subjectiva de la resposta final de l'agent.
📚 Lectures Complementàries¶
- LangChain — Vector Store Integrations
- Johnson et al. (2019) — Billion-scale similarity search with GPUs (FAISS). arXiv:1702.08734
- ChromaDB — Documentació Oficial
- Pinecone — Learning Center: Vector Databases
- Hugging Face — MTEB Leaderboard — Rànquing actualitzat de models d'embedding