Inicio / Artículos

Dentro de los bancos de datos vectoriales para RAG: almacenamiento de fragmentos, HNSW, IVF y reclasificación

Cómo almacenan los bancos de datos vectoriales RAG los fragmentos, buscan millones de vectores con HNSW e IVF, añaden búsqueda híbrida y reclasificación, y comparan las medidas de similitud.

Editor: REACTAPP.TOP9 min de lectura1968 palabras

Fuentes: Inside Vector Databases for RAG: From Chunk Storage to HNSW & IVF Search

Un sistema de generación reforzada por recuperación debe responder a una pregunta práctica desde el principio: ¿dónde se almacenan los fragmentos de texto? La respuesta más común, y la que utilizan la mayoría de los sistemas en producción, es guardar cada fragmento dentro de la base de datos vectorial, justo al lado de su representación vectorial.

Así es un registro almacenado

En configuraciones basadas en FAISS, Pinecone o Weaviate, cada registro incluye tres elementos: el vector de representación, el texto del fragmento y algunos metadatos.

{
        "embedding": [0.123, 0.456, ...],
        "text": "Transformer models are powerful...",
        "metadata": {
        "doc_id": "doc1",
        "page": 5
        }
}

Mantener el fragmento y su vector juntos tiene tres ventajas. La búsqueda es rápida porque el texto coincidente se devuelve en la misma respuesta. La arquitectura se mantiene simple, sin necesidad de consultar un segundo almacenamiento. Además, la latencia se mantiene baja, lo cual es importante cuando un LLM espera el resultado en tiempo real.

La recuperación de información se lleva a cabo en cinco pasos: un usuario hace una pregunta, esta se convierte en un embedding, la base de datos encuentra los embeddings almacenados más cercanos, se devuelve directamente el texto del fragmento coincidente, y el LLM escribe su respuesta a partir de ese texto. No hay búsquedas adicionales en ningún momento.

Cómo se mantiene rápida la búsqueda

La descripción habitual en una sola línea es que una base de datos vectorial encuentra embeddings similares mediante la similitud cosénica, el producto escalar o la distancia euclidiana. Eso es cierto, pero omite los aspectos técnicos interesantes. Con millones o miles de millones de vectores, ¿cómo es posible que una consulta se resuelva en milisegundos?

El enfoque ingenuo es la fuerza bruta: se compara la consulta con cada vector almacenado. Ofrece una precisión perfecta pero una velocidad terrible. Un millón de vectores implica un millón de comparaciones por cada consulta, y eso no es escalable.

Los sistemas reales utilizan búsquedas de vecino más cercano aproximado (ANN). La idea es reducir primero el espacio de búsqueda y luego examinar solo un pequeño subconjunto de vectores, aceptando una ligera pérdida de precisión a cambio de una gran mejora en velocidad. ANN se implementa mediante estructuras de índice especializadas. Por ejemplo, FAISS proporciona índices de este tipo, y allí también están disponibles HNSW e IVF.

HNSW, un grafo en capas

HNSW es la abreviatura de Hierarchical Navigable Small World. Se basa en grafos, y Pinecone y Weaviate lo utilizan internamente. Cada vector se convierte en un nodo, y cada nodo está conectado a vecinos similares. Una búsqueda consiste en recorrer ese grafo.

El gráfico tiene varias capas. La capa superior contiene muy pocos nodos y ofrece una visión general, mientras que la capa inferior muestra cada nodo en detalle. Una comparación entre autopistas y calles locales funciona bien en este caso.

Su construcción se realiza de la siguiente manera: cuando se inserta un embedding, se le asigna un nivel aleatorio, de modo que la mayoría de los nodos quedan únicamente en la capa inferior y solo unos pocos escasos llegan a la superior. Dentro de cada nivel, el nodo se conecta con sus vecinos más cercanos, medidos mediante el coseno, el producto escalar o la distancia euclidiana. Lo que se obtiene al final es una red en la que los vectores cercanos están estrechamente conectados y los distantes solo de forma superficial.

La búsqueda comienza en la capa superior. La consulta encuentra allí el nodo que mejor se ajusta, pasa al nivel siguiente a partir de ese nodo y repite este proceso de selección óptima. Esto continúa hasta llegar a la capa inferior. En las capas superiores y medias, la búsqueda es estrictamente basada en la selección óptima: se va al mejor vecino y se detiene cuando no hay mejoras, para luego descender. La capa inferior es más exploratoria. Una cola de prioridades con candidatos comienza desde el nodo de entrada, y la búsqueda sigue añadiendo coincidencias adecuadas y examinando varios vecinos al mismo tiempo.

En la práctica, dos parámetros son importantes:

  • M representa la cantidad de conexiones por nodo. Un valor mayor de M aumenta la tasa de recuperación, pero consume más memoria y tiempo de procesamiento.
  • efSearch indica cuántos candidatos se exploran durante una consulta. Un valor mayor mejora la tasa de recuperación, pero a costa de una mayor latencia.

En pocas palabras, M determina la calidad del índice, mientras que efSearch sacrifica la precisión en favor de la velocidad al realizar búsquedas.

IVF, clustering primero

El IVF (índice de archivo invertido) es común en FAISS y adopta un enfoque completamente diferente. No crea un grafo; en su lugar, realiza clustering, agrupando vectores similares y buscando únicamente en los grupos relevantes. Una biblioteca es una buena analogía: los libros están ordenados por categoría, por lo que nadie tiene que revisar cada estante.

Al insertar datos, un algoritmo de clustering como k-means divide los vectores en N clústeres. Cada clúster tiene un centroide, y cada vector se asigna al más cercano. Al realizar una búsqueda, el embedding se compara con todos los centroides, se elige el clúster más cercano y se evalúa la consulta contra cada vector dentro de ese clúster para obtener los resultados principales.

Los dos métodos difieren en cuanto al uso de memoria. IVF almacena menos conexiones, por lo que consume menos recursos. HNSW mantiene un grafo con muchas aristas por nodo, por lo que utiliza más memoria.

Donde la búsqueda vectorial pura falla

Los embeddings capturan el significado, el contexto y la cercanía conceptual. Son menos efectivos con palabras clave exactas, números e IDs. Al buscar “kafka error code 504”, un índice vectorial podría devolver “kafka network issues” o “timeouts in distributed systems”, omitiendo el documento que realmente contiene el código 504.

Los sistemas RAG en producción resuelven esto mediante una búsqueda híbrida, que combina la recuperación semántica (embeddings más algoritmos de búsqueda anidada como HNSW o IVF) con la recuperación por palabras clave para coincidencias exactas usando BM25. En una arquitectura típica, las dos búsquedas se ejecutan en sistemas separados, y sus resultados se fusionan y se vuelven a clasificar.

Las bases de datos vectoriales más modernas gestionan internamente búsquedas híbridas, por lo que no se necesita un segundo sistema. Tanto Weaviate como Pinecone lo soportan de forma nativa. Está convirtiéndose rápidamente en la opción por defecto para entornos de producción, ya que combina la comprensión del significado con la coincidencia de la intención exacta.

¿Acaso “híbrido” no significa almacenar todo dos veces?

Con una configuración externa, sí: los mismos fragmentos de datos suelen encontrarse tanto en la base de datos vectorial como en un motor de palabras clave como Elasticsearch. Eso es intencional. El almacén vectorial mantiene un índice HNSW o IVF para la similitud semántica, mientras que el motor de palabras clave mantiene un índice invertido para búsquedas exactas. Cada índice está diseñado para su propio propósito, por lo que esto no constituye redundancia.

Por lo general, los equipos mantienen una única fuente de verdad, como una base de datos o un data lake, y la sincronizan en ambos sistemas a través de pipelines. Esto mantiene la coherencia entre ambos mientras la recuperación de datos sigue siendo rápida y precisa.

Reclasificación de los resultados principales

Muchos ingenieros suponen que el trabajo está terminado una vez que los resultados top-K provienen de la base de datos vectorial. Pero no es así. Esos resultados son aproximadamente relevantes, no están perfectamente ordenados, y se puede pasar por alto una relevancia más detallada.

Tomemos la consulta “kafka error 504 fix”. Los resultados top-K podrían devolver “Problemas de tiempo de espera en Kafka”, luego “Errores en sistemas distribuidos” y finalmente “Código de error 504: tiempo de espera en la pasarela”. El tercer resultado debería ser claramente el primero, pero la búsqueda vectorial no siempre logra eso.

La reclasificación toma los fragmentos recuperados y los reordena mediante un análisis más profundo de la consulta. Las bases de datos vectoriales generalmente no lo hacen por sí mismas, por lo que la lógica se encuentra en la capa de la aplicación. Dos enfoques son los más utilizados:

  1. Reclasificación basada en LLM. Envía la consulta y los fragmentos a un LLM y pídele que los clasifique por relevancia. Es fácil de implementar y el modelo entiende bien el contexto. La desventaja es que es lento debido a la llamada adicional y se vuelve costoso a gran escala.
  2. Cross-encoder. Este es el estándar para la reclasificación de alta calidad. El modelo toma la consulta y un fragmento juntos como par y genera una puntuación de relevancia. Repites este proceso para todos los candidatos de top-K y los ordenas por puntuación. Es determinista y considerablemente más preciso.

Un ejemplo de cross-encoder compacto con LangChain:

"""Super-simple CrossEncoder reranking example.

Steps:
1. Define a query and a few short documents.
2. Build (query, doc) pairs.
3. Use a CrossEncoder to get a relevance score for each pair.
4. Print raw scores, then print documents sorted by score.
"""

from sentence_transformers import CrossEncoder


def main() -> None:
        # 1. Create model
model_name = "cross-encoder/ms-marco-MiniLM-L-6-v2"
print(f"Loading CrossEncoder model: {model_name}\n")
model = CrossEncoder(model_name)

    # 2. Query and documents
        query = "What is a vector database?"
documents = [
        "A vector database stores embeddings and allows similarity search.",
        "Relational databases store structured data in tables.",
        "FAISS is a library for efficient similarity search of vectors.",
        "Vector databases are used in AI applications like RAG.",
        ]

        # 3. Build (query, doc) pairs
pairs = [(query, doc) for doc in documents]

        # 4. Get scores
scores = model.predict(pairs)

print("Query:\n  " + query + "\n")
print("Raw scores (higher = more relevant):")
    for doc, score in zip(documents, scores):
print(f"  score={score:.4f}  |  doc={doc}")

    # 5. Sort by score (descending)
ranked = sorted(zip(documents, scores), key=lambda x: x[1], reverse=True)

print("\nDocuments sorted by cross-encoder score:\n")
    for rank, (doc, score) in enumerate(ranked, start=1):
print(f"Rank {rank}: score={score:.4f}")
print(f"  {doc}\n")


if __name__ == "__main__":
main()

Output
****************************************************
Query:
  What is a vector database?

Raw scores (higher = more relevant):
  score=8.4591  |  doc=A vector database stores embeddings and allows similarity search.
  score=-7.1590  |  doc=Relational databases store structured data in tables.
  score=1.0106  |  doc=FAISS is a library for efficient similarity search of vectors.
  score=7.0736  |  doc=Vector databases are used in AI applications like RAG.

Documents sorted by cross-encoder score:

Rank 1: score=8.4591
  A vector database stores embeddings and allows similarity search.

Rank 2: score=7.0736
  Vector databases are used in AI applications like RAG.

Rank 3: score=1.0106
  FAISS is a library for efficient similarity search of vectors.

Rank 4: score=-7.1590
  Relational databases store structured data in tables.

Medición de la similitud

La calidad de la recuperación depende en gran medida de cómo se calcula la cercanía vectorial. Tres métricas cubren prácticamente todo.

Similitud coseno analiza el ángulo entre dos vectores, es decir, solo la dirección. Dos flechas que apuntan en la misma dirección son muy similares, mientras que las flechas opuestas son completamente diferentes, independientemente de su longitud. La fórmula es cos(θ) = A·B / (‖A‖‖B‖). Con la consulta “Kafka error 504”, un documento sobre “Manejo de errores en Kafka” obtendría una puntuación alta (alrededor de 0.9), mientras que uno sobre “Indexación de bases de datos” tendría una puntuación baja (alrededor de 0.2). Este método es adecuado para embeddings y constituye la opción habitual en RAG, ya que lo importante es el significado y no la magnitud. Pruebe con query = [1, 1], docA = [2, 2] y docB = [1, 0]: docA apunta en la misma dirección que la consulta, por lo que su valor de coseno indica una coincidencia perfecta, aunque sea más largo.

Producto escalar combina dirección y magnitud: A·B = |A| × |B| × cos(θ). Los vectores más grandes se consideran más importantes, y aquellos que apuntan en la misma dirección se consideran más similares.

Distancia euclidiana mide la distancia en línea recta entre puntos. Cuanto más cercanos, más similares son. Se utiliza principalmente en problemas de clustering.

Cómo encajan las piezas

Un buen pipeline RAG es algo más que una base de datos vectorial conectada a un LLM. La dirección en un embedding transmite significado, mientras que la magnitud indica la intensidad de la señal. FAISS, Pinecone y Weaviate utilizan HNSW e IVF para realizar búsquedas a gran escala. Después de eso, la calidad depende de algunas decisiones adicionales:

  • Fragmentar cuidadosamente los documentos para preservar el contexto
  • Utilizar búsquedas híbridas para cubrir coincidencias semánticas y por palabras clave
  • Reordenar los resultados con un cross-encoder o un LLM
  • escribir instrucciones claras para guiar al modelo y reducir las alucinaciones
  • ajustar los umbrales top-K y de puntuación

Todo el proceso consta de: datos, fragmentación, embeddings, indexación, recuperación, reclasificación, instrucciones, LLM, respuesta.

Leer más