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.
Antes de empezar
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.
Crear un índice vectorial
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.
Consultas
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.
Un pipeline RAG en Node.js
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}`;
Lo mismo en Python
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]
Dimensionar su servicio
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
FLOAT16en lugar deFLOAT32si 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.
Información útil
- 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
VADDyVSIMestá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.