@@ -16,57 +16,54 @@ weight: 3
16
16
---
17
17
18
18
[ Redis Query Engine] ({{< relref "/develop/interact/search-and-query" >}})
19
- lets you index vector fields in [ hash] ({{< relref "/develop/data-types/hashes" >}})
19
+ enables you to index vector fields in [ hash] ({{< relref "/develop/data-types/hashes" >}})
20
20
or [ JSON] ({{< relref "/develop/data-types/json" >}}) objects (see the
21
21
[ Vectors] ({{< relref "/develop/interact/search-and-query/advanced-concepts/vectors" >}})
22
22
reference page for more information).
23
- Among other things, vector fields can store * text embeddings* , which are AI-generated vector
24
- representations of the semantic information in pieces of text. The
23
+
24
+ Vector fields can store * text embeddings* , which are AI-generated vector
25
+ representations of text content. The
25
26
[ vector distance] ({{< relref "/develop/interact/search-and-query/advanced-concepts/vectors#distance-metrics" >}})
26
- between two embeddings indicates how similar they are semantically. By comparing the
27
- similarity of an embedding generated from some query text with embeddings stored in hash
28
- or JSON fields, Redis can retrieve documents that closely match the query in terms
29
- of their meaning.
27
+ between two embeddings measures their semantic similarity. When you compare the
28
+ similarity of a query embedding with stored embeddings, Redis can retrieve documents
29
+ that closely match the query's meaning.
30
30
31
31
In the example below, we use the
32
32
[ ` @xenova/transformers ` ] ( https://www.npmjs.com/package/@xenova/transformers )
33
33
library to generate vector embeddings to store and index with
34
- Redis Query Engine.
34
+ Redis Query Engine. The code is first demonstrated for hash documents with a
35
+ separate section to explain the
36
+ [ differences with JSON documents] ( #differences-with-json-documents ) .
35
37
36
38
## Initialize
37
39
38
- Install [ ` node-redis ` ] ({{< relref "/develop/clients/nodejs" >}}) if you
39
- have not already done so. Also, install ` @xenova/transformers ` with the
40
- following command:
40
+ Install the required dependencies:
41
+
42
+ 1 . Install [ ` node-redis ` ] ({{< relref "/develop/clients/nodejs" >}}) if you haven't already.
43
+ 2 . Install ` @xenova/transformers ` :
41
44
42
45
``` bash
43
46
npm install @xenova/transformers
44
47
```
45
48
46
- In a new JavaScript source file, start by importing the required classes:
49
+ In your JavaScript source file, import the required classes:
47
50
48
51
``` js
49
52
import * as transformers from ' @xenova/transformers' ;
50
53
import {VectorAlgorithms , createClient , SchemaFieldTypes } from ' redis' ;
51
54
```
52
55
53
- The first of these imports is the ` @xenova/transformers ` module, which handles
54
- the embedding models.
55
- Here, we use an instance of the
56
+ The ` @xenova/transformers ` module handles embedding models. This example uses the
56
57
[ ` all-distilroberta-v1 ` ] ( https://huggingface.co/sentence-transformers/all-distilroberta-v1 )
57
- model for the embeddings. This model generates vectors with 768 dimensions, regardless
58
- of the length of the input text, but note that the input is truncated to 128
59
- tokens (see
60
- [ Word piece tokenization] ( https://huggingface.co/learn/nlp-course/en/chapter6/6 )
61
- at the [ Hugging Face] ( https://huggingface.co/ ) docs to learn more about the way tokens
62
- are related to the original text).
63
-
64
- The ` pipe ` value obtained here is a function that we can call to generate the
65
- embeddings. We also need an object to pass some options for the ` pipe() ` function
66
- call. These specify the way the sentence embedding is generated from individual
67
- token embeddings (see the
58
+ model, which:
59
+ - Generates 768-dimensional vectors
60
+ - Truncates input to 128 tokens
61
+ - Uses word piece tokenization (see [ Word piece tokenization] ( https://huggingface.co/learn/nlp-course/en/chapter6/6 )
62
+ at the [ Hugging Face] ( https://huggingface.co/ ) docs for details)
63
+
64
+ The ` pipe ` function generates embeddings. The ` pipeOptions ` object specifies how to generate sentence embeddings from token embeddings (see the
68
65
[ ` all-distilroberta-v1 ` ] ( https://huggingface.co/sentence-transformers/all-distilroberta-v1 )
69
- docs for more information).
66
+ documentation for details):
70
67
71
68
``` js
72
69
let pipe = await transformers .pipeline (
@@ -81,38 +78,35 @@ const pipeOptions = {
81
78
82
79
## Create the index
83
80
84
- Connect to Redis and delete any index previously created with the
85
- name ` vector_idx ` . (The ` dropIndex() ` call throws an exception if
86
- the index doesn't already exist, which is why you need the
87
- ` try...catch ` block.)
81
+ First, connect to Redis and remove any existing index named ` vector_idx ` :
88
82
89
83
``` js
90
84
const client = createClient ({url: ' redis://localhost:6379' });
91
-
92
85
await client .connect ();
93
86
94
- try { await client .ft .dropIndex (' vector_idx' ); } catch {}
87
+ try {
88
+ await client .ft .dropIndex (' vector_idx' );
89
+ } catch (e) {
90
+ // Index doesn't exist, which is fine
91
+ }
95
92
```
96
93
97
- Next, create the index.
98
- The schema in the example below specifies hash objects for storage and includes
99
- three fields: the text content to index, a
100
- [ tag] ({{< relref "/develop/interact/search-and-query/advanced-concepts/tags" >}})
101
- field to represent the "genre" of the text, and the embedding vector generated from
102
- the original text content. The ` embedding ` field specifies
103
- [ HNSW] ({{< relref "/develop/interact/search-and-query/advanced-concepts/vectors#hnsw-index" >}})
104
- indexing, the
105
- [ L2] ({{< relref "/develop/interact/search-and-query/advanced-concepts/vectors#distance-metrics" >}})
106
- vector distance metric, ` Float32 ` values to represent the vector's components,
107
- and 768 dimensions, as required by the ` all-distilroberta-v1 ` embedding model.
94
+ Next, create the index with the following schema:
95
+ - ` content ` : Text field for the content to index
96
+ - ` genre ` : Tag field representing the text's genre
97
+ - ` embedding ` : Vector field with:
98
+ - HNSW indexing
99
+ - L2 distance metric
100
+ - Float32 values
101
+ - 768 dimensions (matching the embedding model)
108
102
109
103
``` js
110
104
await client .ft .create (' vector_idx' , {
111
105
' content' : {
112
106
type: SchemaFieldTypes .TEXT ,
113
107
},
114
108
' genre' : {
115
- type: SchemaFieldTypes .TAG ,
109
+ type: SchemaFieldTypes .TAG ,
116
110
},
117
111
' embedding' : {
118
112
type: SchemaFieldTypes .VECTOR ,
@@ -121,50 +115,37 @@ await client.ft.create('vector_idx', {
121
115
DISTANCE_METRIC : ' L2' ,
122
116
DIM : 768 ,
123
117
}
124
- },{
118
+ }, {
125
119
ON : ' HASH' ,
126
120
PREFIX : ' doc:'
127
121
});
128
122
```
129
123
130
124
## Add data
131
125
132
- You can now supply the data objects, which will be indexed automatically
133
- when you add them with [ ` hSet() ` ] ({{< relref "/commands/hset" >}}), as long as
134
- you use the ` doc: ` prefix specified in the index definition.
135
-
136
- Use the ` pipe() ` method and the ` pipeOptions ` object that we created earlier to
137
- generate the embedding that represents the ` content ` field.
138
- The object returned by ` pipe() ` includes a ` data ` attribute, which is a
139
- [ ` Float32Array ` ] ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array )
140
- that contains the embedding data. If you are indexing hash objects, as
141
- we are here, then you must also call
142
- [ ` Buffer.from() ` ] ( https://nodejs.org/api/buffer.html#static-method-bufferfromarraybuffer-byteoffset-length )
143
- on this array's ` buffer ` value to convert the ` Float32Array `
144
- to a binary string. If you are indexing JSON objects, you can just
145
- use the ` Float32Array ` directly to represent the embedding.
146
-
147
- Make the ` hSet() ` calls within a
148
- [ ` Promise.all() ` ] ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all )
149
- call to create a Redis [ pipeline] ({{< relref "/develop/use/pipelining" >}})
150
- (not to be confused with the ` @xenova/transformers ` pipeline).
151
- This combines the commands together into a batch to reduce network
152
- round trip time.
126
+ Add data objects to the index using ` hSet() ` . The index automatically processes objects with the ` doc: ` prefix.
127
+
128
+ For each document:
129
+ 1 . Generate an embedding using the ` pipe() ` function and ` pipeOptions `
130
+ 2 . Convert the embedding to a binary string using ` Buffer.from() `
131
+ 3 . Store the document with ` hSet() `
132
+
133
+ Use ` Promise.all() ` to batch the commands and reduce network round trips:
153
134
154
135
``` js
155
136
const sentence1 = ' That is a very happy person' ;
156
137
const doc1 = {
157
138
' content' : sentence1,
158
- ' genre' : ' persons' ,
159
- ' embedding' : Buffer .from (
139
+ ' genre' : ' persons' ,
140
+ ' embedding' : Buffer .from (
160
141
(await pipe (sentence1, pipeOptions)).data .buffer
161
142
),
162
143
};
163
144
164
145
const sentence2 = ' That is a happy dog' ;
165
146
const doc2 = {
166
147
' content' : sentence2,
167
- ' genre' : ' pets' ,
148
+ ' genre' : ' pets' ,
168
149
' embedding' : Buffer .from (
169
150
(await pipe (sentence2, pipeOptions)).data .buffer
170
151
)
@@ -173,7 +154,7 @@ const doc2 = {
173
154
const sentence3 = ' Today is a sunny day' ;
174
155
const doc3 = {
175
156
' content' : sentence3,
176
- ' genre' : ' weather' ,
157
+ ' genre' : ' weather' ,
177
158
' embedding' : Buffer .from (
178
159
(await pipe (sentence3, pipeOptions)).data .buffer
179
160
)
@@ -188,24 +169,14 @@ await Promise.all([
188
169
189
170
## Run a query
190
171
191
- After you have created the index and added the data, you are ready to run a query.
192
- To do this, you must create another embedding vector from your chosen query
193
- text. Redis calculates the vector distance between the query vector and each
194
- embedding vector in the index and then ranks the results in order of this
195
- distance value.
196
-
197
- The code below creates the query embedding using ` pipe() ` , as with
198
- the indexing, and passes it as a parameter during execution
199
- (see
200
- [ Vector search] ({{< relref "/develop/interact/search-and-query/query/vector-search" >}})
201
- for more information about using query parameters with embeddings).
202
-
203
- The query returns an array of objects representing the documents
204
- that were found (which are hash objects here). The ` id ` attribute
205
- contains the document's key. The ` value ` attribute contains an object
206
- with a key-value entry corresponding to each index field specified in the
207
- ` RETURN ` option of the query.
172
+ To query the index:
173
+ 1 . Generate an embedding for your query text
174
+ 2 . Pass the embedding as a parameter to the search
175
+ 3 . Redis calculates vector distances and ranks results
208
176
177
+ The query returns an array of document objects. Each object contains:
178
+ - ` id ` : The document's key
179
+ - ` value ` : An object with fields specified in the ` RETURN ` option
209
180
210
181
``` js
211
182
const similar = await client .ft .search (
@@ -229,27 +200,109 @@ for (const doc of similar.documents) {
229
200
await client .quit ();
230
201
```
231
202
232
- The code is now ready to run, but note that it may take a while to download the
233
- ` all-distilroberta-v1 ` model data the first time you run it. The
234
- code outputs the following results:
203
+ The first run may take longer as it downloads the model data. The output shows results ordered by score (vector distance), with lower scores indicating greater similarity:
235
204
236
205
```
237
206
doc:1: 'That is a very happy person', Score: 0.127055495977
238
207
doc:2: 'That is a happy dog', Score: 0.836842417717
239
208
doc:3: 'Today is a sunny day', Score: 1.50889515877
240
209
```
241
210
242
- The results are ordered according to the value of the ` score `
243
- field, which represents the vector distance here. The lowest distance indicates
244
- the greatest similarity to the query.
245
- As you would expect, the result for ` doc:1 ` with the content text
246
- * "That is a very happy person"*
247
- is the result that is most similar in meaning to the query text
248
- * "That is a happy person"* .
211
+ ## Differences with JSON documents
212
+
213
+ JSON documents support richer data modeling with nested fields. Key differences from hash documents:
214
+
215
+ 1 . Use paths in the schema to identify fields
216
+ 2 . Declare aliases for paths using the ` AS ` option
217
+ 3 . Set ` ON ` to ` JSON ` when creating the index
218
+ 4 . Use arrays instead of binary strings for vectors
219
+ 5 . Use ` json.set() ` instead of ` hSet() `
220
+
221
+ Create the index with path aliases:
222
+
223
+ ``` js
224
+ await client .ft .create (' vector_json_idx' , {
225
+ ' $.content' : {
226
+ type: SchemaFieldTypes .TEXT ,
227
+ AS : ' content' ,
228
+ },
229
+ ' $.genre' : {
230
+ type: SchemaFieldTypes .TAG ,
231
+ AS : ' genre' ,
232
+ },
233
+ ' $.embedding' : {
234
+ type: SchemaFieldTypes .VECTOR ,
235
+ TYPE : ' FLOAT32' ,
236
+ ALGORITHM : VectorAlgorithms .HNSW ,
237
+ DISTANCE_METRIC : ' L2' ,
238
+ DIM : 768 ,
239
+ AS : ' embedding' ,
240
+ }
241
+ }, {
242
+ ON : ' JSON' ,
243
+ PREFIX : ' jdoc:'
244
+ });
245
+ ```
246
+
247
+ Add data using ` json.set() ` . Convert the ` Float32Array ` to a standard JavaScript array using the spread operator:
248
+
249
+ ``` js
250
+ const jSentence1 = ' That is a very happy person' ;
251
+ const jdoc1 = {
252
+ ' content' : jSentence1,
253
+ ' genre' : ' persons' ,
254
+ ' embedding' : [... (await pipe (jSentence1, pipeOptions)).data ],
255
+ };
256
+
257
+ const jSentence2 = ' That is a happy dog' ;
258
+ const jdoc2 = {
259
+ ' content' : jSentence2,
260
+ ' genre' : ' pets' ,
261
+ ' embedding' : [... (await pipe (jSentence2, pipeOptions)).data ],
262
+ };
263
+
264
+ const jSentence3 = ' Today is a sunny day' ;
265
+ const jdoc3 = {
266
+ ' content' : jSentence3,
267
+ ' genre' : ' weather' ,
268
+ ' embedding' : [... (await pipe (jSentence3, pipeOptions)).data ],
269
+ };
270
+
271
+ await Promise .all ([
272
+ client .json .set (' jdoc:1' , ' $' , jdoc1),
273
+ client .json .set (' jdoc:2' , ' $' , jdoc2),
274
+ client .json .set (' jdoc:3' , ' $' , jdoc3)
275
+ ]);
276
+ ```
277
+
278
+ Query JSON documents using the same syntax, but note that the vector parameter must still be a binary string:
279
+
280
+ ``` js
281
+ const jsons = await client .ft .search (
282
+ ' vector_json_idx' ,
283
+ ' *=>[KNN 3 @embedding $B AS score]' ,
284
+ {
285
+ " PARAMS" : {
286
+ B : Buffer .from (
287
+ (await pipe (' That is a happy person' , pipeOptions)).data .buffer
288
+ ),
289
+ },
290
+ ' RETURN' : [' score' , ' content' ],
291
+ ' DIALECT' : ' 2'
292
+ },
293
+ );
294
+ ```
295
+
296
+ The results are identical to the hash document query, except for the ` jdoc: ` prefix:
297
+
298
+ ```
299
+ jdoc:1: 'That is a very happy person', Score: 0.127055495977
300
+ jdoc:2: 'That is a happy dog', Score: 0.836842417717
301
+ jdoc:3: 'Today is a sunny day', Score: 1.50889515877
302
+ ```
249
303
250
304
## Learn more
251
305
252
306
See
253
307
[ Vector search] ({{< relref "/develop/interact/search-and-query/query/vector-search" >}})
254
- for more information about the indexing options, distance metrics, and query format
255
- for vectors.
308
+ for more information about indexing options, distance metrics, and query format.
0 commit comments