Redis®*: Vector search en RAG
Semantisch zoeken, aanbevelingen en retrieval-augmented generation met Redis
👋 Welkom bij de Stackhero-documentatie!
Stackhero biedt een kant-en-klare Redis cloud oplossing met tal van voordelen, waaronder:
Redis Commanderwebinterface inbegrepen.- Onbeperkte berichtgrootte en overdrachten.
- Updates eenvoudig met één klik.
- Optimale prestaties en sterke beveiliging dankzij een privé, dedicated infrastructuur.
Bespaar tijd en maak uw leven eenvoudiger: het kost slechts 5 minuten om de Redis cloud hosting oplossing van Stackhero uit te proberen!
Als u werkt aan semantisch zoeken, een aanbevelingsengine of een retrieval-augmented generation (RAG) pipeline, heeft u een betrouwbare manier nodig om embeddings op te slaan en snel de dichtstbijzijnde matches bij een query te vinden. Uw Redis-instantie op Stackhero biedt dit standaard: de query-engine indexeert vectoren en reageert binnen milliseconden op gelijkeniszoekopdrachten. U hoeft geen aparte vectordatabase naast Redis te gebruiken: alles draait op één plek.
Deze aanpak is zowel productief als efficiënt dankzij twee belangrijke eigenschappen:
- Hybride queries. U kunt vector-gelijkeniszoekopdrachten combineren met standaardfilters zoals numerieke bereiken, tags of full-text, allemaal in één enkele aanvraag. Bijvoorbeeld: "de 5 segmenten die het dichtst bij deze vraag liggen, maar alleen uit documenten die deze gebruiker mag lezen, gepubliceerd na januari" kan in één query worden afgehandeld, niet in meerdere.
- Vereenvoudigde architectuur. Embeddings, cache en sessies worden samen beheerd, delen inloggegevens, back-ups en monitoring. Dit vermindert de complexiteit en overhead.
Voordat u begint
Schakel de Search-module in (en JSON als u documenten als JSON wilt opslaan) in het gedeelte Modules van uw serviceconfiguratie op het Stackhero dashboard. Voor een stapsgewijze uitleg, zie de search en JSON handleiding.
Een vectorindex aanmaken
Om een vectorveld te definiëren, geeft u de dimensie en de afstandsmaat op. De dimensie moet overeenkomen met uw embeddingmodel: bijvoorbeeld 1536 voor OpenAI text-embedding-3-small, 768 voor veel open-source modellen, enzovoort.
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 maakt een graafindex die zoekopdrachten snel houdt naarmate uw collectie groeit, en is daarom een goede keuze boven enkele duizenden vectoren. Voor kleine collecties waar exacte resultaten belangrijk zijn, kunt u FLAT gebruiken voor brute-force zoeken.
COSINE is de aanbevolen metriek voor de meeste tekst-embeddingmodellen. U kunt ook kiezen voor L2 of IP als uw use-case dat vereist.
Query's uitvoeren
Een K nearest neighbors (KNN) query ziet er als volgt uit. De vector wordt aangeleverd als ruwe little-endian float32-bytes:
FT.SEARCH chunksIndex "*=>[KNN 5 @embedding $queryVector AS score]"
PARAMS 2 queryVector "<rawBytes>"
SORTBY score
RETURN 2 content score
DIALECT 2
Om gelijkeniszoekopdrachten te combineren met filters, vervangt u eenvoudigweg * door een filterexpressie:
FT.SEARCH chunksIndex "(@documentId:{doc42} @publishedAt:[1735689600 +inf])=>[KNN 5 @embedding $queryVector AS score]"
PARAMS 2 queryVector "<rawBytes>"
SORTBY score
DIALECT 2
Vectorquery's vereisen
DIALECT 2. Als u dit weglaat, gebruikt de query de oude syntax en krijgt u een foutmelding.
Een RAG-pipeline 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. Maak de index één keer aan bij het opstarten
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. Indexeer uw segmenten (embedding komt van uw modelprovider)
async function indexChunk({ id, documentId, content, embedding }) {
await client.hSet(`chunk:${id}`, {
content,
documentId,
embedding: toBytes(embedding)
});
}
// 3. Haal de segmenten op die het dichtst bij een vraag liggen, beperkt tot één document
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. Geef deze door aan uw model als context
const context = await retrieve({ questionEmbedding, documentId: 'doc42' });
const prompt = `Beantwoord uitsluitend met deze context:\n\n${context.join('\n\n')}\n\nVraag: ${question}`;
Hetzelfde 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'])
# Maak de index één keer aan bij het opstarten
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]
Uw service dimensioneren
Vectoren worden in het geheugen opgeslagen. U kunt het geheugengebruik schatten met deze formule:
numberOfVectors x dimensions x 4 bytes, plus ongeveer 30–50% extra voor de HNSW-graaf.
Bijvoorbeeld, één miljoen vectoren van 1536 dimensies gebruiken ongeveer 6 GB voor de ruwe vectoren. Een 20 GB-abonnement is een comfortabele start voor deze schaal. U kunt het geheugengebruik verminderen door:
- Een kleiner model te gebruiken. Bijvoorbeeld, een model met 768 dimensies halveert het geheugengebruik ten opzichte van een model met 1536 dimensies.
- Opslaan als
FLOAT16in plaats vanFLOAT32als uw model dit ondersteunt, wat het geheugenverbruik nogmaals halveert.
U kunt het daadwerkelijke gebruik monitoren met FT.INFO chunksIndex en de used_memory-metriek in uw Prometheus monitoring. Als uw gebruik toeneemt, kunt u eenvoudig upgraden via uw dashboard.
Goed om te weten
- Vectorindexen werken alleen op database 0, net als alle zoekindexen in Redis.
- Vector sets bieden een eenvoudig alternatief zonder configuratie. De
VADD- enVSIM-commando's zijn ingebouwd in Redis, zodat u direct gelijkeniszoekopdrachten kunt uitvoeren zonder modules te activeren. Dit is handig voor eenvoudige aanbevelingsscenario's op één enkele set. De query-engine is de betere keuze als u filters, paginering of meerdere velden nodig heeft. - Het opbouwen van een index over bestaande keys gebeurt op de achtergrond. U kunt de voortgang volgen met
FT.INFO. Zoekopdrachten geven gedeeltelijke resultaten terug totdat de indexering is voltooid.
Meer weten over vector search in Redis? Zie de officiële Redis vector search documentatie.