Redis®*: Ricerca vettoriale e RAG
Ricerca semantica, raccomandazioni e retrieval-augmented generation con Redis
👋 Benvenuti nella documentazione di Stackhero!
Stackhero offre una soluzione Redis cloud pronta all'uso che garantisce numerosi vantaggi, tra cui:
- Interfaccia web Redis Commander inclusa.
- Dimensione e trasferimento dei messaggi illimitati.
- Aggiornamenti semplici con un solo clic.
- Prestazioni ottimali e sicurezza avanzata grazie a un'infrastruttura privata e dedicata.
Risparmia tempo e semplificati la vita: bastano 5 minuti per provare la soluzione di hosting Redis cloud di Stackhero!
Se state sviluppando una ricerca semantica, un motore di raccomandazione o una pipeline di retrieval-augmented generation (RAG), avete bisogno di un modo affidabile per memorizzare gli embeddings e trovare rapidamente le corrispondenze più vicine a una query. L'istanza Redis su Stackhero offre questa funzionalità nativamente: il motore di query indicizza i vettori e risponde alle ricerche di similarità in pochi millisecondi. Non è necessario aggiungere un database vettoriale separato accanto a Redis: tutto funziona in un unico ambiente.
Questo approccio è sia produttivo che efficiente grazie a due caratteristiche chiave:
- Query ibride. Potete combinare la ricerca di similarità vettoriale con filtri standard come intervalli numerici, tag o testo integrale, tutto in una singola richiesta. Ad esempio: "i 5 segmenti più vicini a questa domanda, ma solo da documenti che questo utente è autorizzato a leggere, pubblicati dopo gennaio" possono essere recuperati con una sola query, senza doverne concatenare diverse.
- Architettura semplificata. Embeddings, cache e sessioni convivono, condividendo credenziali, backup e monitoraggio. Questo riduce la complessità e l'overhead.
Prima di iniziare
Abilitate il modulo Search (e JSON se intendete memorizzare i documenti in formato JSON) nella sezione Modules della configurazione del vostro servizio sulla dashboard Stackhero. Per una guida dettagliata passo-passo, consultate la guida Search e JSON.
Creazione di un indice vettoriale
Per dichiarare un campo vettoriale, specificate la dimensione e la metrica di distanza. La dimensione deve corrispondere al vostro modello di embedding: ad esempio, 1536 per OpenAI text-embedding-3-small, 768 per molti modelli open source, e così via.
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 indice a grafo che mantiene le query veloci anche con la crescita della collezione, rendendolo una scelta ideale sopra alcune migliaia di vettori. Per collezioni piccole dove conta la precisione esatta, potete usare FLAT per una ricerca esaustiva.
COSINE è la metrica consigliata per la maggior parte dei modelli di embedding testuale. Potete anche scegliere L2 o IP se il vostro caso d'uso lo richiede.
Query
Una query K nearest neighbors (KNN) si presenta così. Il vettore viene fornito come byte raw float32 little-endian:
FT.SEARCH chunksIndex "*=>[KNN 5 @embedding $queryVector AS score]"
PARAMS 2 queryVector "<rawBytes>"
SORTBY score
RETURN 2 content score
DIALECT 2
Per combinare la ricerca di similarità con dei filtri, basta sostituire * con un'espressione di filtro:
FT.SEARCH chunksIndex "(@documentId:{doc42} @publishedAt:[1735689600 +inf])=>[KNN 5 @embedding $queryVector AS score]"
PARAMS 2 queryVector "<rawBytes>"
SORTBY score
DIALECT 2
Le query vettoriali richiedono
DIALECT 2. Se lo omettete, la query utilizza la sintassi precedente e restituisce un errore.
Una pipeline RAG in 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. Create the index once, at startup
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. Indicizzate i vostri segmenti (l'embedding proviene dal vostro provider di modelli)
async function indexChunk({ id, documentId, content, embedding }) {
await client.hSet(`chunk:${id}`, {
content,
documentId,
embedding: toBytes(embedding)
});
}
// 3. Recuperate i segmenti più vicini a una domanda, limitati 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. Fornite questi segmenti al vostro modello come contesto
const context = await retrieve({ questionEmbedding, documentId: 'doc42' });
const prompt = `Rispondi utilizzando solo questo contesto:\n\n${context.join('\n\n')}\n\nDomanda: ${question}`;
Lo stesso in 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'])
# Create the index once, at startup
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]
Dimensionamento del servizio
I vettori sono memorizzati in memoria. Potete stimare il fabbisogno di memoria con questa formula:
numberOfVectors x dimensions x 4 bytes, più circa il 30–50% in più per il grafo HNSW.
Ad esempio, un milione di vettori da 1536 dimensioni utilizzano circa 6 GB per i vettori raw. Un piano da 20 GB è un punto di partenza comodo per questa scala. Potete ridurre l'utilizzo di memoria:
- Utilizzando un modello più piccolo. Ad esempio, un modello da 768 dimensioni dimezza la memoria rispetto a uno da 1536 dimensioni.
- Memorizzando in
FLOAT16invece diFLOAT32se il vostro modello lo consente, dimezzando ulteriormente lo spazio necessario.
Potete monitorare l'utilizzo reale con FT.INFO chunksIndex e la metrica used_memory nel vostro monitoraggio Prometheus. Se l'utilizzo cresce, l'upgrade del piano è semplice e può essere effettuato direttamente dalla dashboard.
Informazioni utili
- Gli indici vettoriali funzionano solo sul database 0, come tutti gli indici di ricerca in Redis.
- I set vettoriali offrono un'alternativa più semplice senza necessità di configurazione. I comandi
VADDeVSIMsono integrati in Redis, quindi potete utilizzare la ricerca di similarità immediatamente, senza abilitare moduli. È comodo per scenari di raccomandazione semplici su un singolo set. Il motore di query è preferibile quando servono filtri, paginazione o più campi. - La costruzione di un indice su chiavi esistenti avviene in background. Potete verificare l'avanzamento con
FT.INFO. Le ricerche restituiscono risultati parziali finché l'indicizzazione non è completata.
Per ulteriori informazioni sulla ricerca vettoriale in Redis, consultate la documentazione ufficiale Redis vector search.