Búsqueda Híbrida y Embeddings en Supabase: Cómo construir un sistema RAG en producción
Hace poco estaba revisando el motor de búsqueda interna de una plataforma técnica. El equipo había montado un sistema de Generación Aumentada por Recuperación (RAG) impecable basado únicamente en embeddings vectoriales almacenados en PostgreSQL.
Si buscabas "¿Cómo corregir errores de autenticación?", el sistema devolvía los artículos de documentación exactos. La búsqueda semántica funcionaba a las mil maravillas.
Pero el desastre ocurrió cuando un usuario buscó el código de error numérico exacto: ERR_401_EXPIRED_TOKEN.
El motor de búsqueda vectorial devolvió artículos sobre contraseñas olvidadas y verificación en dos pasos, pero omitió el artículo que contenía la constante exacta ERR_401_EXPIRED_TOKEN.
¿Por qué ocurrió esto? Porque las búsquedas vectoriales entienden el significado de las frases, pero son pésimas encontrando términos exactos, códigos de producto, nombres de variables o números de serie.
La solución definitiva para llevar sistemas RAG a producción se llama Búsqueda Híbrida (Hybrid Search).
Por qué la Búsqueda Vectorial Pura Falla en Producción
Los modelos de embeddings transforman fragmentos de texto en vectores numéricos dentro de un espacio multidimensional.
- Búsqueda Vectorial (Cosimilitud / Distancia Euclídea): Excelente para capturar conceptos relacionados. Si buscas "vehículo ecológico", encontrará documentos sobre "coches eléctricos".
- Búsqueda por Texto Completo (Full-Text Search / BM25): Excelente para palabras clave exactas. Si buscas "SKU-9942", encontrará la fila que contiene esa cadena sin intentar interpretar su significado.
Un sistema RAG profesional necesita combinar ambas estrategias.
┌──────────────────────────────────────────┐
│ Consulta del Usuario: "ERR_401 token" │
└────────────────────┬─────────────────────┘
│
┌──────────────────────────┴──────────────────────────┐
▼ ▼
┌──────────────────────────┐ ┌──────────────────────────┐
│ Búsqueda Vectorial │ │ Búsqueda Texto Completo │
│ (pgvector / HNSW) │ │ (tsvector / BM25) │
└───────────┬──────────────┘ └───────────┬──────────────┘
│ │
└──────────────────────────┬───────────────────────────────┘
▼
┌──────────────────────────────────────────┐
│ Fusion de Rangos Recíprocos (RRF en SQL) │
└────────────────────┬─────────────────────┘
▼
┌──────────────────────────────────────────┐
│ Contexto Ideal para el Modelo LLM │
└──────────────────────────────────────────┘
Implementación de Búsqueda Híbrida en Supabase & PostgreSQL
Supabase incluye la extensión pgvector sobre PostgreSQL nativo. Podemos implementar Búsqueda Híbrida directamente en la base de datos con una función SQL almacenada que ejecute Reciprocal Rank Fusion (RRF).
1. Habilitar la extensión y crear la tabla con vector y tsvector
-- Habilitar la extensión pgvector
CREATE EXTENSION IF NOT EXISTS vector;
-- Tabla de documentos para RAG
CREATE TABLE documentos (
id BIGSERIAL PRIMARY KEY,
contenido TEXT NOT NULL,
embedding VECTOR(1536), -- Dimensión para text-embedding-3-small de OpenAI
fts TSVECTOR GENERATED ALWAYS AS (to_tsvector(39;spanish39;, contenido)) STORED
);
-- Crear índice vectorial HNSW y de texto completo GIN
CREATE INDEX idx_documentos_embedding ON documentos USING hnsw (embedding vector_cosine_ops);
CREATE INDEX idx_documentos_fts ON documentos USING gin (fts);
2. Función Almacenada RPC de Fusión Híbrida (RRF)
CREATE OR REPLACE FUNCTION busqueda_hibrida_documentos(
query_text TEXT,
query_embedding VECTOR(1536),
match_count INT DEFAULT 5,
rrf_k INT DEFAULT 60
)
RETURNS TABLE (id BIGINT, contenido TEXT, score FLOAT)
LANGUAGE sql AS $$
WITH full_text AS (
SELECT id, ROW_NUMBER() OVER (ORDER BY ts_rank_cd(fts, websearch_to_tsquery(39;spanish39;, query_text)) DESC) AS rank
FROM documentos
WHERE fts @@ websearch_to_tsquery(39;spanish39;, query_text)
LIMIT 20
),
vector_search AS (
SELECT id, ROW_NUMBER() OVER (ORDER BY embedding <=> query_embedding) AS rank
FROM documentos
ORDER BY embedding <=> query_embedding
LIMIT 20
)
SELECT
d.id,
d.contenido,
COALESCE(1.0 / (rrf_k + ft.rank), 0.0) + COALESCE(1.0 / (rrf_k + vs.rank), 0.0) AS score
FROM documentos d
LEFT JOIN full_text ft ON d.id = ft.id
LEFT JOIN vector_search vs ON d.id = vs.id
WHERE ft.id IS NOT NULL OR vs.id IS NOT NULL
ORDER BY score DESC
LIMIT match_count;
$$;
3. Invocación desde TypeScript
import { createClient } from 39;@supabase/supabase-js39;;
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
async function buscarContextoRAG(query: string, embedding: number[]) {
const { data, error } = await supabase.rpc(39;busqueda_hibrida_documentos39;, {
query_text: query,
query_embedding: embedding,
match_count: 5,
});
if (error) throw new Error(`Fallo en la búsqueda RAG: ${error.message}`);
return data;
}
Al aplicar programación defensiva en TypeScript, aseguras que los vectores devueltos cumplan estrictamente con las dimensiones de tu modelo de embedding antes de invocar la consulta RPC.
Optimización de RAG y Control de Tokens
- Aislamiento de Grafos: Combina la búsqueda híbrida con principios de graph engineering para que la base de datos devuelva únicamente los nodos de información directamente relacionados con la consulta.
- Presupuesto de Tokens: Filtrar los 5 mejores resultados consolidados por la función RRF reduce drásticamente el volumen de datos enviado en la ventana de contexto. Como analizamos en nuestro post sobre el coste de subagentes al cambiar de modelo, reducir el exceso de contexto optimiza los tiempos de respuesta y ahorra costes en tu API de IA.
La Búsqueda Híbrida combina lo mejor de dos mundos: la comprensión conceptual de los embeddings y la precisión milimétrica del texto completo.
Si quieres dominar el desarrollo de sistemas RAG y arquitecturas backend avanzadas con PostgreSQL y Supabase, explora los Cursos de Dominicode. Y si quieres construir productos reales de IA junto a otros ingenieros senior, te esperamos en Dominicode Labs.
Preguntas frecuentes
¿Por qué usar pgvector en Supabase en lugar de una base de datos vectorial dedicada como Pinecone o Chroma?
Utilizar pgvector en PostgreSQL/Supabase te permite mantener todos tus datos relacionales, usuarios y vectores en la misma base de datos. Esto elimina la necesidad de sincronizar dos bases de datos distintas, reduce los costes de infraestructura y permite hacer JOINs nativos entre tablas relacionales y embeddings.
¿Qué es el valor rrf_k en la función Reciprocal Rank Fusion?
rrf_k es una constante de suavizado (por defecto 60) utilizada en el algoritmo Reciprocal Rank Fusion. Sirve para evitar que un documento clasificado en la posición #1 en un método domine desproporcionadamente sobre un documento que quedó en posición #2 en ambos métodos.
¿Qué dimensión debe tener la columna VECTOR en PostgreSQL?
La dimensión depende exclusivamente del modelo de embeddings que utilices. Por ejemplo, text-embedding-3-small de OpenAI usa 1536 dimensiones, text-embedding-3-large usa 3072 dimensiones, y modelos locales ligeros como all-MiniLM-L6-v2 usan 384 dimensiones.
¿Cómo afecta el índice HNSW al rendimiento de inserción en Supabase?
El índice HNSW (Hierarchical Navigable Small World) ofrece consultas de búsqueda vectorial ultrarrápidas en tiempo de lectura, a costa de un ligero aumento en el tiempo de inserción de filas. Para aplicaciones con muchas lecturas y pocas escrituras masivas, HNSW es la opción óptima frente al índice IVFFlat tradicional.
Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.
