Redis®*: Búsqueda vectorial y RAG

Búsqueda semántica, recomendaciones y retrieval-augmented generation con Redis

👋 ¡Bienvenido a la documentación de Stackhero!

Stackhero ofrece una solución Redis cloud lista para usar que proporciona numerosas ventajas, entre ellas:

  • Interfaz web Redis Commander incluida.
  • Tamaño y transferencia de mensajes ilimitados.
  • Actualizaciones sencillas con solo un clic.
  • Rendimiento óptimo y seguridad reforzada gracias a una infraestructura privada y dedicada.

Ahorre tiempo y simplifique su trabajo: solo necesita 5 minutos para probar la solución de alojamiento Redis cloud de Stackhero.

Si está desarrollando una búsqueda semántica, un motor de recomendaciones o un pipeline de retrieval-augmented generation (RAG), necesita una solución fiable para almacenar embeddings y encontrar rápidamente las coincidencias más cercanas a una consulta. Su instancia de Redis en Stackhero ofrece esta funcionalidad de forma nativa: el motor de consultas indexa los vectores y responde a búsquedas de similitud en milisegundos. No es necesario añadir una base de datos vectorial adicional junto a Redis: todo funciona en un único lugar.

Este enfoque es productivo y eficiente gracias a dos características clave:

  • Consultas híbridas. Puede combinar la búsqueda de similitud vectorial con filtros estándar como rangos numéricos, etiquetas o texto completo, todo en una sola consulta. Por ejemplo: "los 5 fragmentos más cercanos a esta pregunta, pero solo de documentos que este usuario puede leer, publicados después de enero" se pueden obtener en una única consulta, sin necesidad de varias llamadas.
  • Arquitectura simplificada. Los embeddings, la caché y las sesiones conviven, compartiendo credenciales, copias de seguridad y monitorización. Esto reduce la complejidad y el overhead.

Active el módulo Search (y JSON si planea almacenar documentos en formato JSON) en la sección Modules de la configuración de su servicio en el panel de Stackhero. Para ver los pasos detallados, consulte la guía de Search y JSON.

Para declarar un campo vectorial, especifique su dimensión y la métrica de distancia. La dimensión debe coincidir con la de su modelo de embedding: por ejemplo, 1536 para OpenAI text-embedding-3-small, 768 para muchos modelos open source, etc.

FT.CREATE chunksIndex
  ON HASH
  PREFIX 1 chunk:
  SCHEMA
    content TEXT
    documentId TAG
    publishedAt NUMERIC
    embedding VECTOR HNSW 6
      TYPE FLOAT32
      DIM 1536
      DISTANCE_METRIC COSINE

HNSW crea un índice en grafo que mantiene las consultas rápidas a medida que crece la colección, por lo que es una buena opción a partir de varios miles de vectores. Para colecciones pequeñas donde la precisión exacta es prioritaria, puede usar FLAT para una búsqueda exhaustiva.

COSINE es la métrica recomendada para la mayoría de modelos de embedding de texto. También puede elegir L2 o IP si su caso de uso lo requiere.

Una consulta de K nearest neighbors (KNN) se realiza así. El vector se suministra como bytes float32 little-endian en bruto:

FT.SEARCH chunksIndex "*=>[KNN 5 @embedding $queryVector AS score]"
  PARAMS 2 queryVector "<rawBytes>"
  SORTBY score
  RETURN 2 content score
  DIALECT 2

Para combinar la búsqueda de similitud con filtros, simplemente sustituya * por una expresión de filtro:

FT.SEARCH chunksIndex "(@documentId:{doc42} @publishedAt:[1735689600 +inf])=>[KNN 5 @embedding $queryVector AS score]"
  PARAMS 2 queryVector "<rawBytes>"
  SORTBY score
  DIALECT 2

Las consultas vectoriales requieren DIALECT 2. Si lo omite, la consulta usará la sintaxis antigua y devolverá un error.

import { createClient, SCHEMA_FIELD_TYPE, SCHEMA_VECTOR_FIELD_ALGORITHM } from 'redis';

const client = createClient({ url: process.env.STACKHERO_REDIS_URL_TLS });
await client.connect();

const DIMENSIONS = 1536;
const toBytes = embedding => Buffer.from(new Float32Array(embedding).buffer);

// 1. Cree el índice una vez, al iniciar
try {
  await client.ft.create(
    'chunksIndex',
    {
      content: SCHEMA_FIELD_TYPE.TEXT,
      documentId: SCHEMA_FIELD_TYPE.TAG,
      embedding: {
        type: SCHEMA_FIELD_TYPE.VECTOR,
        ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.HNSW,
        TYPE: 'FLOAT32',
        DIM: DIMENSIONS,
        DISTANCE_METRIC: 'COSINE'
      }
    },
    { ON: 'HASH', PREFIX: 'chunk:' }
  );
}
catch (error) {
  if (!error.message.includes('Index already exists')) {
    throw error;
  }
}

// 2. Indexe sus fragmentos (el embedding lo proporciona su proveedor de modelo)
async function indexChunk({ id, documentId, content, embedding }) {
  await client.hSet(`chunk:${id}`, {
    content,
    documentId,
    embedding: toBytes(embedding)
  });
}

// 3. Recupere los fragmentos más cercanos a una pregunta, restringidos a un documento
async function retrieve({ questionEmbedding, documentId, count = 5 }) {
  const results = await client.ft.search(
    'chunksIndex',
    `(@documentId:{${documentId}})=>[KNN ${count} @embedding $queryVector AS score]`,
    {
      PARAMS: { queryVector: toBytes(questionEmbedding) },
      SORTBY: 'score',
      RETURN: [ 'content', 'score' ],
      DIALECT: 2
    }
  );

  return results.documents.map(({ value }) => value.content);
}

// 4. Envíelos a su modelo como contexto
const context = await retrieve({ questionEmbedding, documentId: 'doc42' });
const prompt = `Responde utilizando solo este contexto:\n\n${context.join('\n\n')}\n\nPregunta: ${question}`;
import os
import numpy as np
import redis
from redis.commands.search.field import TextField, TagField, VectorField
from redis.commands.search.index_definition import IndexDefinition, IndexType
from redis.commands.search.query import Query

DIMENSIONS = 1536

r = redis.from_url(os.environ['STACKHERO_REDIS_URL_TLS'])

# Cree el índice una vez, al iniciar
try:
    r.ft('chunksIndex').create_index(
        (
            TextField('content'),
            TagField('documentId'),
            VectorField(
                'embedding',
                'HNSW',
                {'TYPE': 'FLOAT32', 'DIM': DIMENSIONS, 'DISTANCE_METRIC': 'COSINE'},
            ),
        ),
        definition=IndexDefinition(prefix=['chunk:'], index_type=IndexType.HASH),
    )
except redis.ResponseError as error:
    if 'Index already exists' not in str(error):
        raise


def index_chunk(chunk_id, document_id, content, embedding):
    r.hset(
        f'chunk:{chunk_id}',
        mapping={
            'content': content,
            'documentId': document_id,
            'embedding': np.array(embedding, dtype=np.float32).tobytes(),
        },
    )


def retrieve(question_embedding, document_id, count=5):
    query = (
        Query(f'(@documentId:{{{document_id}}})=>[KNN {count} @embedding $queryVector AS score]')
        .sort_by('score')
        .return_fields('content', 'score')
        .dialect(2)
    )
    results = r.ft('chunksIndex').search(
        query,
        query_params={'queryVector': np.array(question_embedding, dtype=np.float32).tobytes()},
    )
    return [document.content for document in results.docs]

Los vectores se almacenan en memoria. Puede estimar las necesidades de memoria con la siguiente fórmula:

numberOfVectors x dimensions x 4 bytes, más aproximadamente un 30–50% adicional para el grafo HNSW.

Por ejemplo, un millón de vectores de 1536 dimensiones utilizan unos 6 GB para los vectores en bruto. Un plan de 20 GB es un punto de partida cómodo para este volumen. Puede reducir el uso de memoria:

  • Utilizando un modelo más pequeño. Por ejemplo, un modelo de 768 dimensiones reduce a la mitad el uso de memoria respecto a uno de 1536 dimensiones.
  • Almacenando en FLOAT16 en lugar de FLOAT32 si su modelo lo permite, lo que reduce el espacio a la mitad de nuevo.

Puede monitorizar el uso real con FT.INFO chunksIndex y la métrica used_memory en su monitorización Prometheus. Si su uso crece, actualizar su plan es sencillo y puede hacerlo desde su panel de control.

  • Los índices vectoriales solo funcionan en la base de datos 0, como todos los índices de búsqueda en Redis.
  • Los conjuntos vectoriales ofrecen una alternativa más sencilla sin necesidad de configuración. Los comandos VADD y VSIM están integrados en Redis, por lo que puede usar la búsqueda de similitud de inmediato, sin activar módulos. Es conveniente para escenarios de recomendación simples sobre un único conjunto. El motor de consultas es la mejor opción si necesita filtros, paginación o varios campos.
  • La construcción de un índice sobre claves existentes se realiza en segundo plano. Puede comprobar el progreso con FT.INFO. Las búsquedas devuelven resultados parciales hasta que finaliza la indexación.

Para obtener más información sobre la búsqueda vectorial en Redis, consulte la documentación oficial de Redis vector search.