docs-site/content/0.24.1/api/vector-search.md
Typesense supports the ability to add embeddings generated by your Machine Learning models to each document, and then doing a nearest-neighbor search on them. This lets you build features like similarity search, recommendations, semantic search, visual search, etc.
An embedding for a JSON document is just an array of floating point numbers (eg: [0.4422, 0.49292, 0.1245, ...]), that is an alternate numeric representation of the document.
These embeddings are generated by Machine Learning models in such a way that documents that are "similar" to each other (for different definitions of similarity depending on the model used), have embeddings that are "closer" to each other (cosine similarity).
Here are some common models you can use to generate these document embeddings: Sentence-BERT, CLIP, OpenAI's Embeddings model, word2vec, etc.
Once you've generated these embeddings, you can import them into Typesense into a special vector field and then do a nearest neighbor search, giving another set of vectors or a document ID as the input, and get the documents that are closest (cosine similarity) to your input.
Here's an example practical application of vector search - a "Find Similar" feature in an ecommerce store: ecommerce-store.typesense.org. (Click on Find Similar below each product).
Here are two articles that go into more depth about embeddings:
Let's now discuss how to do index and search embeddings in Typesense.
We'll assume that you've already generated your embeddings using a machine learning model. If not, here's a quick example of how to use the Sentence-BERT model to generate embeddings.
Once your document embeddings are ready, you want to create a collection that contains a float[] field
with a num_dim property for indexing them. The num_dim property specifies the number of
dimensions (length of the float array) that your embeddings contain.
Let's create a collection called docs with a vector field called vec that contains just 4 dimensions.
:::tip We create a vector with 4 dimensions to keep the code snippets readable. Depending on what model you use, real world use will require creating vector fields with at least 256 dimensions to produce good results. :::
<Tabs :tabs="['JavaScript','PHP','Python','Ruby', 'Dart','Java','Shell']"> <template v-slot:JavaScript>let schema = {
'name': 'docs',
'fields': [
{
'name': 'title',
'type': 'string'
},
{
'name': 'points',
'type': 'int32'
},
{
'name': 'vec',
'type': 'float[]',
'num_dim': 4
}
],
'default_sorting_field': 'points'
}
client.collections().create(schema)
$schema = [
'name' => 'docs',
'fields' => [
[
'name' => 'title',
'type' => 'string'
],
[
'name' => 'points',
'type' => 'int32'
],
[
'name' => 'vec',
'type' => 'float[]',
'num_dim' => 4
]
],
'default_sorting_field' => 'points'
];
$client->collections->create($schema);
schema = {
'name': 'docs',
'fields': [
{
'name' : 'title',
'type' : 'string'
},
{
'name' : 'points',
'type' : 'int32'
},
{
'name' : 'vec',
'type' : 'float[]',
'num_dim' : 4
}
],
'default_sorting_field': 'points'
}
client.collections.create(schema)
schema = {
'name' => 'places',
'fields' => [
{
'name' => 'title',
'type' => 'string'
},
{
'name' => 'points',
'type' => 'int32'
},
{
'name' => 'vec',
'type' => 'float[]',
'num_dim' => 4
}
],
'default_sorting_field' => 'points'
}
client.collections.create(schema)
final schema = Schema(
'docs',
{
Field('title', type: Type.string),
Field('points', type: Type.int32),
Field('vec', type: Type.float, isMultivalued: true, dimensions: 4),
},
defaultSortingField: Field('points', type: Type.int32),
);
await client.collections.create(schema);
CollectionSchema collectionSchema = new CollectionSchema();
collectionschema.name("docs")
.addFieldsItem(new Field().name("title").type("string"))
.addFieldsItem(new Field().name("points").type("int32"))
.addFieldsItem(new Field().name("vec").type("float[]").num_dim(4))
.defaultSortingField("points");
CollectionResponse collectionResponse = client.collections().create(collectionSchema);
curl -k "http://localhost:8108/collections" -X POST \
-H "Content-Type: application/json" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" -d '{
"name": "docs",
"fields": [
{"name": "title", "type": "string" },
{"name": "points", "type": "int32" },
{"name": "vec", "type": "float[]", "num_dim": 4}
],
"default_sorting_field": "points"
}'
Let's now index a document with a vector.
<Tabs :tabs="['JavaScript','PHP','Python','Ruby', 'Dart','Java','Shell']"> <template v-slot:JavaScript>let document = {
'title': 'Louvre Museuem',
'points': 1,
'vec': [0.04, 0.234, 0.113, 0.001]
}
client.collections('docs').documents().create(document)
$document = [
'title' => 'Louvre Museuem',
'points' => 1,
'vec' => array(0.04, 0.234, 0.113, 0.001)
];
$client->collections['docs']->documents->create($document);
document = {
'title': 'Louvre Museuem',
'points': 1,
'vec': [0.04, 0.234, 0.113, 0.001]
}
client.collections['docs'].documents.create(document)
document = {
'title' => 'Louvre Museuem',
'points' => 1,
'vec' => [0.04, 0.234, 0.113, 0.001]
}
client.collections['docs'].documents.create(document)
final document = {
'title': 'Louvre Museuem',
'points': 1,
'vec': [0.04, 0.234, 0.113, 0.001]
};
await client.collection('docs').documents.create(document);
HaashMap<String, Object> document = new HashMap<>();
float[] vec = {0.04, 0.234, 0.113, 0.001}
document.add("title", "Louvre Museuem");
document.add("points", 1);
document.add("vec", vec);
client.collection("docs").documents.create(document);
curl "http://localhost:8108/collections/docs/documents" -X POST \
-H "Content-Type: application/json" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
-d '{"points":1,"title":"Louvre Museuem", "vec": [0.04, 0.234, 0.113, 0.001]}'
We can now search for documents that contain a vec field value "closest" to a given query vector.
To control the number of documents that are returned, you can either use the per_page pagination parameter or
the k parameter within the vector query.
let searchRequests = {
'searches': [
{
'collection': 'docs',
'q': '*',
'vector_query' : 'vec:([0.96826, 0.94, 0.39557, 0.306488], k:100)'
}
]
}
let commonSearchParams = {}
client.multiSearch.perform(searchRequests, commonSearchParams)
$searchRequests = [
'searches' => [
[
'collection' => 'docs',
'q' => '*',
'vector_query' => 'vec:([0.96826, 0.94, 0.39557, 0.306488], k:100)'
]
]
];
// Search parameters that are common to all searches go here
$commonSearchParams = [];
$client->multiSearch->perform($searchRequests, $commonSearchParams);
search_requests = {
'searches': [
{
'collection': 'docs',
'q' : '*',
'vector_query': 'vec:([0.96826, 0.94, 0.39557, 0.306488], k:100)'
}
]
}
# Search parameters that are common to all searches go here
common_search_params = {}
client.multi_search.perform(search_requests, common_search_params)
search_requests = {
'searches': [
{
'collection' => 'docs',
'q' => '*',
'vector_query' => 'vec:([0.96826, 0.94, 0.39557, 0.306488], k:100)'
}
]
}
# Search parameters that are common to all searches go here
common_search_params = {}
client.multi_search.perform(search_requests, common_search_params)
final searchRequests = {
'searches': [
{
'collection': 'docs',
'q': '*',
'vector_query': 'vec:([0.96826, 0.94, 0.39557, 0.306488], k:100)',
}
]
};
// Search parameters that are common to all searches go here
final commonSearchParams = {};
await client.multiSearch.perform(searchRequests, queryParams: commonSearchParams);
HashMap<String,String > search1 = new HashMap<>();
search1.put("collection","docs");
search1.put("q","*");
search1.put("vector_query", "vec:([0.96826, 0.94, 0.39557, 0.306488], k:100)");
List<HashMap<String, String>> searches = new ArrayList<>();
searches.add(search1);
HashMap<String, List<HashMap<String ,String>>> searchRequests = new HashMap<>();
searchRequests.put("searches", searches);
HashMap<String,String> commonSearchParams = new HashMap<>();
commonSearchParams.put("query_by","name");
client.multiSearch.perform(searchRequests, commonSearchParams);
curl 'http://localhost:8108/multi_search?collection=docs' -X POST -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
--data-raw '{"searches":[{"q":"*", "vector_query": "vec:([0.96826,0.94,0.39557,0.306488])" }]}'
NOTE: If both per_page and k parameters are provided, the larger value is used.
:::tip Since vector search queries tend to be large because of the large dimension of the query vector, we are using the multi_search end-point that sends the search parameters as a POST request body. :::
Every matching hit in the response will contain a vector_distance field that indicates how "close" the document's
vector value is to the query vector. Typesense uses the cosine similarity, so this distance will be a value between
0 and 2.
02.The hits are automatically sorted in ascending order of the vector_distance, i.e. best matching
documents appear first.
Sample Response
<Tabs :tabs="['JSON']"> <template v-slot:JSON>{
"facet_counts": [],
"found": 1,
"hits": [
{
"document": {
"id": "0",
"vec": [
0.04, 0.234, 0.113, 0.001
]
},
"highlight": {
"full": {},
"snippet": {}
},
"highlights": [],
"vector_distance": 0.19744956493377686
}
],
"out_of": 1,
"page": 1,
"request_params": {
"collection_name": "docs",
"per_page": 10,
"q": "*"
},
"search_cutoff": false,
"search_time_ms": 0
}
If you have a particular document id and want to find documents that are "similar" to this document, you can
do a vector query that references this id directly.
curl 'http://localhost:8108/multi_search?collection=docs' -X POST -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
--data-raw '{"searches":[{"q":"*", "vector_query": "vec:([], id: foobar)" }]}'
By specifying an empty query vector [] and passing an id parameter, this query
would return all documents whose vec value is closest to the foobar document's vec value.
:::tip
The foobar document itself will not be returned in the results.
:::
By default, Typesense uses the built-in HNSW index to do approximate nearest neighbor vector searches. This scales
well for large datasets. However, if you wish to bypass the HNSW index and do a flat / brute-force ranking of
vectors, you can do that via the flat_search_cutoff parameter.
For example, if you wish to do brute-force vector search when a given query matches fewer than 20 documents, sending
flat_search_cutoff=20 will bypass the HNSW index when the number of results found is less than 20.
Here's an example where we are filtering on the category field and asking the vector search to use direct
flat searching if the number of results produced by the filtering operation is less than 20 results.
curl 'http://localhost:8108/multi_search?collection=docs' -X POST -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
--data-raw '{"searches":[{"q":"*", "filter_by": "category:shoes", "vector_query": "vec:([0.96826, 0.94, 0.39557, 0.306488], k:100, flat_search_cutoff: 20)" }]}'
Here's a demo that shows you how to implement a "Find Similar" feature using Vector Search in an ecommerce store: https://ecommerce-store.typesense.org/.
Click on "Find Similar" below each product tile for notes on how to implement this.
Here's a demo that shows you how to implement Semantic Search, using an embeddings API and Vector Search: https://github.com/typesense/typesense-instantsearch-semantic-search-demo