Efficient Full Text Search With Vespa: Configure Semantic Search
29 Mar 2024
Semantic search is the process of retrieving information by understanding the meaning behind your query, rather than just matching keywords. For example, if you search for "healthy breakfast recipes," a semantic search engine might return results like "10 Nutritious Breakfast Ideas" or "Quick and Easy Breakfast Options." It considers synonyms, related concepts, and context to provide more relevant results.
Goal
Configure semantic search utilizing e5-small-v2 embedding model
Prerequisites
Before proceeding, ensure Customizing Ranking is completed.
Define Embedding Model in VAP
-
Add necessary e5-small-v2-int8.onnx and
tokenizer.jsonfiles (Use vespa tooling to generate required resources) -
Define the model in services.xml:
<component id="e5" type="hugging-face-embedder"> <transformer-model path="model/e5-small-v2-int8.onnx"/> <tokenizer-model path="model/tokenizer.json"/> </component>
Add Embedding Field
-
Define emb_field as follows:
field emb_field type tensor<float>(x[384]) {
indexing {
# Initialize variables
"" | set_var tags_var | set_var description_var | set_var title_var;
select_input {tags: (input tags | join " ") | set_var tags_var;};
select_input {description: input description | set_var description_var;};
select_input {title: input title | set_var title_var;};
"passage: " . (get_var tags_var) . " " . (get_var description_var) . " " . (get_var title_var) | embed e5 | attribute | index
}
}
Here we concatenate tags, description and title fields to compute embedding using the specified e5 model.
-
Create new book_ann_v1 query-profile as:
<?xml version="1.0" encoding="UTF-8"?>
<query-profile id="book_ann_v1">
<field name="maxHits">100</field>
<field name="maxOffset">100</field>
<field name="hits">10</field>
<field name="ranking.profile">ann</field>
<field name="yql">select * from book where
({"targetHits": 100, "label": "nns"}nearestNeighbor(emb_field, embedding))
</field>
<field name="ranking.features.query(embedding)">embed(e5, "query: %{search_term}")</field>
<field name="search_term"></field>
<field name="timeout">2s</field>
<field name="rules.off">false</field>
</query-profile>
Here, the ranking.features.query(embedding) field computes the embedding based on the search_term parameter and passes it to the nearestNeighbor function for ranking matching documents.
-
Add a new rank profile based on the
nearestNeighborfunction:
rank-profile ann inherits default {
inputs {
query(embedding) tensor<float>(x[384])
}
function annMatchScore () {
expression: closeness(label,nns)
}
first-phase {
expression: annMatchScore
}
}
Here, label nns refers to the annotation defined in the query-profile.
Execute Search
Let’s search for "huge gem":
curl --location 'http://localhost:8080/search/' \
--header 'Content-Type: application/json' \
--data '{
"queryProfile": "book_ann_v1",
"search_term": "huge gem",
"query_filter": "AND year > 1900"
}'
As anticipated, "The Diamond as Big as the Ritz" receives the highest rank.
Summary
By following these steps, we have configured a semantic search that significantly improves the search quality.
Next Steps
Explore Text-To-Image Search to improve search quality further