
# 

Semantic search

## 

Learn how to search by meaning rather than exact keywords.

* * *

Semantic search interprets the meaning behind user queries rather than exact [keywords](../keyword-search/index.md). It uses machine learning to capture the intent and context behind the query, handling language nuances like synonyms, phrasing variations, and word relationships.

## When to use semantic search[#](#when-to-use-semantic-search)

Semantic search is useful in applications where the depth of understanding and context is important for delivering relevant results. A good example is in customer support or knowledge base search engines. Users often phrase their problems or questions in various ways, and a traditional keyword-based search might not always retrieve the most helpful documents. With semantic search, the system can understand the meaning behind the queries and match them with relevant solutions or articles, even if the exact wording differs.

For instance, a user searching for "increase text size on display" might miss articles titled "How to adjust font size in settings" in a keyword-based search system. However, a semantic search engine would understand the intent behind the query and correctly match it to relevant articles, regardless of the specific terminology used.

It's also possible to combine semantic search with keyword search to get the best of both worlds. See [Hybrid search](../hybrid-search/index.md) for more details.

## How semantic search works[#](#how-semantic-search-works)

Semantic search uses an intermediate representation called an “embedding vector” to link database records with search queries. A vector, in the context of semantic search, is a list of numerical values. They represent various features of the text and allow for the semantic comparison between different pieces of text.

The best way to think of embeddings is by plotting them on a graph, where each embedding is a single point whose coordinates are the numerical values within its vector. Importantly, embeddings are plotted such that similar concepts are positioned close together while dissimilar concepts are far apart. For more details, see [What are embeddings?](../concepts/index.md#what-are-embeddings)

Embeddings are generated using a language model, and embeddings are compared to each other using a similarity metric. The language model is trained to understand the semantics of language, including syntax, context, and the relationships between words. It generates embeddings for both the content in the database and the search queries. Then the similarity metric, often a function like cosine similarity or dot product, is used to compare the query embeddings with the document embeddings (in other words, to measure how close they are to each other on the graph). The documents with embeddings most similar to the query's are deemed the most relevant and are returned as search results.

## Embedding models[#](#embedding-models)

There are many embedding models available today. Supabase Edge Functions has [built in support](../../functions/examples/semantic-search/index.md) for the `gte-small` model. Others can be accessed through third-party APIs like [OpenAI](https://platform.openai.com/docs/guides/embeddings), where you send your text in the request and receive an embedding vector in the response. Others can run locally on your own compute, such as through Transformers.js for JavaScript implementations. For more information on local implementation, see [Generate embeddings](../quickstarts/generate-text-embeddings/index.md).

It's crucial to remember that when using embedding models with semantic search, you must use the same model for all embedding comparisons. Comparing embeddings created by different models will yield meaningless results.

## Semantic search in Postgres[#](#semantic-search-in-postgres)

To implement semantic search in Postgres we use `pgvector` - an extension that allows for efficient storage and retrieval of high-dimensional vectors. These vectors are numerical representations of text (or other types of data) generated by embedding models.

1.  Enable the `pgvector` extension by running:
    
    ```
    1create extension vector2with3  schema extensions;
    ```
    
2.  Create a table to store the embeddings:
    
    ```
    1create table documents (2  id bigint primary key generated always as identity,3  content text,4  embedding extensions.vector(512)5);
    ```
    
    Or if you have an existing table, you can add a vector column like so:
    
    ```
    1alter table documents2add column embedding extensions.vector(512);
    ```
    
    In this example, we create a column named `embedding` which uses the newly enabled `vector` data type. The size of the vector (as indicated in parentheses) represents the number of dimensions in the embedding. Here we use 512, but adjust this to match the number of dimensions produced by your embedding model.
    

For more details on vector columns, including how to generate embeddings and store them, see [Vector columns](../vector-columns/index.md).

### Similarity metric[#](#similarity-metric)

`pgvector` support 3 operators for computing distance between embeddings:

**Operator**

**Description**

`<->`

Euclidean distance

`<#>`

negative inner product

`<=>`

cosine distance

These operators are used directly in your SQL query to retrieve records that are most similar to the user's search query. Choosing the right operator depends on your needs. Inner product (also known as dot product) tends to be the fastest if your vectors are normalized.

The easiest way to perform semantic search in Postgres is by creating a function:

```
1-- Match documents using cosine distance (<=>)2create or replace function match_documents (3  query_embedding extensions.vector(512),4  match_threshold float,5  match_count int6)7returns setof documents8language sql9as $$10  select *11  from documents12  where documents.embedding <=> query_embedding < 1 - match_threshold13  order by documents.embedding <=> query_embedding asc14  limit least(match_count, 200);15$$;
```

Here we create a function `match_documents` that accepts three parameters:

1.  `query_embedding`: a one-time embedding generated for the user's search query. Here we set the size to 512, but adjust this to match the number of dimensions produced by your embedding model.
2.  `match_threshold`: the minimum similarity between embeddings. This is a value between 1 and -1, where 1 is most similar and -1 is most dissimilar.
3.  `match_count`: the maximum number of results to return. Note the query may return less than this number if `match_threshold` resulted in a small shortlist. Limited to 200 records to avoid unintentionally overloading your database.

In this example, we return a `setof documents` and refer to `documents` throughout the query. Adjust this to use the relevant tables in your application.

You'll notice we are using the cosine distance (`<=>`) operator in our query. Cosine distance is a safe default when you don't know whether or not your embeddings are normalized. If you know for a fact that they are normalized (for example, your embedding is returned from OpenAI), you can use negative inner product (`<#>`) for better performance:

```
1-- Match documents using negative inner product (<#>)2create or replace function match_documents (3  query_embedding extensions.vector(512),4  match_threshold float,5  match_count int6)7returns setof documents8language sql9as $$10  select *11  from documents12  where documents.embedding <#> query_embedding < -match_threshold13  order by documents.embedding <#> query_embedding asc14  limit least(match_count, 200);15$$;
```

Note that since `<#>` is negative, we negate `match_threshold` accordingly in the `where` clause. For more information on the different operators, see the [pgvector docs](https://github.com/pgvector/pgvector?tab=readme-ov-file#vector-operators).

### Calling from your application[#](#calling-from-your-application)

Finally you can execute this function from your application. If you are using a Supabase client library such as [`supabase-js`](https://github.com/supabase/supabase-js), you can invoke it using the `rpc()` method:

```
1const { data: documents } = await supabase.rpc('match_documents', {2  query_embedding: embedding, // pass the query embedding3  match_threshold: 0.78, // choose an appropriate threshold for your data4  match_count: 10, // choose the number of matches5})
```

You can also call this method directly from SQL:

```
1select *2from match_documents(3  '[...]'::extensions.vector(512), -- pass the query embedding4  0.78, -- chose an appropriate threshold for your data5  10 -- choose the number of matches6);
```

In this scenario, you'll likely use a Postgres client library to establish a direct connection from your application to the database. It's best practice to parameterize your arguments before executing the query.

## Next steps[#](#next-steps)

As your database scales, you will need an index on your vector columns to maintain fast query speeds. See [Vector indexes](../vector-indexes/index.md) for an in-depth guide on the different types of indexes and how they work.

For larger datasets, choosing and tuning the right index is critical for maintaining fast and accurate semantic search.

## pgvector index tuning[#](#pgvector-index-tuning)

When working with embedding datasets at scale (100k+ rows), index selection and tuning can significantly impact query latency and accuracy.

Supabase uses Postgres with the `pgvector` extension, which supports two primary index types for vector similarity search: IVFFlat and HNSW.

### IVFFlat index[#](#ivfflat-index)

**Best for:**

*   Large datasets (100k-10M rows)
*   Fast approximate search
*   Lower memory usage

```
1create index on documents2using ivfflat (embedding vector_cosine_ops)3with (lists = 100);
```

### HNSW index[#](#hnsw-index)

**Best for:**

*   High accuracy requirements
*   Read-heavy workloads
*   Low-latency semantic search
*   Scenarios where recall is more important than memory usage

```
1create index on documents2using hnsw (embedding vector_cosine_ops);
```

## See also[#](#see-also)

*   [Embedding concepts](../concepts/index.md)
*   [Vector columns](../vector-columns/index.md)
*   [Vector indexes](../vector-indexes/index.md)
*   [Hybrid search](../hybrid-search/index.md)
*   [Keyword search](../keyword-search/index.md)
