El primer instinto cuando necesitas búsqueda vectorial para RAG suele ser “añado Pinecone o Weaviate”. Si ya usas PostgreSQL, pgvector te da búsqueda vectorial sin añadir otra base de datos al stack, con las ventajas de ACID, joins SQL, y una sola infraestructura que mantener.
No es la solución para todo, pero para muchos casos es la más pragmática.
Instalación
# En Debian/Ubuntu
sudo apt install postgresql-16-pgvector
# Con Docker
docker run -e POSTGRES_PASSWORD=password ankane/pgvector
-- En tu base de datos
CREATE EXTENSION vector;
Crear la tabla con embeddings
CREATE TABLE documentos (
id BIGSERIAL PRIMARY KEY,
contenido TEXT NOT NULL,
metadata JSONB,
embedding vector(1536), -- dimensión según el modelo de embedding
created_at TIMESTAMPTZ DEFAULT NOW()
);
La dimensión del vector debe coincidir con tu modelo de embedding:
- text-embedding-3-small (OpenAI): 1536
- text-embedding-ada-002 (OpenAI): 1536
- jina-embeddings-v2 (Jina): 768
- nomic-embed-text: 768
Indexación
Sin índice, pgvector hace búsqueda exacta (costosa para colecciones grandes). Con índice, usa aproximación:
-- IVFFlat: bueno para la mayoría de casos
-- lists = sqrt(número de filas) es una buena heurística
CREATE INDEX ON documentos
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- HNSW: mejor recall, más memoria, más lento de construir
CREATE INDEX ON documentos
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
IVFFlat: mejor para datasets que cambian frecuentemente (el índice se actualiza bien). Peor recall a velocidades iguales que HNSW.
HNSW: mejor recall y velocidad de consulta. Más memoria y tiempo de construcción inicial. Ideal para datasets que cambian poco.
Búsqueda vectorial
-- Top 5 documentos más similares a un embedding dado
SELECT id, contenido, metadata,
1 - (embedding <=> '[0.1, 0.2, ...]'::vector) AS similitud
FROM documentos
ORDER BY embedding <=> '[0.1, 0.2, ...]'::vector
LIMIT 5;
Los operadores disponibles:
<=>: distancia coseno (para texto, el más habitual)<->: distancia euclidiana<#>: producto interno negativo
Implementación en Python
import psycopg2
from openai import OpenAI
import numpy as np
openai_client = OpenAI()
conn = psycopg2.connect("postgresql://user:pass@localhost/dbname")
def get_embedding(text: str) -> list[float]:
response = openai_client.embeddings.create(
model="text-embedding-3-small",
input=text
)
return response.data[0].embedding
def insert_document(contenido: str, metadata: dict = None):
embedding = get_embedding(contenido)
with conn.cursor() as cur:
cur.execute(
"INSERT INTO documentos (contenido, metadata, embedding) VALUES (%s, %s, %s)",
(contenido, psycopg2.extras.Json(metadata), embedding)
)
conn.commit()
def search_similar(query: str, k: int = 5) -> list[dict]:
query_embedding = get_embedding(query)
with conn.cursor() as cur:
cur.execute("""
SELECT id, contenido, metadata,
1 - (embedding <=> %s::vector) AS similitud
FROM documentos
ORDER BY embedding <=> %s::vector
LIMIT %s
""", (query_embedding, query_embedding, k))
rows = cur.fetchall()
return [
{"id": r[0], "contenido": r[1], "metadata": r[2], "similitud": r[3]}
for r in rows
]
Búsqueda híbrida: vectorial + texto completo
Una ventaja de estar en PostgreSQL: puedes combinar búsqueda vectorial con búsqueda de texto completo en una sola query:
SELECT id, contenido,
(1 - (embedding <=> query_embedding)) * 0.7 +
ts_rank(to_tsvector('spanish', contenido), plainto_tsquery('spanish', 'consulta')) * 0.3
AS score_combinado
FROM documentos,
(SELECT '[embedding aquí]'::vector AS query_embedding) params
WHERE to_tsvector('spanish', contenido) @@ plainto_tsquery('spanish', 'consulta')
OR (1 - (embedding <=> query_embedding)) > 0.7
ORDER BY score_combinado DESC
LIMIT 10;
Esto implementa búsqueda híbrida sin necesidad de un sistema de búsqueda adicional como Elasticsearch.
Cuándo usar pgvector vs base de datos vectorial dedicada
Usa pgvector cuando:
- Ya tienes PostgreSQL en tu stack
- Tu colección es de tamaño moderado (<10M vectores)
- Necesitas combinar búsqueda vectorial con queries SQL complejas
- Quieres simplicidad operacional
Considera Pinecone/Weaviate/Qdrant cuando:
- Escala muy grande (>100M vectores)
- Necesitas filtrado muy rápido sobre metadatos a escala
- Tu caso de uso es puramente búsqueda vectorial sin joins SQL
- Necesitas características específicas (multi-tenancy nativa, etc.)
Para la mayoría de aplicaciones de RAG con bases de conocimiento típicas (1K-1M documentos), pgvector es la opción más pragmática.
pgvector v0.7+ requerido para índices HNSW. PostgreSQL 15+ recomendado.