Alle artikelen
RAG

5 min leestijd

Embeddings en semantisch zoeken: een praktische gids voor productteams

Hoe embeddings semantisch zoeken mogelijk maken, hoe je een model en vectoropslag kiest, en hoe je meet of je zoekresultaten echt beter zijn geworden.

Wat embeddings en semantisch zoeken zijn

Een embedding is een lijst getallen, meestal een paar honderd tot een paar duizend, die de betekenis van een stuk tekst weergeeft. Een embeddingmodel is zo getraind dat teksten met een vergelijkbare betekenis dicht bij elkaar uitkomen. Semantisch zoeken maakt een embedding van de zoekvraag en vindt de documenten of producten waarvan de embeddings er het dichtst bij liggen.

Zo vindt een zoekfunctie de bedoeling, ook als de woorden verschillen. Een klant die zoekt op "warme jas om naar je werk te fietsen" vindt een product dat is beschreven als "winddichte gevoerde forensenjas". Een medewerker die vraagt "hoe declareer ik reiskosten" vindt het declaratiebeleid.

Deze gids is voor productmanagers en engineers die productzoeken, zoeken op de website of zoeken in interne kennis willen verbeteren. We behandelen hoe je een model kiest, waar je vectoren opslaat, hoe je semantische ranking combineert met ranking op trefwoord en hoe je relevantie meet, zodat je ziet of een wijziging heeft geholpen.

Een embeddingmodel kiezen

Er zijn veel goede embeddingmodellen, commercieel en met open gewichten. Openbare ranglijsten zijn een redelijk startpunt, maar ze rangschikken modellen op benchmarkdata. Wat telt, is hoe een model presteert op jouw zoekvragen en jouw content. Weeg deze factoren af:

  • Zoekkwaliteit op je eigen data, gemeten met je eigen evaluatieset zoals hieronder beschreven.
  • Talen. Een Nederlandse en Engelse catalogus vraagt om een meertalig model dat beide aankan, ook bij gemengde zoekvragen.
  • Dimensies en opslag. Grotere vectoren kunnen meer vastleggen, maar kosten meer opslag en zoekwerk. Sommige modellen ondersteunen verkorte embeddings met een beperkt kwaliteitsverlies.
  • Lengte van de input. Het maximale aantal tokens per input bepaalt hoe lang een chunk of productbeschrijving mag zijn.
  • Hosting en regels voor data. Een model via een API is eenvoudig in gebruik; een model met open gewichten dat je zelf host, houdt data binnen je eigen omgeving.
  • Kosten en latency, zowel voor het indexeren van de hele catalogus als voor elke zoekvraag.

Kies twee of drie kandidaten, maak embeddings van een representatieve steekproef en vergelijk ze op dezelfde evaluatieset. Sla bij elke vector de naam en versie van het model op. Wissel je van model, dan moet je alles opnieuw embedden, omdat vectoren van verschillende modellen niet vergelijkbaar zijn.

pgvector of een aparte vectordatabase

Staat je data al in Postgres, dan voegt pgvector een kolomtype voor vectoren toe, plus afstandsoperatoren en indexen voor approximate nearest neighbour search (HNSW en IVFFlat). De basis past in een paar regels:

SQL
CREATE EXTENSION IF NOT EXISTS vector;

ALTER TABLE products ADD COLUMN embedding vector(1024);

CREATE INDEX products_embedding_idx
  ON products USING hnsw (embedding vector_cosine_ops);

-- nearest in-stock products to the query embedding
SELECT id, title, 1 - (embedding <=> $1) AS similarity
FROM products
WHERE in_stock
ORDER BY embedding <=> $1
LIMIT 20;

Aparte vectordatabases voegen functies toe voor zeer grote of zeer veeleisende workloads. De afwegingen:

AfwegingpgvectorAparte vectordatabase
BeheerEén database die je al draait, back-upt en beveiligtNog een systeem om te draaien, te monitoren en voor te betalen
Filteren en joinsVolledige SQL: rechten, voorraad, prijs en categorie in dezelfde queryMetadatafilters, met wisselende ondersteuning voor complexe voorwaarden
ConsistentieVectoren worden bijgewerkt in dezelfde transactie als de bronrijVraagt om een sync-pipeline vanuit je leidende bron
SchaalKan veel productieworkloads goed aan, met afgestelde indexenGebouwd voor zeer grote indexen en veel query's
Extra'sAlles wat Postgres en zijn extensies biedenVaak ingebouwd hybride zoeken, multi-tenancy en beheerde schaalbaarheid

Onze standaard is pgvector, tot metingen een reden geven om over te stappen. Bij de meeste productcatalogi en kennisbanken voorkom je een hele categorie synchronisatiefouten door vectoren naast de data te bewaren die ze beschrijven.

Hybride ranking

Puur semantisch zoeken heeft blinde vlekken. Het kan een vergelijkbaar product hoger zetten dan het exacte product dat een gebruiker intypte, en het gaat slecht om met SKU's, modelnummers, merknamen en maten. Zoeken op trefwoord, met BM25 of de full-text search van Postgres, is juist daar sterk.

Hybride ranking voert beide uit en voegt de resultaten samen. Veelgebruikte aanpakken:

  • Reciprocal rank fusion (RRF) combineert de posities uit elke lijst, zonder dat de scores vergelijkbaar hoeven te zijn. Het is eenvoudig en een goede standaard.
  • Gewogen scores mengen normaliseert beide scores en mengt ze. Dat geeft meer controle, maar vraagt om afstemming.
  • Reranking geeft de samengevoegde topresultaten aan een cross-encoder of LLM, die elk resultaat scoort tegen de zoekvraag.
  • Bedrijfssignalen zoals voorraad, marge, populariteit en personalisatie komen als laatste laag. Die houd je apart, zodat je de relevantie los kunt meten.

Onze gids voor een RAG-chatbot in productie bevat een hybride query voor pgvector met RRF.

Relevantie meten met recall@k en nDCG

Zonder metingen wordt het afstellen van zoeken een discussie over losse zoekvragen. Bouw een beoordeelde set: een paar honderd echte zoekvragen uit je zoeklogs, elk met de items die relevant zijn. Geef die items bij voorkeur een cijfer, bijvoorbeeld 0 voor niet relevant, 1 voor deels relevant, 2 voor een goede match en 3 voor precies goed.

Twee metrics dekken de meeste behoeften:

  • recall@k is het aandeel relevante items dat in de bovenste k resultaten staat. Het laat zien of de juiste resultaten überhaupt gevonden worden. Dat telt het meest voor RAG en voor de kandidatenfase vóór reranking.
  • nDCG@k (normalised discounted cumulative gain) beloont het bovenaan zetten van de meest relevante items, op basis van beoordelingen met cijfers. Het sluit aan bij hoe mensen een pagina met resultaten scannen.
Python
import numpy as np

def recall_at_k(retrieved: list[str], relevant: set[str], k: int) -> float:
    if not relevant:
        return 0.0
    return len(set(retrieved[:k]) & relevant) / len(relevant)

def dcg(gains: np.ndarray) -> float:
    discounts = np.log2(np.arange(2, gains.size + 2))
    return float(np.sum((2**gains - 1) / discounts))

def ndcg_at_k(retrieved: list[str], grades: dict[str, int], k: int) -> float:
    gains = np.array([grades.get(doc_id, 0) for doc_id in retrieved[:k]], dtype=float)
    ideal = np.array(sorted(grades.values(), reverse=True)[:k], dtype=float)
    best = dcg(ideal)
    return dcg(gains) / best if best > 0 else 0.0

Volg deze metrics per groep zoekvragen, zoals veelgezochte vragen, long-tail-vragen en vragen met SKU's, omdat een gemiddelde een achteruitgang in één groep kan verbergen. Worden de offline metrics beter, bevestig dat dan met online metrics zoals de doorklikratio, het aandeel zoekopdrachten dat tot een winkelwagen leidt en het aandeel zoekopdrachten zonder resultaten. Voor de retrieval-kant van RAG lees je hoe je RAG evalueert.

Waar je begint

  1. 01Exporteer een paar honderd echte zoekvragen uit je zoeklogs, ook vragen die nu slechte of geen resultaten geven.
  2. 02Beoordeel per zoekvraag de relevante resultaten, waar het kan met cijfers.
  3. 03Meet je huidige zoekfunctie met recall@k en nDCG@k als baseline.
  4. 04Voeg embeddings in pgvector toe voor twee kandidaat-modellen en vergelijk ze.
  5. 05Combineer met zoeken op trefwoord via RRF, voeg zo nodig een reranker toe en meet elke stap.
  6. 06Zet het live achter een feature flag en vergelijk daarna de online metrics met de baseline.

Deze aanpak volgden we voor semantisch productzoeken in retail. Wil je hulp bij het opzetten van de evaluatie of de zoekfunctie zelf, dan legt onze aanpak uit hoe we zulke projecten uitvoeren.

Alle artikelen

Werk samen met Vantion.