Redis®*: Vektorsuche und RAG

Semantische Suche, Empfehlungen und Retrieval-Augmented Generation mit Redis

👋 Willkommen in der Stackhero-Dokumentation!

Stackhero bietet eine einsatzbereite Redis Cloud-Lösung mit zahlreichen Vorteilen, darunter:

  • Redis Commander Web-Oberfläche inklusive.
  • Unbegrenzte Nachrichten­größe und Übertragungen.
  • Updates ganz einfach per Klick.
  • Optimale Performance und starke Sicherheit durch eine private, dedizierte Infrastruktur.

Sparen Sie Zeit und vereinfachen Sie Ihren Alltag: Es dauert nur 5 Minuten, um die Redis Cloud Hosting-Lösung von Stackhero auszuprobieren!

Wenn Sie eine semantische Suche, eine Empfehlungs-Engine oder eine Retrieval-Augmented Generation (RAG) Pipeline entwickeln, benötigen Sie eine zuverlässige Möglichkeit, Embeddings zu speichern und schnell die nächsten Übereinstimmungen zu einer Anfrage zu finden. Ihre Redis-Instanz auf Stackhero bietet dies nativ: Die Query Engine indexiert Vektoren und beantwortet Ähnlichkeitssuchen in Millisekunden. Sie müssen keine separate Vektordatenbank neben Redis betreiben – alles läuft an einem Ort.

Dieser Ansatz ist sowohl produktiv als auch effizient, dank zweier zentraler Funktionen:

  • Hybride Abfragen. Sie können die Vektor-Ähnlichkeitssuche mit Standardfiltern wie numerischen Bereichen, Tags oder Volltext kombinieren – alles in einer einzigen Anfrage. Zum Beispiel: „Die 5 Chunks, die dieser Frage am nächsten sind, aber nur aus Dokumenten, die dieser Benutzer lesen darf und die nach Januar veröffentlicht wurden“, kann in einer einzigen Abfrage erledigt werden, nicht in mehreren.
  • Vereinfachte Architektur. Embeddings, Cache und Sessions befinden sich gemeinsam an einem Ort und teilen sich Zugangsdaten, Backups und Monitoring. Das reduziert Komplexität und Overhead.

Aktivieren Sie das Modul Search (und JSON, falls Sie Dokumente als JSON speichern möchten) im Bereich Modules Ihrer Service-Konfiguration auf dem Stackhero Dashboard. Für eine Schritt-für-Schritt-Anleitung siehe die Search und JSON Anleitung.

Um ein Vektorfeld zu deklarieren, geben Sie dessen Dimension und Distanzmetrik an. Die Dimension muss zu Ihrem Embedding-Modell passen: Zum Beispiel 1536 für OpenAI text-embedding-3-small, 768 für viele Open-Source-Modelle usw.

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 erstellt einen Graph-Index, der auch bei wachsender Sammlung schnelle Abfragen ermöglicht und ist daher ab einigen Tausend Vektoren die beste Wahl. Für kleine Sammlungen, bei denen exakte Ergebnisse wichtig sind, können Sie FLAT für eine Brute-Force-Suche verwenden.

COSINE ist die empfohlene Metrik für die meisten textbasierten Embedding-Modelle. Sie können auch L2 oder IP wählen, falls Ihr Anwendungsfall dies erfordert.

Eine K-nearest-neighbors (KNN) Abfrage sieht so aus. Der Vektor wird als rohe little-endian float32 Bytes übergeben:

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

Um Ähnlichkeitssuche mit Filtern zu kombinieren, ersetzen Sie einfach * durch einen Filterausdruck:

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

Vektorabfragen erfordern DIALECT 2. Wenn Sie dies weglassen, wird die ältere Syntax verwendet und die Abfrage führt zu einem Fehler.

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. Erstellen Sie den Index einmalig beim Start
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. Indexieren Sie Ihre Chunks (das Embedding stammt von Ihrem Modellanbieter)
async function indexChunk({ id, documentId, content, embedding }) {
  await client.hSet(`chunk:${id}`, {
    content,
    documentId,
    embedding: toBytes(embedding)
  });
}

// 3. Rufen Sie die Chunks ab, die einer Frage am nächsten sind, beschränkt auf ein Dokument
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. Geben Sie diese als Kontext an Ihr Modell weiter
const context = await retrieve({ questionEmbedding, documentId: 'doc42' });
const prompt = `Antworten Sie ausschließlich mit diesem Kontext:\n\n${context.join('\n\n')}\n\nFrage: ${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'])

# Erstellen Sie den Index einmalig beim Start
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]

Vektoren werden im Arbeitsspeicher gespeichert. Sie können den Speicherbedarf mit folgender Formel abschätzen:

numberOfVectors x dimensions x 4 bytes, plus etwa 30–50 % zusätzlich für den HNSW-Graphen.

Beispiel: Eine Million Vektoren mit 1536 Dimensionen benötigen etwa 6 GB für die Rohdaten. Ein 20-GB-Plan ist für diese Größenordnung ein komfortabler Startpunkt. Sie können den Speicherbedarf reduzieren, indem Sie:

  • Ein kleineres Modell verwenden. Ein Modell mit 768 Dimensionen halbiert den Speicherbedarf im Vergleich zu einem mit 1536 Dimensionen.
  • FLOAT16 statt FLOAT32 speichern, sofern Ihr Modell dies unterstützt, was den Speicherbedarf nochmals halbiert.

Sie können die tatsächliche Nutzung mit FT.INFO chunksIndex und der Metrik used_memory in Ihrem Prometheus Monitoring überwachen. Wenn Ihr Bedarf steigt, können Sie Ihren Plan unkompliziert direkt über das Dashboard upgraden.

  • Vektorindizes funktionieren nur auf Datenbank 0, wie alle Suchindizes in Redis.
  • Vektormengen bieten eine einfachere Alternative ohne Einrichtung. Die Befehle VADD und VSIM sind in Redis integriert, sodass Sie sofort mit der Ähnlichkeitssuche starten können, ohne Module zu aktivieren. Das ist praktisch für einfache Empfehlungsszenarien auf einer einzigen Menge. Die Query Engine ist die bessere Wahl, wenn Sie Filter, Paginierung oder mehrere Felder benötigen.
  • Das Erstellen eines Index über bestehende Schlüssel läuft im Hintergrund. Sie können den Fortschritt mit FT.INFO prüfen. Abfragen liefern solange Teilergebnisse, bis die Indexierung abgeschlossen ist.

Weitere Informationen zur Vektorsuche in Redis finden Sie in der offiziellen Redis Vector Search Dokumentation.