Inicio / Artículos

Búsqueda semántica en PostgreSQL con pgvector y Node.js, sin necesidad de LLM

Búsqueda semántica en PostgreSQL con pgvector y Node.js, sin LLM: embeddings de MiniLM, chunks, un índice IVFFlat, operadores de distancia y cómo leer las puntuaciones.

Editor: REACTAPP.TOP8 min de lectura1884 palabras

Fuentes: Talk to Your Database Like a Human — No LLM Required: Semantic Search with Node.js + PostgreSQL

Una base de datos puede responder a preguntas formuladas en inglés corriente sin necesidad de ningún modelo de lenguaje en el proceso. Lo que lo hace posible es la búsqueda vectorial. Con Node.js, PostgreSQL y la extensión pgvector, se puede crear un sistema que haga coincidencias basadas en la intención en lugar de en palabras clave, y el único modelo utilizado es un pequeño modelo de embedding que convierte el texto en números.

Las bases de datos relacionales siempre han sido excelentes para almacenar y recuperar datos, siempre y cuando se formule la pregunta exacta. Las coincidencias exactas, los filtros y las consultas estructuradas son poderosos pero limitados. Las bases de datos vectoriales parecen abordar un problema diferente: buscar contenido no estructurado según su significado, y no según las palabras que contiene.

Cuatro términos que vale la pena aclarar

Token. Es la pieza de texto más pequeña con la que trabaja un modelo. Los tokens no siempre son palabras completas. Un token puede ser una palabra entera como “hello”, un fragmento (por lo que “unbelievable” podría convertirse en “un”, “believ” y “able”), un único carácter como “!”, o un poco de puntuación o espacio en blanco. Una frase como “ChatGPT es genial” podría dividirse en “Chat”, “G”, “PT”, “es” y “genial”.

Chunk. Es una porción pequeña y autónoma de un documento más grande, generalmente unas pocas oraciones o un párrafo. Incluir todo el documento de una sola vez no funciona bien, por lo que primero se divide en chunks. El uso de chunks acelera el procesamiento, mejora la precisión de la búsqueda y mantiene los resultados relevantes para la consulta.

Embedding. Un sustituto numérico de un texto, imagen o audio que captura su significado. Un modelo de aprendizaje automático, como MiniLM, lo genera. Puedes considerar un embedding como la idea que subyace a los datos.

Vector. La forma en que se almacena realmente un embedding: un arreglo de números.

[0.2, -0.5, 0.9, ...]

Esa es la estructura que se mantiene en memoria y en PostgreSQL una vez instalada una extensión como pgvector.

En resumen, la idea es la siguiente: las palabras, oraciones o imágenes se convierten en embeddings. Las cosas con significados similares terminan con vectores que se encuentran cerca unos de otros, mientras que las cosas no relacionadas quedan más separadas. Una cadena como “Hello world” se transforma en una larga lista de números flotantes:

// "Hello world" turns into this embedding:
const embedding = [0.01, -0.42, 0.15, ..., 0.99]; // ← this is a vector

Y esa lista se guarda junto al texto original:

INSERT INTO documents (content, embedding)
VALUES ('Hello world', embedding);

Por qué una base de datos relacional no es suficiente

Un RDBMS tradicional funciona con tablas, filas y columnas. Maneja solicitudes como “todos los tickets con estado OPEN” o “usuarios creados después de 2024” sin esfuerzo. Sin embargo, las aplicaciones modernas también tratan con documentos, conversaciones, imágenes y audio. La búsqueda por coincidencia exacta no sirve de mucho con estos tipos de datos.

Una base de datos vectorial almacena embeddings junto con los datos y encuentra elementos por similitud y contexto. Así, en lugar de solicitar documentos que contengan la palabra “error”, se pueden buscar documentos sobre problemas de autenticación, y se obtendrán incluso cuando “error” nunca aparezca. La comparación es una distancia matemática entre vectores, no una coincidencia de texto.

El flujo habitual es el siguiente:

  1. Llega el contenido en bruto: documentos, tickets o cualquier otra cosa.
  2. Un modelo de embedding lo convierte en vectores.
  3. Los vectores se almacenan, por ejemplo, en PostgreSQL con pgvector.
  4. Un índice de vecino más cercano aproximado (ANN) acelera las búsquedas.
  5. La consulta del usuario también se convierte en un vector.
  6. La base de datos devuelve los vectores más cercanos a ella, es decir, los de significado más similar.

Esto permite:

  • Búsqueda semántica, basada en el significado de una consulta y no solo en sus palabras exactas.
  • Memoria a largo plazo para un LLM, al conservar interacciones, documentos o hechos anteriores que el modelo puede recordar aunque no estuvieran en sus datos de entrenamiento.
  • Recomendaciones, al encontrar elementos o comportamientos que se parecen entre sí en el espacio de embedding.
  • Búsqueda de similitud en texto, imágenes, audio o video.

PostgreSQL como almacén de vectores

No es necesario adoptar una nueva base de datos para todo esto. Con pgvector, PostgreSQL puede almacenar embeddings directamente, ejecutar consultas de similitud y utilizar índices como IVFFlat para mantener una alta velocidad. La base de datos que ya utiliza simplemente adquiere una nueva capacidad: pasar de almacenar datos a poder comparar su significado. La búsqueda deja de centrarse en la coincidencia de palabras y comienza a centrarse en la coincidencia de significados.

Configuración paso a paso

1. Instale PostgreSQL e active pgvector. Instale PostgreSQL desde la página oficial de descargas, conéctese y active la extensión:

CREATE EXTENSION IF NOT EXISTS vector;

2. Cree la tabla de documentos. Esta contiene el texto en bruto y su representación vectorial. El 384 mencionado en la definición corresponde al tamaño de salida del modelo all-MiniLM-L6-v2.

-- Note: 384 is the embedding size output by all-MiniLM-L6-v2
CREATE TABLE documents (
  id SERIAL PRIMARY KEY,
  content TEXT,
  embedding VECTOR(384)
);

3. Agregar un índice vectorial. Para mantener eficiente la búsqueda de similitud, utilice un índice de vecino más cercano aproximado:

CREATE INDEX documents_embedding_idx
ON documents
USING ivfflat (embedding vector_l2_ops)
WITH (lists = 100);

Esto selecciona IVFFlat para búsquedas aproximadas rápidas, mide con la distancia L2 (euclidiana) y divide los vectores en 100 clústeres.

4. Alinear el operador de consulta con el índice. El operador de su consulta debe coincidir con la clase de operadores con la que construyó el índice. pgvector ofrece varios operadores, cada uno emparejado con su propia clase. El ejemplo aquí utiliza <#> y configura vector_l2_ops. Tenga en cuenta que en la documentación de pgvector la distancia L2 normalmente se representa con <->, mientras que <#> corresponde al producto interno negativo, así que verifique que su consulta e índice estén alineados.

5. Ejecutar el proyecto. Un ejemplo funcional completo se encuentra en un repositorio de GitHub llamado vectorgarquer/vectorized-database-embeddings. Clónelo o cree una rama a partir de él, y luego instale las dependencias necesarias:

npm install

Copie .env.example a un archivo .env en la raíz del proyecto y complete los detalles de conexión a PostgreSQL. Luego, inícielo:

npm start

6. Observe cómo realiza la búsqueda. Una vez en marcha, el programa se conecta, inserta los embeddings y ejecuta búsquedas semánticas sin necesidad de más entrada. A partir de ahí puedes proporcionarle tus propios datos y consultas. La búsqueda de ejemplo es “¿Qué significa la IA generativa?”, y los resultados que encuentra en la base de datos se muestran de esta manera:

Search Results:

[1] Distance: -0.7562
n be represented in another modality such as text.
2.2  A Model-, System-, and Application-Level View
of Generative AI
2.2.1 Model-Level View
A  generative  AI  model  is  a  type  of  machine  learning
architecture  that  uses  AI  algorithms  to  create  novel  data
instances,  drawing  upon  the  patterns  and  relationships
observed in the training data. A generative AI model is of
critically central  yet incomplete  nature,  as it requires fur-
ther  fine-tuning  to  specific   tasks   thro


[2] Distance: -0.7562
n be represented in another modality such as text.
2.2  A Model-, System-, and Application-Level View
of Generative AI
2.2.1 Model-Level View
A  generative  AI  model  is  a  type  of  machine  learning
architecture  that  uses  AI  algorithms  to  create  novel  data
instances,  drawing  upon  the  patterns  and  relationships
observed in the training data. A generative AI model is of
critically central  yet incomplete  nature,  as it requires fur-
ther  fine-tuning  to  specific   tasks   thro


[3] Distance: -0.7562
n be represented in another modality such as text.
2.2  A Model-, System-, and Application-Level View
of Generative AI
2.2.1 Model-Level View
A  generative  AI  model  is  a  type  of  machine  learning
architecture  that  uses  AI  algorithms  to  create  novel  data
instances,  drawing  upon  the  patterns  and  relationships
observed in the training data. A generative AI model is of
critically central  yet incomplete  nature,  as it requires fur-
ther  fine-tuning  to  specific   tasks   thro


[4] Distance: -0.7562
n be represented in another modality such as text.
2.2  A Model-, System-, and Application-Level View
of Generative AI
2.2.1 Model-Level View
A  generative  AI  model  is  a  type  of  machine  learning
architecture  that  uses  AI  algorithms  to  create  novel  data
instances,  drawing  upon  the  patterns  and  relationships
observed in the training data. A generative AI model is of
critically central  yet incomplete  nature,  as it requires fur-
ther  fine-tuning  to  specific   tasks   thro


[5] Distance: -0.7094

assistants,  personalized  education  and  service,  and  digital
art. As such, generative AI has substantial implications for
BISE  practitioners  and  scholars  as  an  interdisciplinary
research community. In our Catchword article, we offered
a conceptualization of the principles of generative AI along
a model-, system-, and application-level view as well as a
social-technical  view  and  described  limitations  of  current
generative   AI.   Ultimately,   we   provided   an   impactful
rese

Leer la salida

Después de ejecutarlo, se obtiene una lista con este formato:

Search Results:

[1] Distance: -0.7562
...
[5] Distance: -0.7094
...

La distancia indica cuán cerca está un fragmento almacenado del embedding de la consulta. Tanto el fragmento como la consulta se convierten en vectores, y PostgreSQL con pgvector mide cuán lejos están entre sí. La regla es sencilla: cuanto menor sea la distancia, más similar será el contenido.

Los valores son negativos, como por ejemplo:

-0.7562

Eso se debe a que el operador <#> devuelve un valor negado para que un ordenamiento ordinario coloque primero las filas más cercanas. En la práctica, un número más negativo indica una coincidencia más cercana y relevante, mientras que un número más cercano a cero indica una coincidencia menos fuerte.

Al observar la clasificación, los resultados del 1 al 4 comparten la misma puntuación de -0.7562, por lo que son igualmente relevantes para la consulta. El resultado 5 tiene una puntuación de -0.7094, lo cual sigue siendo relevante pero con menos similitud.

También podrías notar varios resultados con texto casi idéntico. Eso es esperado. El documento de origen se dividió en fragmentos; los fragmentos adyacentes se superponen o repiten contenido similar, y cada uno tiene su propio embedding.

Qué muestra el resultado

Aunque no había un LLM en el momento de la consulta, el sistema seguía comprendiendo el significado y no solo las palabras clave, recuperaba el texto que se ajustaba al contexto y lo clasificaba según la similitud vectorial. Los fragmentos devueltos estaban relacionados con modelos de IA generativa, una visión a nivel de modelo y arquitecturas de aprendizaje automático, por lo que la búsqueda funcionaba claramente a nivel de conceptos en lugar de palabras exactas.

El resultado es un motor de búsqueda semántica que funciona dentro de PostgreSQL. No se necesita ningún LLM para realizar consultas, no existe una base de datos vectorial separada, y los embeddings junto con pgvector se encargan de todo el trabajo.

Leer más

  • ¿Qué realmente hace funcionar una red neuronal? Los compiladores de IA detrás de Python — Sigue a un modelo desde una expresión en Python a través de grafos de cálculo, fusión de operadores, representaciones intermedias al estilo MLIR y su conversión en código para CPU, GPU y aceleradores.
  • Su modelo semántico es la API: proporcionando métricas certificadas a los agentes — Por qué los agentes de IA inventan definiciones privadas de ingresos e inventario, y cómo un modelo semántico regulado con detalles precisos, propietarios y mecanismos de rechazo les ofrece un contrato único.
  • RAG sin vectores: cómo PageIndex lee documentos sin embeddings — La búsqueda vectorial divide los documentos largos en fragmentos y pierde el contexto. Vea cómo un índice en forma de árbol basado en razonamiento encuentra respuestas en informes complejos, con código.
  • RAG avanzado en Milvus: búsqueda híbrida, HyDE, reescritura de consultas y reclasificación — Construya un pipeline RAG con Milvus paso a paso: vectores densos y BM25, búsqueda híbrida RRF, filtros de metadatos, reescritura de consultas, HyDE, descomposición y reclasificación con cross-encoder.