Redis®*: Vektorinė paieška ir RAG

Semantinė paieška, rekomendacijos ir retrieval-augmented generation su Redis

👋 Sveiki atvykę į Stackhero dokumentaciją!

Stackhero siūlo paruoštą naudoti Redis cloud sprendimą, kuris suteikia daugybę privalumų, įskaitant:

  • Įtraukta Redis Commander žiniatinklio sąsaja.
  • Neribotas žinučių dydis ir perdavimas.
  • Paprasti atnaujinimai vienu paspaudimu.
  • Optimali veikla ir stipri apsauga dėl privačios, dedikuotos infrastruktūros.

Taupykite laiką ir palengvinkite sau darbą: išbandyti Stackhero Redis cloud hosting sprendimą užtrunka tik 5 minutes!

Jei kuriate semantinę paiešką, rekomendacijų variklį arba retrieval-augmented generation (RAG) procesą, jums reikia patikimo būdo saugoti embedding'us ir greitai rasti artimiausius atitikmenis užklausai. Jūsų Redis instancija Stackhero platformoje tai siūlo natūraliai: užklausų variklis indeksuoja vektorius ir į panašumo paieškas atsako per milisekundes. Nereikia diegti atskiros vektorinės duomenų bazės šalia Redis – viskas veikia vienoje vietoje.

Šis metodas yra produktyvus ir efektyvus dėl dviejų pagrindinių savybių:

  • Hibridinės užklausos. Galite derinti vektorinę panašumo paiešką su standartiniais filtrais, tokiais kaip skaitinės ribos, žymos ar pilnas tekstas, viską vienoje užklausoje. Pavyzdžiui: „5 fragmentai, artimiausi šiam klausimui, bet tik iš dokumentų, kuriuos šis naudotojas gali skaityti, publikuotų po sausio mėnesio“ – visa tai galima gauti viena užklausa, o ne keliomis.
  • Supaprastinta architektūra. Embedding'ai, podėlis (cache) ir sesijos laikomi kartu, dalijasi prisijungimo duomenimis, atsarginėmis kopijomis ir stebėsena. Tai sumažina sudėtingumą ir administravimo kaštus.

Aktyvuokite Search modulį (ir JSON, jei planuojate dokumentus saugoti JSON formatu) savo paslaugos konfigūracijos Modules skiltyje Stackhero valdymo pulte. Išsamią instrukciją rasite Search ir JSON gide.

Norėdami deklaruoti vektoriaus lauką, nurodykite jo dimensiją ir atstumo metriką. Dimensija turi atitikti jūsų embedding modelį: pavyzdžiui, 1536 OpenAI text-embedding-3-small, 768 daugeliui atviro kodo modelių ir pan.

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 sukuria grafo indeksą, kuris užtikrina greitas užklausas net kolekcijai augant, todėl tai geras pasirinkimas, kai turite daugiau nei kelis tūkstančius vektorių. Mažoms kolekcijoms, kur svarbus tikslumas, galite naudoti FLAT – tai tiesioginė paieška.

COSINE yra rekomenduojama metrika daugumai tekstinių embedding modelių. Jei reikia, galite rinktis ir L2 arba IP.

K artimiausių kaimynų (KNN) užklausa atrodo taip. Vektorius perduodamas kaip raw little-endian float32 baitai:

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

Norėdami derinti panašumo paiešką su filtrais, tiesiog pakeiskite * filtro išraiška:

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

Vektorinėms užklausoms būtinas DIALECT 2. Jei to nenurodysite, bus naudojama sena sintaksė ir gausite klaidą.

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. Sukurkite indeksą vieną kartą, paleidžiant aplikaciją
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. Indeksuokite fragmentus (embedding gaunamas iš jūsų modelio tiekėjo)
async function indexChunk({ id, documentId, content, embedding }) {
  await client.hSet(`chunk:${id}`, {
    content,
    documentId,
    embedding: toBytes(embedding)
  });
}

// 3. Gaukite fragmentus, artimiausius klausimui, ribojant pagal 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. Pateikite juos savo modeliui kaip kontekstą
const context = await retrieve({ questionEmbedding, documentId: 'doc42' });
const prompt = `Atsakykite naudodami tik šį kontekstą:\n\n${context.join('\n\n')}\n\nKlausimas: ${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'])

# Sukurkite indeksą vieną kartą, paleidžiant aplikaciją
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]

Vektoriai saugomi atmintyje. Atminties poreikius galite apskaičiuoti pagal formulę:

numberOfVectors x dimensions x 4 bytes, plius apie 30–50% papildomai HNSW grafui.

Pavyzdžiui, milijonas vektorių po 1536 dimensijas užima apie 6 GB žaliems vektoriams. 20 GB planas yra patogus startas tokiai apimčiai. Atminties naudojimą galite sumažinti:

  • Naudodami mažesnį modelį. Pavyzdžiui, 768 dimensijų modelis naudoja dvigubai mažiau atminties nei 1536 dimensijų modelis.
  • Saugodami FLOAT16 vietoje FLOAT32, jei jūsų modelis tai palaiko – taip atminties poreikis sumažėja dar perpus.

Faktinį naudojimą galite stebėti su FT.INFO chunksIndex ir used_memory metrika savo Prometheus stebėsenoje. Jei poreikiai auga, plano atnaujinimas paprastas ir atliekamas per valdymo pultą.

  • Vektoriniai indeksai veikia tik 0 duomenų bazėje, kaip ir visi paieškos indeksai Redis aplinkoje.
  • Vektorinių aibių (vector sets) funkcionalumas – paprastesnė alternatyva be papildomos konfigūracijos. Komandos VADD ir VSIM yra integruotos į Redis, tad galite iškart naudoti panašumo paiešką be jokių modulių aktyvavimo. Tai patogu paprastoms rekomendacijų užduotims su viena aibe. Jei reikia filtrų, puslapiavimo ar kelių laukų, užklausų variklis yra geresnis pasirinkimas.
  • Indekso kūrimas esamiems raktams vyksta fone. Progresą galite stebėti su FT.INFO. Paieškos užklausos grąžins dalinius rezultatus, kol indeksavimas nebus baigtas.

Daugiau apie vektorinę paiešką Redis rasite oficialioje Redis vector search dokumentacijoje.