What embeddings and semantic search are
An embedding is a list of numbers, typically a few hundred to a few thousand, that represents the meaning of a piece of text. An embedding model is trained so that texts with similar meanings end up close together. Semantic search embeds the user's query and finds the documents or products whose embeddings are nearest to it.
That lets search match intent even when the wording differs. A shopper searching for "warm jacket for cycling to work" can find a product described as a "windproof insulated commuter coat", and an employee asking "how do I claim travel costs" can find the expenses policy.
This guide is for product managers and engineers improving product search, site search or internal knowledge search. It covers choosing a model, where to store vectors, combining semantic and keyword ranking, and measuring relevance so you can tell whether a change helped.
Choosing an embedding model
There are many capable embedding models, both commercial and open-weight. Public leaderboards are a reasonable starting point, but they rank models on benchmark data, and what counts is performance on your queries and your content. Weigh these factors:
- Retrieval quality on your data, measured with your own evaluation set as described below.
- Language coverage. A Dutch and English catalogue needs a multilingual model that handles both, including mixed queries.
- Dimensions and storage. Larger vectors can capture more but cost more to store and search. Some models support shortened embeddings with a modest loss in quality.
- Input length. The maximum tokens per input limits how long a chunk or product description can be.
- Hosting and data rules. An API model is simple to run; an open-weight model you host keeps data inside your environment.
- Cost and latency, both for indexing the full catalogue and for every query.
Pick two or three candidates, embed a representative sample and compare them on the same evaluation set. Store the model name and version with every vector. Switching models means re-embedding everything, because vectors from different models are not comparable.
pgvector or a dedicated vector database
If your data already lives in Postgres, pgvector adds a vector column type, distance operators and approximate nearest neighbour indexes (HNSW and IVFFlat). The basics fit in a few lines:
Dedicated vector databases add features aimed at very large or very demanding workloads. The trade-offs:
| Consideration | pgvector | Dedicated vector database |
|---|---|---|
| Operations | One database you already run, back up and secure | Another system to run, monitor and pay for |
| Filtering and joins | Full SQL: permissions, stock, price and category in the same query | Metadata filters, with varying support for complex conditions |
| Consistency | Vectors update in the same transaction as the source row | Needs a sync pipeline from your source of truth |
| Scale | Handles many production workloads well, with index tuning | Built for very large indexes and high query volumes |
| Extras | Whatever Postgres and its extensions provide | Often built-in hybrid search, multi-tenancy and managed scaling |
Our default is pgvector until measurements show a reason to move. For most product catalogues and knowledge bases, keeping vectors next to the data they describe removes a whole category of sync bugs.
Hybrid ranking
Pure semantic search has blind spots. It can rank a similar product above the exact one a user typed, and it handles SKUs, model numbers, brand names and sizes poorly. Keyword search, with BM25 or Postgres full-text search, is strong in exactly those places.
Hybrid ranking runs both and merges the results. Common approaches:
- Reciprocal rank fusion (RRF) combines the ranks from each list without needing comparable scores. It is simple and a good default.
- Weighted score blending normalises both scores and mixes them, which gives more control but needs tuning.
- Reranking passes the merged top results to a cross-encoder or LLM that scores each one against the query.
- Business signals such as stock, margin, popularity and personalisation are applied as a final layer, kept separate so relevance can be measured on its own.
Our production RAG chatbot guide includes a hybrid query for pgvector with RRF.
Measuring relevance with recall@k and nDCG
Without measurement, search tuning turns into a debate about individual queries. Build a judged set: a few hundred real queries from your search logs, each with the items that are relevant, ideally graded (for example 0 for irrelevant, 1 for partly relevant, 2 for a good match and 3 for exactly right).
Two metrics cover most needs:
- recall@k is the share of relevant items that appear in the top k results. It tells you whether the right results are found at all, which matters most for RAG and for the candidate stage before reranking.
- nDCG@k (normalised discounted cumulative gain) rewards putting the most relevant items at the top, using graded judgements. It reflects how people scan a results page.
Track these per query segment, such as head queries, long-tail queries and queries containing SKUs, because an average can hide a regression in one group. Once offline metrics improve, confirm with online metrics like click-through, search-to-cart rate and the share of searches with no results. For the retrieval side of RAG specifically, see how to evaluate RAG.
Where to start
- 01Export a few hundred real queries from your search logs, including ones that currently return poor results or none.
- 02Judge the relevant results for each query, with graded relevance where you can.
- 03Measure your current search with recall@k and nDCG@k as a baseline.
- 04Add embeddings in
pgvectorfor two candidate models and compare them. - 05Combine with keyword search using RRF, add a reranker if needed, and measure each step.
- 06Release behind a feature flag, then check online metrics against the baseline.
This is the approach we took for semantic product search in retail. If you want help setting up the evaluation or the search itself, how we work explains how we run projects like this.