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 Commander webinterface 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.

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.

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.

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.

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}`;
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]

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 FLOAT16 in plaats van FLOAT32 als 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.

  • Vectorindexen werken alleen op database 0, net als alle zoekindexen in Redis.
  • Vector sets bieden een eenvoudig alternatief zonder configuratie. De VADD- en VSIM-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.