@@ -16,57 +16,54 @@ weight: 3
1616---
1717
1818[ 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" >}})
2020or [ JSON] ({{< relref "/develop/data-types/json" >}}) objects (see the
2121[ Vectors] ({{< relref "/develop/interact/search-and-query/advanced-concepts/vectors" >}})
2222reference 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
2526[ 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.
3030
3131In the example below, we use the
3232[ ` @xenova/transformers ` ] ( https://www.npmjs.com/package/@xenova/transformers )
3333library 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 ) .
3537
3638## Initialize
3739
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 ` :
4144
4245``` bash
4346npm install @xenova/transformers
4447```
4548
46- In a new JavaScript source file, start by importing the required classes:
49+ In your JavaScript source file, import the required classes:
4750
4851``` js
4952import * as transformers from ' @xenova/transformers' ;
5053import {VectorAlgorithms , createClient , SchemaFieldTypes } from ' redis' ;
5154```
5255
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
5657[ ` 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
6865[ ` all-distilroberta-v1 ` ] ( https://huggingface.co/sentence-transformers/all-distilroberta-v1 )
69- docs for more information).
66+ documentation for details):
7067
7168``` js
7269let pipe = await transformers .pipeline (
@@ -81,38 +78,35 @@ const pipeOptions = {
8178
8279## Create the index
8380
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 ` :
8882
8983``` js
9084const client = createClient ({url: ' redis://localhost:6379' });
91-
9285await client .connect ();
9386
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+ }
9592```
9693
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)
108102
109103``` js
110104await client .ft .create (' vector_idx' , {
111105 ' content' : {
112106 type: SchemaFieldTypes .TEXT ,
113107 },
114108 ' genre' : {
115- type: SchemaFieldTypes .TAG ,
109+ type: SchemaFieldTypes .TAG ,
116110 },
117111 ' embedding' : {
118112 type: SchemaFieldTypes .VECTOR ,
@@ -121,50 +115,37 @@ await client.ft.create('vector_idx', {
121115 DISTANCE_METRIC : ' L2' ,
122116 DIM : 768 ,
123117 }
124- },{
118+ }, {
125119 ON : ' HASH' ,
126120 PREFIX : ' doc:'
127121});
128122```
129123
130124## Add data
131125
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:
153134
154135``` js
155136const sentence1 = ' That is a very happy person' ;
156137const doc1 = {
157138 ' content' : sentence1,
158- ' genre' : ' persons' ,
159- ' embedding' : Buffer .from (
139+ ' genre' : ' persons' ,
140+ ' embedding' : Buffer .from (
160141 (await pipe (sentence1, pipeOptions)).data .buffer
161142 ),
162143};
163144
164145const sentence2 = ' That is a happy dog' ;
165146const doc2 = {
166147 ' content' : sentence2,
167- ' genre' : ' pets' ,
148+ ' genre' : ' pets' ,
168149 ' embedding' : Buffer .from (
169150 (await pipe (sentence2, pipeOptions)).data .buffer
170151 )
@@ -173,7 +154,7 @@ const doc2 = {
173154const sentence3 = ' Today is a sunny day' ;
174155const doc3 = {
175156 ' content' : sentence3,
176- ' genre' : ' weather' ,
157+ ' genre' : ' weather' ,
177158 ' embedding' : Buffer .from (
178159 (await pipe (sentence3, pipeOptions)).data .buffer
179160 )
@@ -188,24 +169,14 @@ await Promise.all([
188169
189170## Run a query
190171
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
208176
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
209180
210181``` js
211182const similar = await client .ft .search (
@@ -229,27 +200,109 @@ for (const doc of similar.documents) {
229200await client .quit ();
230201```
231202
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:
235204
236205```
237206doc:1: 'That is a very happy person', Score: 0.127055495977
238207doc:2: 'That is a happy dog', Score: 0.836842417717
239208doc:3: 'Today is a sunny day', Score: 1.50889515877
240209```
241210
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+ ```
249303
250304## Learn more
251305
252306See
253307[ 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