Valkey: Vektorsuche und RAG
Semantische Suche, Empfehlungen und Retrieval-Augmented Generation mit Valkey
👋 Willkommen in der Stackhero-Dokumentation!
Stackhero bietet eine einsatzbereite Valkey Cloud-Lösung mit zahlreichen Vorteilen, darunter:
Valkey AdminWeb-Oberfläche inklusive.- Unbegrenzte Nachrichtengröß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 Valkey Cloud Hosting-Lösung von Stackhero auszuprobieren!
Wenn Sie eine semantische Suche, eine Empfehlungs-Engine oder eine Retrieval-Augmented Generation (RAG) Pipeline entwickeln, müssen Sie Embeddings speichern und schnell die nächsten Übereinstimmungen zu einer Anfrage finden. Stackhero für Valkey unterstützt dies nativ: Das Search-Modul indexiert Vektoren und beantwortet Ähnlichkeitsanfragen in Millisekunden. Sie müssen keine separate Vektor-Datenbank neben dem bereits genutzten Valkey-Service einrichten.
Zwei Funktionen machen diesen Ansatz praktisch und effizient:
- Hybride Abfragen. Sie können Vektor-Ähnlichkeitssuchen durchführen und gleichzeitig reguläre Filter (wie numerische Bereiche, Tags oder Text) in einer einzigen Anfrage anwenden. Zum Beispiel können Sie „die 5 Chunks, die dieser Frage am nächsten sind, aber nur aus Dokumenten, die dieser Benutzer lesen darf“ mit einer einzigen Abfrage abrufen – nicht mit zwei.
- Eine Komponente weniger. Ihre Embeddings werden zusammen mit Cache- und Sitzungsdaten gespeichert, unter Verwendung derselben Zugangsdaten, Backups und Monitoring-Tools. Das hält Ihren Stack schlanker und vereinfacht das Management.
Bevor Sie starten
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. Weitere Details finden Sie im Search- und JSON-Guide.
Erstellen eines Vektor-Index
Deklarieren Sie ein Vektorfeld, indem Sie die Dimension und die Distanzmetrik angeben. Die Dimension muss zu Ihrem Modell passen: Zum Beispiel 1536 für OpenAI text-embedding-3-small oder 768 für viele Open-Source-Modelle.
FT.CREATE chunksIndex
ON HASH
PREFIX 1 chunk:
SCHEMA
content TEXT
documentId TAG
embedding VECTOR HNSW 6
TYPE FLOAT32
DIM 1536
DISTANCE_METRIC COSINE
HNSW erstellt einen Graph-Index, der auch bei großen Sammlungen schnelle, approximative Suchen ermöglicht. Dies wird für Sammlungen mit mehr als einigen Tausend Vektoren empfohlen. Für kleine Sammlungen können Sie FLAT für eine exakte, brute-force Suche verwenden.
COSINE ist in der Regel die beste Distanzmetrik für Text-Embeddings. Auch die Metriken L2 und IP werden unterstützt.
Abfragen
Eine K-nearest neighbors (KNN) Abfrage übergibt den Vektor als Parameter, als rohe little-endian float32 Bytes:
FT.SEARCH chunksIndex "*=>[KNN 5 @embedding $queryVector]"
PARAMS 2 queryVector "<rawBytes>"
Um Vektorsuche mit Filtern zu kombinieren, ersetzen Sie einfach das * durch Ihren Filterausdruck:
FT.SEARCH chunksIndex "@documentId:{doc42}=>[KNN 5 @embedding $queryVector]"
PARAMS 2 queryVector "<rawBytes>"
Eine RAG-Pipeline in Node.js
Valkey verwendet dasselbe Protokoll wie Redis, daher können Sie jeden Redis-kompatiblen Client nutzen.
import { createClient, SCHEMA_FIELD_TYPE, SCHEMA_VECTOR_FIELD_ALGORITHM } from 'redis';
const client = createClient({ url: process.env.STACKHERO_VALKEY_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 (Embedding kommt von Ihrem Modell-Provider)
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]`,
{ PARAMS: { queryVector: toBytes(questionEmbedding) } }
);
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}`;
Dasselbe 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_VALKEY_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]')
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]
Dimensionierung Ihres Services
Vektoren werden im Arbeitsspeicher gehalten, daher ist es wichtig, deren Speicherbedarf einzuplanen. Die grobe Schätzung für float32-Vektoren lautet:
numberOfVectors x dimensions x 4 bytes, plus etwa 30 bis 50 Prozent zusätzlich für den HNSW-Graphen.
Beispielsweise benötigen eine Million Vektoren mit 1536 Dimensionen etwa 6 GB für die Rohdaten. Ein 20-GB-Plan ist ein komfortabler Ausgangspunkt. Sie können den Speicherbedarf reduzieren, indem Sie ein Modell mit weniger Dimensionen wählen: Ein Modell mit 768 Dimensionen benötigt nur halb so viel Speicher wie eines mit 1536 Dimensionen.
Sie können den tatsächlichen Speicherverbrauch mit FT.INFO chunksIndex und mit der Metrik used_memory in Ihrem Prometheus Monitoring überprüfen. Falls Sie mehr Speicher benötigen, können Sie Ihren Plan jederzeit über das Dashboard upgraden.
Gut zu wissen
- Das Indexieren bestehender Keys läuft im Hintergrund. Sie können den Fortschritt mit
FT.INFOverfolgen. Suchen liefern solange Teilergebnisse, bis das Backfilling abgeschlossen ist. - Das Search-Modul implementiert einen fokussierten Satz an
FT.*-Befehlen. Für die Vektorsuche sind alle wesentlichen Funktionen abgedeckt: HNSW und exakte KNN-Suche, hybride Filterung undFT.AGGREGATE.
Eine vollständige Referenz finden Sie in der Valkey Search Dokumentation.