@@ -9,10 +9,10 @@ categories:
99- oss
1010- kubernetes
1111- clients
12- description : Store and query vector embeddings with Redis
13- linkTitle : Vector embeddings
14- title : Vector embeddings
15- weight : 5
12+ description : Learn how to index and query vector embeddings with Redis
13+ linkTitle : Index and query vectors
14+ title : Index and query vectors
15+ weight : 4
1616---
1717
1818[ Redis Query Engine] ({{< relref "/develop/interact/search-and-query" >}})
@@ -28,196 +28,207 @@ similarity of an embedding generated from some query text with embeddings stored
2828or JSON fields, Redis can retrieve documents that closely match the query in terms
2929of their meaning.
3030
31- The [ RedisVL ] ({{< relref "/develop/clients/redis-vl" >}}) library provides a
32- high-level Python API to help you use vector embeddings with Redis Query
33- Engine. The example below shows how RedisVL can retrieve data
34- that has a similar meaning to the query text you supply .
31+ In the example below, we use the
32+ [ ` sentence-transformers ` ] ( https://pypi.org/project/sentence-transformers/ )
33+ library to generate vector embeddings to store and index with
34+ Redis Query Engine .
3535
3636## Initialize
3737
38- Start by importing the required classes:
38+ Install [ ` redis-py ` ] ({{< relref "/develop/clients/redis-py" >}}) if you
39+ have not already done so. Also, install ` sentence-transformers ` with the
40+ following command:
41+
42+ ``` bash
43+ pip install sentence-transformers
44+ ```
45+
46+ In a new Python source file, start by importing the required classes:
3947
4048``` python
41- from redisvl.utils.vectorize.text.huggingface import (
42- HFTextVectorizer
43- )
44- from redisvl.index import SearchIndex
45- from redisvl.query import VectorQuery
49+ from sentence_transformers import SentenceTransformer
50+ from redis.commands.search.query import Query
51+ from redis.commands.search.field import TextField, TagField, VectorField
52+ from redis.commands.search.indexDefinition import IndexDefinition, IndexType
53+
54+ import numpy as np
55+ import redis
4656```
4757
4858The first of these imports is the
49- [ ` HFTextVectorizer ` ] ( https://docs.redisvl.com/en/stable/api/vectorizer.html#hftextvectorizer )
50- class, which generates
51- an embedding from a section sample of text. Here, we create an instance of ` HFTextVectorizer `
52- that uses the
59+ ` SentenceTransformer ` class, which generates an embedding from a section of text.
60+ Here, we create an instance of ` SentenceTransformer ` that uses the
5361[ ` all-MiniLM-L6-v2 ` ] ( https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2 )
5462model for the embeddings. This model generates vectors with 384 dimensions, regardless
5563of the length of the input text, but note that the input is truncated to 256
5664tokens (see
5765[ Word piece tokenization] ( https://huggingface.co/learn/nlp-course/en/chapter6/6 )
58- at the [ Hugging Face] ( https://huggingface.co/ ) docs to learn more about how tokens
66+ at the [ Hugging Face] ( https://huggingface.co/ ) docs to learn more about the way tokens
5967are related to the original text).
6068
6169``` python
62- hf = HFTextVectorizer( model = " sentence-transformers/all-MiniLM-L6-v2" )
70+ model = SentenceTransformer( " sentence-transformers/all-MiniLM-L6-v2" )
6371```
6472
6573## Create the index
6674
67- RedisVL's
68- [ ` SearchIndex ` ] ( https://docs.redisvl.com/en/stable/api/searchindex.html#searchindex-api )
69- class provides the
70- [ ` from_dict() ` ] ( https://docs.redisvl.com/en/stable/api/searchindex.html#redisvl.index.SearchIndex.from_dict )
71- method, which lets you specify your index schema with a Python dictionary, as shown
72- below. Another option is
73- [ ` from_yaml() ` ] ( https://docs.redisvl.com/en/stable/api/searchindex.html#redisvl.index.SearchIndex.from_yaml ) ,
74- which loads the index schema from a [ YAML] ( https://en.wikipedia.org/wiki/YAML ) file.
75+ Connect to Redis and delete any index previously created with the
76+ name ` vector_idx ` . (The ` dropindex() ` call throws an exception if
77+ the index doesn't already exist, which is why you need the
78+ ` try: except: ` block.)
79+
80+ ``` python
81+ r = redis.Redis(decode_responses = True )
7582
83+ try :
84+ r.ft(" vector_idx" ).dropindex(True )
85+ except redis.exceptions.ResponseError:
86+ pass
87+ ```
88+
89+ Next, we create the index.
7690The schema in the example below specifies hash objects for storage and includes
7791three fields: the text content to index, a
7892[ tag] ({{< relref "/develop/interact/search-and-query/advanced-concepts/tags" >}})
7993field to represent the "genre" of the text, and the embedding vector generated from
80- the original text content. The attributes of the vector field (specified in the
81- ` attrs ` object) specify [ HNSW] ({{< relref "/develop/interact/search-and-query/advanced-concepts/vectors#hnsw-index" >}}) indexing, the
94+ the original text content. The ` embedding ` field specifies
95+ [ HNSW] ({{< relref "/develop/interact/search-and-query/advanced-concepts/vectors#hnsw-index" >}})
96+ indexing, the
8297[ L2] ({{< relref "/develop/interact/search-and-query/advanced-concepts/vectors#distance-metrics" >}})
8398vector distance metric, ` Float32 ` values to represent the vector's components,
8499and 384 dimensions, as required by the ` all-MiniLM-L6-v2 ` embedding model.
85100
86101``` python
87- index = SearchIndex.from_dict({
88- " index" : {
89- " name" : " vector_idx" ,
90- " prefix" : " doc" ,
91- " storage_type" : " hash" ,
92- },
93- " fields" : [
94- {" name" : " content" , " type" : " text" },
95- {" name" : " genre" , " type" : " tag" },
96- {
97- " name" : " embedding" ,
98- " type" : " vector" ,
99- " attrs" : {
100- " algorithm" : " HNSW" ,
101- " dims" : 384 ,
102- " distance_metric" : " L2" ,
103- " datatype" : " FLOAT32" ,
104- },
105- },
106- ],
107- })
108- ```
109-
110- When you have created the ` SearchIndex ` object, you must connect to the Redis
111- server using the
112- [ ` connect() ` ] ( https://docs.redisvl.com/en/stable/api/searchindex.html#redisvl.index.SearchIndex.connect )
113- method and then use the
114- [ ` create() ` ] ( https://docs.redisvl.com/en/stable/api/searchindex.html#redisvl.index.SearchIndex.create )
115- method to create the index in the database. The ` overwrite ` parameter for ` create() `
116- ensures that the index you create replaces any existing index with
117- the same name. The ` drop ` parameter deletes any objects that were
118- indexed by the index you are replacing.
102+ schema = (
103+ TextField(" content" ),
104+ TagField(" genre" ),
105+ VectorField(" embedding" , " HNSW" , {
106+ " TYPE" : " FLOAT32" ,
107+ " DIM" : 384 ,
108+ " DISTANCE_METRIC" :" L2"
109+ })
110+ )
119111
120- ``` python
121- index.connect(" redis://localhost:6379" )
122- index.create(overwrite = True , drop = True )
112+ r.ft(" vector_idx" ).create_index(
113+ schema,
114+ definition = IndexDefinition(
115+ prefix = [" doc:" ], index_type = IndexType.HASH
116+ )
117+ )
123118```
124119
125120## Add data
126121
127122You can now supply the data objects, which will be indexed automatically
128- when you add them using the
129- [ ` load() ` ] ( https://docs.redisvl.com/en/stable/api/searchindex.html#redisvl.index.SearchIndex.load )
130- method. Use the
131- [ ` embed() ` ] ( https://docs.redisvl.com/en/stable/api/vectorizer.html#redisvl.utils.vectorize.text.huggingface.HFTextVectorizer.embed )
132- method of the ` HFTextVectorizer ` instance to create the embedding that represents the ` content ` field. By default, ` embed() ` returns a list of ` float ` values to represent the embedding
133- vector, but you can also supply the ` as_buffer ` parameter to encode this list as a
134- binary string. Use the string representation when you are indexing hash objects
123+ when you add them with [ ` hset() ` ] ({{< relref "/commands/hset" >}}), as long as
124+ you use the ` doc: ` prefix specified in the index definition.
125+
126+ Use the ` model.encode() ` method of ` SentenceTransformer `
127+ as shown below to create the embedding that represents the ` content ` field.
128+ The ` astype() ` option that follows the ` model.encode() ` call specifies that
129+ we want a vector of ` float32 ` values. The ` tobytes() ` option encodes the
130+ vector components together as a single binary string rather than the
131+ default Python list of ` float ` values.
132+ Use the binary string representation when you are indexing hash objects
135133(as we are here), but use the default list of ` float ` for JSON objects.
136134
137135``` python
138- hf = HFTextVectorizer(model = " sentence-transformers/all-MiniLM-L6-v2" )
139-
140- data = [
141- {
142- " content" : " That is a very happy person" ,
143- " genre" : " persons" ,
144- " embedding" : hf.embed(" That is a very happy person" , as_buffer = True )
145- },
146- {
147- " content" : " That is a happy dog" ,
148- " genre" : " pets" ,
149- " embedding" : hf.embed(" That is a happy dog" , as_buffer = True )
150- },
151- {
152- " content" : " Today is a sunny day" ,
153- " genre" : " weather" ,
154- " embedding" : hf.embed(" Today is a sunny day" , as_buffer = True )
155- }
156- ]
136+ content = " That is a very happy person"
137+
138+ r.hset(" doc:0" , mapping = {
139+ " content" : content,
140+ " genre" : " persons" ,
141+ " embedding" : model.encode(content).astype(np.float32).tobytes(),
142+ })
143+
144+ content = " That is a happy dog"
145+
146+ r.hset(" doc:1" , mapping = {
147+ " content" : content,
148+ " genre" : " pets" ,
149+ " embedding" : model.encode(content).astype(np.float32).tobytes(),
150+ })
151+
152+ content = " Today is a sunny day"
157153
158- index.load(data)
154+ r.hset(" doc:2" , mapping = {
155+ " content" : content,
156+ " genre" : " weather" ,
157+ " embedding" : model.encode(content).astype(np.float32).tobytes(),
158+ })
159159```
160160
161161## Run a query
162162
163- After you have created the index, you are ready to run a query against it .
163+ After you have created the index and added the data , you are ready to run a query.
164164To do this, you must create another embedding vector from your chosen query
165165text. Redis calculates the similarity between the query vector and each
166166embedding vector in the index as it runs the query. It then ranks the
167167results in order of this numeric similarity value.
168168
169- Create a
170- [ ` VectorQuery ` ] ( https://docs.redisvl.com/en/stable/api/query.html#vectorquery )
171- instance by supplying the embedding vector for your
172- query text, along with the hash or JSON field to match against and the number
173- of results you want, as shown in the example below. Then, call the
174- [ ` query() ` ] ( https://docs.redisvl.com/en/stable/api/searchindex.html#redisvl.index.SearchIndex.query )
175- method of ` SearchIndex ` with your ` VectorQuery ` instance to run the query.
169+ The code below creates the query embedding using ` model.encode() ` , as with
170+ the indexing, and passes it as a parameter when the query executes
171+ (see
172+ [ Vector search] ({{< relref "/develop/interact/search-and-query/query/vector-search" >}})
173+ for more information about using query parameters with embeddings).
176174
177175``` python
178- query = VectorQuery(
179- vector = hf.embed(" That is a happy person" ),
180- vector_field_name = " embedding" ,
181- return_fields = [" content" ],
182- num_results = 3 ,
176+ q = Query(
177+ " *=>[KNN 3 @embedding $vec AS vector_distance]"
178+ ).return_field(" score" ).dialect(2 )
179+
180+ query_text = " That is a happy person"
181+
182+ res = r.ft(" vector_idx" ).search(
183+ q, query_params = {
184+ " vec" : model.encode(query_text).astype(np.float32).tobytes()
185+ }
183186)
184187
185- results = index.query(query)
186- print (results)
188+ print (res)
187189```
188190
189191The code is now ready to run, but note that it may take a while to complete when
190192you run it for the first time (which happens because RedisVL must download the
191193` all-MiniLM-L6-v2 ` model data before it can
192- generate the embeddings). When you run the code, it outputs the following results
193- as a list of dict objects :
194+ generate the embeddings). When you run the code, it outputs the following result
195+ object (slightly formatted here for clarity) :
194196
195197``` Python
196- [
197- {
198- ' id' : ' doc:cf3c28e7fdd44753af3080a9a7f8d8e9' ,
199- ' vector_distance' : ' 0.114169985056' ,
200- ' content' : ' That is a very happy person'
201- },
202- {
203- ' id' : ' doc:614508f297b644d0be47dde5373bb059' ,
204- ' vector_distance' : ' 0.610845386982' ,
205- ' content' : ' That is a happy dog'
206- },
207- {
208- ' id' : ' doc:930a7dfca0d74808baee490d747c9534' ,
209- ' vector_distance' : ' 1.48624813557' ,
210- ' content' : ' Today is a sunny day'
211- }
212- ]
198+ Result{
199+ 3 total,
200+ docs: [
201+ Document {
202+ ' id' : ' doc:0' ,
203+ ' payload' : None ,
204+ ' vector_distance' : ' 0.114169985056' ,
205+ ' content' : ' That is a very happy person'
206+ },
207+ Document {
208+ ' id' : ' doc:1' ,
209+ ' payload' : None ,
210+ ' vector_distance' : ' 0.610845386982' ,
211+ ' content' : ' That is a happy dog'
212+ },
213+ Document {
214+ ' id' : ' doc:2' ,
215+ ' payload' : None ,
216+ ' vector_distance' : ' 1.48624813557' ,
217+ ' content' : ' Today is a sunny day'
218+ }
219+ ]
220+ }
213221```
214222
215223Note that the results are ordered according to the value of the ` vector_distance `
216224field, with the lowest distance indicating the greatest similarity to the query.
217- As you would expect, the text * "That is a very happy person"* is the result that is
218- most similar in meaning to the query text * "That is a happy person"* .
225+ As you would expect, the result for ` doc:0 ` with the content text * "That is a very happy person"*
226+ is the result that is most similar in meaning to the query text
227+ * "That is a happy person"* .
219228
220229## Learn more
221230
222- See the [ RedisVL documentation] ( https://docs.redisvl.com/en/stable/index.html )
223- for more details about its features and examples of how to use them.
231+ See
232+ [ Vector search] ({{< relref "/develop/interact/search-and-query/query/vector-search" >}})
233+ for more information about the indexing options, distance metrics, and query format
234+ for vectors.
0 commit comments