Skip to content

Commit d5fd0f0

Browse files
DOC-4911 added Go vector query example
1 parent 61db7c2 commit d5fd0f0

File tree

2 files changed

+319
-1
lines changed

2 files changed

+319
-1
lines changed

content/develop/clients/go/transpipe.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ categories:
1212
description: Learn how to use Redis pipelines and transactions
1313
linkTitle: Pipelines/transactions
1414
title: Pipelines and transactions
15-
weight: 2
15+
weight: 4
1616
---
1717

1818
Redis lets you send a sequence of commands to the server together in a batch.
Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
---
2+
categories:
3+
- docs
4+
- develop
5+
- stack
6+
- oss
7+
- rs
8+
- rc
9+
- oss
10+
- kubernetes
11+
- clients
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: 3
16+
---
17+
18+
[Redis Query Engine]({{< relref "/develop/interact/search-and-query" >}})
19+
lets you index vector fields in [hash]({{< relref "/develop/data-types/hashes" >}})
20+
or [JSON]({{< relref "/develop/data-types/json" >}}) objects (see the
21+
[Vectors]({{< relref "/develop/interact/search-and-query/advanced-concepts/vectors" >}})
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
25+
[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.
30+
31+
In the example below, we use the
32+
[`huggingfaceembedder`](https://pkg.go.dev/github.com/henomis/lingoose@v0.3.0/embedder/huggingface)
33+
package from the [`LinGoose`](https://pkg.go.dev/github.com/henomis/lingoose@v0.3.0)
34+
framework to generate vector embeddings to store and index with
35+
Redis Query Engine.
36+
37+
## Initialize
38+
39+
Start a new Go module with the following command:
40+
41+
```bash
42+
go mod init vecexample
43+
```
44+
45+
Then, in your module folder, install
46+
[`go-redis`]({{< relref "/develop/clients/go-redis" >}})
47+
and the
48+
[`huggingfaceembedder`](https://pkg.go.dev/github.com/henomis/lingoose@v0.3.0/embedder/huggingface)
49+
package:
50+
51+
```bash
52+
go get github.com/redis/go-redis/v9
53+
go get github.com/henomis/lingoose/embedder/huggingface
54+
```
55+
56+
Add the following imports to your module's main program file:
57+
58+
```go
59+
package main
60+
61+
import (
62+
"context"
63+
"encoding/binary"
64+
"fmt"
65+
"math"
66+
67+
huggingfaceembedder "github.com/henomis/lingoose/embedder/huggingface"
68+
"github.com/redis/go-redis/v9"
69+
)
70+
```
71+
72+
You must also create a [HuggingFace account](https://huggingface.co/join)
73+
and add a new access token to use the embedding model. See the
74+
[HuggingFace](https://huggingface.co/docs/hub/en/security-tokens)
75+
docs to learn how to create and manage access tokens. Note that the
76+
account and the `all-MiniLM-L6-v2` model that we will use to produce
77+
the embeddings for this example are both available for free.
78+
79+
## Add a helper function
80+
81+
The `huggingfaceembedder` model outputs the embeddings as a
82+
`[]float32` array. If you are storing your documents as
83+
[hash]({{< relref "/develop/data-types/hashes" >}}) objects
84+
(as we are in this example), then you must convert this array
85+
to a `byte` string before adding it as a hash field. In this example,
86+
we will use the function below to produce the `byte` string:
87+
88+
```go
89+
func floatsToBytes(fs []float32) []byte {
90+
buf := make([]byte, len(fs)*4)
91+
92+
for i, f := range fs {
93+
u := math.Float32bits(f)
94+
binary.NativeEndian.PutUint32(buf[i*4:], u)
95+
}
96+
97+
return buf
98+
}
99+
```
100+
101+
Note that if you are using [JSON]({{< relref "/develop/data-types/json" >}})
102+
objects to store your documents instead of hashes, then you should store
103+
the `[]float32` array directly without first converting it to a `byte`
104+
string.
105+
106+
## Create the index
107+
108+
In the `main()` function, connect to Redis and delete any index previously
109+
created with the name `vector_idx`:
110+
111+
```go
112+
ctx := context.Background()
113+
rdb := redis.NewClient(&redis.Options{
114+
Addr: "localhost:6379",
115+
Password: "", // no password docs
116+
DB: 0, // use default DB
117+
Protocol: 2,
118+
})
119+
120+
rdb.FTDropIndexWithArgs(ctx,
121+
"vector_idx",
122+
&redis.FTDropIndexOptions{
123+
DeleteDocs: true,
124+
},
125+
)
126+
```
127+
128+
Next, create the index.
129+
The schema in the example below specifies hash objects for storage and includes
130+
three fields: the text content to index, a
131+
[tag]({{< relref "/develop/interact/search-and-query/advanced-concepts/tags" >}})
132+
field to represent the "genre" of the text, and the embedding vector generated from
133+
the original text content. The `embedding` field specifies
134+
[HNSW]({{< relref "/develop/interact/search-and-query/advanced-concepts/vectors#hnsw-index" >}})
135+
indexing, the
136+
[L2]({{< relref "/develop/interact/search-and-query/advanced-concepts/vectors#distance-metrics" >}})
137+
vector distance metric, `Float32` values to represent the vector's components,
138+
and 384 dimensions, as required by the `all-MiniLM-L6-v2` embedding model.
139+
140+
```go
141+
_, err := rdb.FTCreate(ctx,
142+
"vector_idx",
143+
&redis.FTCreateOptions{
144+
OnHash: true,
145+
Prefix: []any{"doc:"},
146+
},
147+
&redis.FieldSchema{
148+
FieldName: "content",
149+
FieldType: redis.SearchFieldTypeText,
150+
},
151+
&redis.FieldSchema{
152+
FieldName: "genre",
153+
FieldType: redis.SearchFieldTypeTag,
154+
},
155+
&redis.FieldSchema{
156+
FieldName: "embedding",
157+
FieldType: redis.SearchFieldTypeVector,
158+
VectorArgs: &redis.FTVectorArgs{
159+
HNSWOptions: &redis.FTHNSWOptions{
160+
Dim: 384,
161+
DistanceMetric: "L2",
162+
Type: "FLOAT32",
163+
},
164+
},
165+
},
166+
).Result()
167+
168+
if err != nil {
169+
panic(err)
170+
}
171+
```
172+
173+
## Create an embedder instance
174+
175+
You need an instance of the `huggingfaceembedder` class to
176+
generate the embeddings. Use the code below to create an
177+
instance that uses the `sentence-transformers/all-MiniLM-L6-v2`
178+
model, passing your HuggingFace access token to the `WithToken()`
179+
method.
180+
181+
```go
182+
hf := huggingfaceembedder.New().
183+
WithToken("<your-access-token>").
184+
WithModel("sentence-transformers/all-MiniLM-L6-v2")
185+
```
186+
187+
## Add data
188+
189+
You can now supply the data objects, which will be indexed automatically
190+
when you add them with [`hset()`]({{< relref "/commands/hset" >}}), as long as
191+
you use the `doc:` prefix specified in the index definition.
192+
193+
Use the `Embed()` method of `huggingfacetransformer`
194+
as shown below to create the embeddings that represent the `content` fields.
195+
This method takes an array of strings and outputs a corresponding
196+
array of `Embedding` objects.
197+
Use the `ToFloat32()` method of `Embedding` to produce the array of float
198+
values that we need, and use the `floatsToBytes()` function we defined
199+
above to convert this array to a `byte` string.
200+
201+
```go
202+
sentences := []string{
203+
"That is a very happy person",
204+
"That is a happy dog",
205+
"Today is a sunny day",
206+
}
207+
208+
tags := []string{
209+
"persons", "pets", "weather",
210+
}
211+
212+
embeddings, err := hf.Embed(ctx, sentences)
213+
214+
if err != nil {
215+
panic(err)
216+
}
217+
218+
for i, emb := range embeddings {
219+
buffer := floatsToBytes(emb.ToFloat32())
220+
221+
if err != nil {
222+
panic(err)
223+
}
224+
225+
_, err = rdb.HSet(ctx,
226+
fmt.Sprintf("doc:%v", i),
227+
map[string]any{
228+
"content": sentences[i],
229+
"genre": tags[i],
230+
"embedding": buffer,
231+
},
232+
).Result()
233+
234+
if err != nil {
235+
panic(err)
236+
}
237+
}
238+
```
239+
240+
## Run a query
241+
242+
After you have created the index and added the data, you are ready to run a query.
243+
To do this, you must create another embedding vector from your chosen query
244+
text. Redis calculates the similarity between the query vector and each
245+
embedding vector in the index as it runs the query. It then ranks the
246+
results in order of this numeric similarity value.
247+
248+
The code below creates the query embedding using `Embed()`, as with
249+
the indexing, and passes it as a parameter when the query executes
250+
(see
251+
[Vector search]({{< relref "/develop/interact/search-and-query/query/vector-search" >}})
252+
for more information about using query parameters with embeddings).
253+
254+
```go
255+
queryEmbedding, err := hf.Embed(ctx, []string{
256+
"That is a happy person",
257+
})
258+
259+
if err != nil {
260+
panic(err)
261+
}
262+
263+
buffer := floatsToBytes(queryEmbedding[0].ToFloat32())
264+
265+
if err != nil {
266+
panic(err)
267+
}
268+
269+
results, err := rdb.FTSearchWithArgs(ctx,
270+
"vector_idx",
271+
"*=>[KNN 3 @embedding $vec AS vector_distance]",
272+
&redis.FTSearchOptions{
273+
Return: []redis.FTSearchReturn{
274+
{FieldName: "vector_distance"},
275+
{FieldName: "content"},
276+
},
277+
DialectVersion: 2,
278+
Params: map[string]any{
279+
"vec": buffer,
280+
},
281+
},
282+
).Result()
283+
284+
if err != nil {
285+
panic(err)
286+
}
287+
288+
for _, doc := range results.Docs {
289+
fmt.Printf(
290+
"ID: %v, Distance:%v, Content:'%v'\n",
291+
doc.ID, doc.Fields["vector_distance"], doc.Fields["content"],
292+
)
293+
}
294+
```
295+
296+
The code is now ready to run, but note that it may take a while to complete when
297+
you run it for the first time (which happens because RedisVL must download the
298+
`all-MiniLM-L6-v2` model data before it can
299+
generate the embeddings). When you run the code, it outputs the following text:
300+
301+
```
302+
ID: doc:0, Distance:0.114169843495, Content:'That is a very happy person'
303+
ID: doc:1, Distance:0.610845327377, Content:'That is a happy dog'
304+
ID: doc:2, Distance:1.48624765873, Content:'Today is a sunny day'
305+
```
306+
307+
The results are ordered according to the value of the `vector_distance`
308+
field, with the lowest distance indicating the greatest similarity to the query.
309+
As you would expect, the result for `doc:0` with the content text *"That is a very happy person"*
310+
is the result that is most similar in meaning to the query text
311+
*"That is a happy person"*.
312+
313+
## Learn more
314+
315+
See
316+
[Vector search]({{< relref "/develop/interact/search-and-query/query/vector-search" >}})
317+
for more information about the indexing options, distance metrics, and query format
318+
for vectors.

0 commit comments

Comments
 (0)