Learn how to index and query vector embeddings with Redis
Redis Query Engine
lets you index vector fields in hash
or JSON objects (see the
Vectors
reference page for more information).
Among other things, vector fields can store text embeddings, which are AI-generated vector
representations of the semantic information in pieces of text. The
vector distance
between two embeddings indicates how similar they are semantically. By comparing the
similarity of an embedding generated from some query text with embeddings stored in hash
or JSON fields, Redis can retrieve documents that closely match the query in terms
of their meaning.
In the example below, we use the
Hugot
library to generate vector embeddings to store and index with
Redis Query Engine. The code is first demonstrated for hash documents with a
separate section to explain the
differences with JSON documents.
Note:
From v9.8.0 onwards,
go-redis uses query dialect 2 by default.
Redis query engine methods such as FTSearch()
will explicitly request this dialect, overriding the default set for the server.
See
Query dialects
for more information.
Initialize
First, install go-redis
if you haven't already done so. Then, install
Hugot
using the following command:
go get github.com/knights-analytics/hugot
Add the following imports to your module's main program file:
packagemainimport("context""encoding/binary""fmt""math""github.com/knights-analytics/hugot""github.com/redis/go-redis/v9")funcfloatsToBytes(fs[]float32)[]byte{buf:=make([]byte,len(fs)*4)fori,f:=rangefs{u:=math.Float32bits(f)binary.NativeEndian.PutUint32(buf[i*4:],u)}returnbuf}funcmain(){ctx:=context.Background()rdb:=redis.NewClient(&redis.Options{Addr:"localhost:6379",Password:"",// no password docsDB:0,// use default DBProtocol:2,})rdb.FTDropIndexWithArgs(ctx,"vector_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err:=rdb.FTCreate(ctx,"vector_idx",&redis.FTCreateOptions{OnHash:true,Prefix:[]any{"doc:"},},&redis.FieldSchema{FieldName:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}// Create a Hugot sessionsession,err:=hugot.NewGoSession()iferr!=nil{panic(err)}deferfunc(){err:=session.Destroy()iferr!=nil{panic(err)}}()// Download the modeldownloadOptions:=hugot.NewDownloadOptions()downloadOptions.OnnxFilePath="onnx/model.onnx"// Specify which ONNX file to usemodelPath,err:=hugot.DownloadModel("sentence-transformers/all-MiniLM-L6-v2","./models/",downloadOptions)iferr!=nil{panic(err)}// Create feature extraction pipeline configurationconfig:=hugot.FeatureExtractionConfig{ModelPath:modelPath,Name:"embeddingPipeline",}// Create the feature extraction pipelineembeddingPipeline,err:=hugot.NewPipeline(session,config)iferr!=nil{panic(err)}sentences:=[]string{"That is a very happy person","That is a happy dog","Today is a sunny day",}tags:=[]string{"persons","pets","weather",}// Generate embeddings using HugotembeddingResult,err:=embeddingPipeline.RunPipeline(sentences)iferr!=nil{panic(err)}// Extract the embeddings from the resultembeddings:=embeddingResult.Embeddingsfori,emb:=rangeembeddings{buffer:=floatsToBytes(emb)_,err=rdb.HSet(ctx,fmt.Sprintf("doc:%v",i),map[string]any{"content":sentences[i],"genre":tags[i],"embedding":buffer,},).Result()iferr!=nil{panic(err)}}// Generate query embedding using HugotqueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}buffer:=floatsToBytes(queryResult.Embeddings[0])results,err:=rdb.FTSearchWithArgs(ctx,"vector_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":buffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangeresults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}rdb.FTDropIndexWithArgs(ctx,"vector_json_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err=rdb.FTCreate(ctx,"vector_json_idx",&redis.FTCreateOptions{OnJSON:true,Prefix:[]any{"jdoc:"},},&redis.FieldSchema{FieldName:"$.content",As:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"$.genre",As:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"$.embedding",As:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}fori,emb:=rangeembeddings{_,err=rdb.JSONSet(ctx,fmt.Sprintf("jdoc:%v",i),"$",map[string]any{"content":sentences[i],"genre":tags[i],"embedding":emb,},).Result()iferr!=nil{panic(err)}}// Generate query embedding for JSON search using HugotjsonQueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}jsonBuffer:=floatsToBytes(jsonQueryResult.Embeddings[0])jsonResults,err:=rdb.FTSearchWithArgs(ctx,"vector_json_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":jsonBuffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangejsonResults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}}
Add a helper function
The Hugot model outputs the embeddings as a
[]float32 array. If you are storing your documents as
hash objects, then you
must convert this array to a byte string before adding it as a hash field.
The function shown below uses Go's binary
package to produce the byte string:
packagemainimport("context""encoding/binary""fmt""math""github.com/knights-analytics/hugot""github.com/redis/go-redis/v9")funcfloatsToBytes(fs[]float32)[]byte{buf:=make([]byte,len(fs)*4)fori,f:=rangefs{u:=math.Float32bits(f)binary.NativeEndian.PutUint32(buf[i*4:],u)}returnbuf}funcmain(){ctx:=context.Background()rdb:=redis.NewClient(&redis.Options{Addr:"localhost:6379",Password:"",// no password docsDB:0,// use default DBProtocol:2,})rdb.FTDropIndexWithArgs(ctx,"vector_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err:=rdb.FTCreate(ctx,"vector_idx",&redis.FTCreateOptions{OnHash:true,Prefix:[]any{"doc:"},},&redis.FieldSchema{FieldName:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}// Create a Hugot sessionsession,err:=hugot.NewGoSession()iferr!=nil{panic(err)}deferfunc(){err:=session.Destroy()iferr!=nil{panic(err)}}()// Download the modeldownloadOptions:=hugot.NewDownloadOptions()downloadOptions.OnnxFilePath="onnx/model.onnx"// Specify which ONNX file to usemodelPath,err:=hugot.DownloadModel("sentence-transformers/all-MiniLM-L6-v2","./models/",downloadOptions)iferr!=nil{panic(err)}// Create feature extraction pipeline configurationconfig:=hugot.FeatureExtractionConfig{ModelPath:modelPath,Name:"embeddingPipeline",}// Create the feature extraction pipelineembeddingPipeline,err:=hugot.NewPipeline(session,config)iferr!=nil{panic(err)}sentences:=[]string{"That is a very happy person","That is a happy dog","Today is a sunny day",}tags:=[]string{"persons","pets","weather",}// Generate embeddings using HugotembeddingResult,err:=embeddingPipeline.RunPipeline(sentences)iferr!=nil{panic(err)}// Extract the embeddings from the resultembeddings:=embeddingResult.Embeddingsfori,emb:=rangeembeddings{buffer:=floatsToBytes(emb)_,err=rdb.HSet(ctx,fmt.Sprintf("doc:%v",i),map[string]any{"content":sentences[i],"genre":tags[i],"embedding":buffer,},).Result()iferr!=nil{panic(err)}}// Generate query embedding using HugotqueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}buffer:=floatsToBytes(queryResult.Embeddings[0])results,err:=rdb.FTSearchWithArgs(ctx,"vector_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":buffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangeresults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}rdb.FTDropIndexWithArgs(ctx,"vector_json_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err=rdb.FTCreate(ctx,"vector_json_idx",&redis.FTCreateOptions{OnJSON:true,Prefix:[]any{"jdoc:"},},&redis.FieldSchema{FieldName:"$.content",As:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"$.genre",As:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"$.embedding",As:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}fori,emb:=rangeembeddings{_,err=rdb.JSONSet(ctx,fmt.Sprintf("jdoc:%v",i),"$",map[string]any{"content":sentences[i],"genre":tags[i],"embedding":emb,},).Result()iferr!=nil{panic(err)}}// Generate query embedding for JSON search using HugotjsonQueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}jsonBuffer:=floatsToBytes(jsonQueryResult.Embeddings[0])jsonResults,err:=rdb.FTSearchWithArgs(ctx,"vector_json_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":jsonBuffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangejsonResults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}}
Note that if you are using JSON
objects to store your documents instead of hashes, then you should store
the []float32 array directly without first converting it to a byte
string (see Differences with JSON documents
below).
Create the index
In the main() function, connect to Redis and delete any index previously
created with the name vector_idx:
packagemainimport("context""encoding/binary""fmt""math""github.com/knights-analytics/hugot""github.com/redis/go-redis/v9")funcfloatsToBytes(fs[]float32)[]byte{buf:=make([]byte,len(fs)*4)fori,f:=rangefs{u:=math.Float32bits(f)binary.NativeEndian.PutUint32(buf[i*4:],u)}returnbuf}funcmain(){ctx:=context.Background()rdb:=redis.NewClient(&redis.Options{Addr:"localhost:6379",Password:"",// no password docsDB:0,// use default DBProtocol:2,})rdb.FTDropIndexWithArgs(ctx,"vector_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err:=rdb.FTCreate(ctx,"vector_idx",&redis.FTCreateOptions{OnHash:true,Prefix:[]any{"doc:"},},&redis.FieldSchema{FieldName:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}// Create a Hugot sessionsession,err:=hugot.NewGoSession()iferr!=nil{panic(err)}deferfunc(){err:=session.Destroy()iferr!=nil{panic(err)}}()// Download the modeldownloadOptions:=hugot.NewDownloadOptions()downloadOptions.OnnxFilePath="onnx/model.onnx"// Specify which ONNX file to usemodelPath,err:=hugot.DownloadModel("sentence-transformers/all-MiniLM-L6-v2","./models/",downloadOptions)iferr!=nil{panic(err)}// Create feature extraction pipeline configurationconfig:=hugot.FeatureExtractionConfig{ModelPath:modelPath,Name:"embeddingPipeline",}// Create the feature extraction pipelineembeddingPipeline,err:=hugot.NewPipeline(session,config)iferr!=nil{panic(err)}sentences:=[]string{"That is a very happy person","That is a happy dog","Today is a sunny day",}tags:=[]string{"persons","pets","weather",}// Generate embeddings using HugotembeddingResult,err:=embeddingPipeline.RunPipeline(sentences)iferr!=nil{panic(err)}// Extract the embeddings from the resultembeddings:=embeddingResult.Embeddingsfori,emb:=rangeembeddings{buffer:=floatsToBytes(emb)_,err=rdb.HSet(ctx,fmt.Sprintf("doc:%v",i),map[string]any{"content":sentences[i],"genre":tags[i],"embedding":buffer,},).Result()iferr!=nil{panic(err)}}// Generate query embedding using HugotqueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}buffer:=floatsToBytes(queryResult.Embeddings[0])results,err:=rdb.FTSearchWithArgs(ctx,"vector_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":buffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangeresults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}rdb.FTDropIndexWithArgs(ctx,"vector_json_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err=rdb.FTCreate(ctx,"vector_json_idx",&redis.FTCreateOptions{OnJSON:true,Prefix:[]any{"jdoc:"},},&redis.FieldSchema{FieldName:"$.content",As:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"$.genre",As:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"$.embedding",As:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}fori,emb:=rangeembeddings{_,err=rdb.JSONSet(ctx,fmt.Sprintf("jdoc:%v",i),"$",map[string]any{"content":sentences[i],"genre":tags[i],"embedding":emb,},).Result()iferr!=nil{panic(err)}}// Generate query embedding for JSON search using HugotjsonQueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}jsonBuffer:=floatsToBytes(jsonQueryResult.Embeddings[0])jsonResults,err:=rdb.FTSearchWithArgs(ctx,"vector_json_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":jsonBuffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangejsonResults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}}
Next, create the index.
The schema in the example below specifies hash objects for storage and includes
three fields: the text content to index, a
tag
field to represent the "genre" of the text, and the embedding vector generated from
the original text content. The embedding field specifies
HNSW
indexing, the
L2
vector distance metric, Float32 values to represent the vector's components,
and 384 dimensions, as required by the all-MiniLM-L6-v2 embedding model.
packagemainimport("context""encoding/binary""fmt""math""github.com/knights-analytics/hugot""github.com/redis/go-redis/v9")funcfloatsToBytes(fs[]float32)[]byte{buf:=make([]byte,len(fs)*4)fori,f:=rangefs{u:=math.Float32bits(f)binary.NativeEndian.PutUint32(buf[i*4:],u)}returnbuf}funcmain(){ctx:=context.Background()rdb:=redis.NewClient(&redis.Options{Addr:"localhost:6379",Password:"",// no password docsDB:0,// use default DBProtocol:2,})rdb.FTDropIndexWithArgs(ctx,"vector_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err:=rdb.FTCreate(ctx,"vector_idx",&redis.FTCreateOptions{OnHash:true,Prefix:[]any{"doc:"},},&redis.FieldSchema{FieldName:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}// Create a Hugot sessionsession,err:=hugot.NewGoSession()iferr!=nil{panic(err)}deferfunc(){err:=session.Destroy()iferr!=nil{panic(err)}}()// Download the modeldownloadOptions:=hugot.NewDownloadOptions()downloadOptions.OnnxFilePath="onnx/model.onnx"// Specify which ONNX file to usemodelPath,err:=hugot.DownloadModel("sentence-transformers/all-MiniLM-L6-v2","./models/",downloadOptions)iferr!=nil{panic(err)}// Create feature extraction pipeline configurationconfig:=hugot.FeatureExtractionConfig{ModelPath:modelPath,Name:"embeddingPipeline",}// Create the feature extraction pipelineembeddingPipeline,err:=hugot.NewPipeline(session,config)iferr!=nil{panic(err)}sentences:=[]string{"That is a very happy person","That is a happy dog","Today is a sunny day",}tags:=[]string{"persons","pets","weather",}// Generate embeddings using HugotembeddingResult,err:=embeddingPipeline.RunPipeline(sentences)iferr!=nil{panic(err)}// Extract the embeddings from the resultembeddings:=embeddingResult.Embeddingsfori,emb:=rangeembeddings{buffer:=floatsToBytes(emb)_,err=rdb.HSet(ctx,fmt.Sprintf("doc:%v",i),map[string]any{"content":sentences[i],"genre":tags[i],"embedding":buffer,},).Result()iferr!=nil{panic(err)}}// Generate query embedding using HugotqueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}buffer:=floatsToBytes(queryResult.Embeddings[0])results,err:=rdb.FTSearchWithArgs(ctx,"vector_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":buffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangeresults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}rdb.FTDropIndexWithArgs(ctx,"vector_json_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err=rdb.FTCreate(ctx,"vector_json_idx",&redis.FTCreateOptions{OnJSON:true,Prefix:[]any{"jdoc:"},},&redis.FieldSchema{FieldName:"$.content",As:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"$.genre",As:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"$.embedding",As:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}fori,emb:=rangeembeddings{_,err=rdb.JSONSet(ctx,fmt.Sprintf("jdoc:%v",i),"$",map[string]any{"content":sentences[i],"genre":tags[i],"embedding":emb,},).Result()iferr!=nil{panic(err)}}// Generate query embedding for JSON search using HugotjsonQueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}jsonBuffer:=floatsToBytes(jsonQueryResult.Embeddings[0])jsonResults,err:=rdb.FTSearchWithArgs(ctx,"vector_json_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":jsonBuffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangejsonResults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}}
Create an embedder instance
You need an instance of the FeatureExtractionPipeline class to
generate the embeddings. Use the code below to create an
instance that uses the sentence-transformers/all-MiniLM-L6-v2
model:
packagemainimport("context""encoding/binary""fmt""math""github.com/knights-analytics/hugot""github.com/redis/go-redis/v9")funcfloatsToBytes(fs[]float32)[]byte{buf:=make([]byte,len(fs)*4)fori,f:=rangefs{u:=math.Float32bits(f)binary.NativeEndian.PutUint32(buf[i*4:],u)}returnbuf}funcmain(){ctx:=context.Background()rdb:=redis.NewClient(&redis.Options{Addr:"localhost:6379",Password:"",// no password docsDB:0,// use default DBProtocol:2,})rdb.FTDropIndexWithArgs(ctx,"vector_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err:=rdb.FTCreate(ctx,"vector_idx",&redis.FTCreateOptions{OnHash:true,Prefix:[]any{"doc:"},},&redis.FieldSchema{FieldName:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}// Create a Hugot sessionsession,err:=hugot.NewGoSession()iferr!=nil{panic(err)}deferfunc(){err:=session.Destroy()iferr!=nil{panic(err)}}()// Download the modeldownloadOptions:=hugot.NewDownloadOptions()downloadOptions.OnnxFilePath="onnx/model.onnx"// Specify which ONNX file to usemodelPath,err:=hugot.DownloadModel("sentence-transformers/all-MiniLM-L6-v2","./models/",downloadOptions)iferr!=nil{panic(err)}// Create feature extraction pipeline configurationconfig:=hugot.FeatureExtractionConfig{ModelPath:modelPath,Name:"embeddingPipeline",}// Create the feature extraction pipelineembeddingPipeline,err:=hugot.NewPipeline(session,config)iferr!=nil{panic(err)}sentences:=[]string{"That is a very happy person","That is a happy dog","Today is a sunny day",}tags:=[]string{"persons","pets","weather",}// Generate embeddings using HugotembeddingResult,err:=embeddingPipeline.RunPipeline(sentences)iferr!=nil{panic(err)}// Extract the embeddings from the resultembeddings:=embeddingResult.Embeddingsfori,emb:=rangeembeddings{buffer:=floatsToBytes(emb)_,err=rdb.HSet(ctx,fmt.Sprintf("doc:%v",i),map[string]any{"content":sentences[i],"genre":tags[i],"embedding":buffer,},).Result()iferr!=nil{panic(err)}}// Generate query embedding using HugotqueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}buffer:=floatsToBytes(queryResult.Embeddings[0])results,err:=rdb.FTSearchWithArgs(ctx,"vector_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":buffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangeresults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}rdb.FTDropIndexWithArgs(ctx,"vector_json_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err=rdb.FTCreate(ctx,"vector_json_idx",&redis.FTCreateOptions{OnJSON:true,Prefix:[]any{"jdoc:"},},&redis.FieldSchema{FieldName:"$.content",As:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"$.genre",As:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"$.embedding",As:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}fori,emb:=rangeembeddings{_,err=rdb.JSONSet(ctx,fmt.Sprintf("jdoc:%v",i),"$",map[string]any{"content":sentences[i],"genre":tags[i],"embedding":emb,},).Result()iferr!=nil{panic(err)}}// Generate query embedding for JSON search using HugotjsonQueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}jsonBuffer:=floatsToBytes(jsonQueryResult.Embeddings[0])jsonResults,err:=rdb.FTSearchWithArgs(ctx,"vector_json_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":jsonBuffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangejsonResults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}}
Add data
You can now supply the data objects, which will be indexed automatically
when you add them with HSet(), as long as
you use the doc: prefix specified in the index definition.
Use the RunPipeline() method of FeatureExtractionPipeline
as shown below to create the embeddings that represent the content fields.
This method takes an array of strings and outputs a corresponding
array of FeatureExtractionOutput objects.
The Embeddings field of FeatureExtractionOutput contains the array of float
values that you need for the index. Use the floatsToBytes() function defined
above to convert this array to a byte string.
packagemainimport("context""encoding/binary""fmt""math""github.com/knights-analytics/hugot""github.com/redis/go-redis/v9")funcfloatsToBytes(fs[]float32)[]byte{buf:=make([]byte,len(fs)*4)fori,f:=rangefs{u:=math.Float32bits(f)binary.NativeEndian.PutUint32(buf[i*4:],u)}returnbuf}funcmain(){ctx:=context.Background()rdb:=redis.NewClient(&redis.Options{Addr:"localhost:6379",Password:"",// no password docsDB:0,// use default DBProtocol:2,})rdb.FTDropIndexWithArgs(ctx,"vector_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err:=rdb.FTCreate(ctx,"vector_idx",&redis.FTCreateOptions{OnHash:true,Prefix:[]any{"doc:"},},&redis.FieldSchema{FieldName:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}// Create a Hugot sessionsession,err:=hugot.NewGoSession()iferr!=nil{panic(err)}deferfunc(){err:=session.Destroy()iferr!=nil{panic(err)}}()// Download the modeldownloadOptions:=hugot.NewDownloadOptions()downloadOptions.OnnxFilePath="onnx/model.onnx"// Specify which ONNX file to usemodelPath,err:=hugot.DownloadModel("sentence-transformers/all-MiniLM-L6-v2","./models/",downloadOptions)iferr!=nil{panic(err)}// Create feature extraction pipeline configurationconfig:=hugot.FeatureExtractionConfig{ModelPath:modelPath,Name:"embeddingPipeline",}// Create the feature extraction pipelineembeddingPipeline,err:=hugot.NewPipeline(session,config)iferr!=nil{panic(err)}sentences:=[]string{"That is a very happy person","That is a happy dog","Today is a sunny day",}tags:=[]string{"persons","pets","weather",}// Generate embeddings using HugotembeddingResult,err:=embeddingPipeline.RunPipeline(sentences)iferr!=nil{panic(err)}// Extract the embeddings from the resultembeddings:=embeddingResult.Embeddingsfori,emb:=rangeembeddings{buffer:=floatsToBytes(emb)_,err=rdb.HSet(ctx,fmt.Sprintf("doc:%v",i),map[string]any{"content":sentences[i],"genre":tags[i],"embedding":buffer,},).Result()iferr!=nil{panic(err)}}// Generate query embedding using HugotqueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}buffer:=floatsToBytes(queryResult.Embeddings[0])results,err:=rdb.FTSearchWithArgs(ctx,"vector_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":buffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangeresults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}rdb.FTDropIndexWithArgs(ctx,"vector_json_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err=rdb.FTCreate(ctx,"vector_json_idx",&redis.FTCreateOptions{OnJSON:true,Prefix:[]any{"jdoc:"},},&redis.FieldSchema{FieldName:"$.content",As:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"$.genre",As:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"$.embedding",As:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}fori,emb:=rangeembeddings{_,err=rdb.JSONSet(ctx,fmt.Sprintf("jdoc:%v",i),"$",map[string]any{"content":sentences[i],"genre":tags[i],"embedding":emb,},).Result()iferr!=nil{panic(err)}}// Generate query embedding for JSON search using HugotjsonQueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}jsonBuffer:=floatsToBytes(jsonQueryResult.Embeddings[0])jsonResults,err:=rdb.FTSearchWithArgs(ctx,"vector_json_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":jsonBuffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangejsonResults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}}
Run a query
After you have created the index and added the data, you are ready to run a query.
To do this, you must create another embedding vector from your chosen query
text. Redis calculates the similarity between the query vector and each
embedding vector in the index as it runs the query. It then ranks the
results in order of this numeric similarity value.
The code below creates the query embedding using RunPipeline(), as with
the indexing, and passes it as a parameter when the query executes
(see
Vector search
for more information about using query parameters with embeddings).
packagemainimport("context""encoding/binary""fmt""math""github.com/knights-analytics/hugot""github.com/redis/go-redis/v9")funcfloatsToBytes(fs[]float32)[]byte{buf:=make([]byte,len(fs)*4)fori,f:=rangefs{u:=math.Float32bits(f)binary.NativeEndian.PutUint32(buf[i*4:],u)}returnbuf}funcmain(){ctx:=context.Background()rdb:=redis.NewClient(&redis.Options{Addr:"localhost:6379",Password:"",// no password docsDB:0,// use default DBProtocol:2,})rdb.FTDropIndexWithArgs(ctx,"vector_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err:=rdb.FTCreate(ctx,"vector_idx",&redis.FTCreateOptions{OnHash:true,Prefix:[]any{"doc:"},},&redis.FieldSchema{FieldName:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}// Create a Hugot sessionsession,err:=hugot.NewGoSession()iferr!=nil{panic(err)}deferfunc(){err:=session.Destroy()iferr!=nil{panic(err)}}()// Download the modeldownloadOptions:=hugot.NewDownloadOptions()downloadOptions.OnnxFilePath="onnx/model.onnx"// Specify which ONNX file to usemodelPath,err:=hugot.DownloadModel("sentence-transformers/all-MiniLM-L6-v2","./models/",downloadOptions)iferr!=nil{panic(err)}// Create feature extraction pipeline configurationconfig:=hugot.FeatureExtractionConfig{ModelPath:modelPath,Name:"embeddingPipeline",}// Create the feature extraction pipelineembeddingPipeline,err:=hugot.NewPipeline(session,config)iferr!=nil{panic(err)}sentences:=[]string{"That is a very happy person","That is a happy dog","Today is a sunny day",}tags:=[]string{"persons","pets","weather",}// Generate embeddings using HugotembeddingResult,err:=embeddingPipeline.RunPipeline(sentences)iferr!=nil{panic(err)}// Extract the embeddings from the resultembeddings:=embeddingResult.Embeddingsfori,emb:=rangeembeddings{buffer:=floatsToBytes(emb)_,err=rdb.HSet(ctx,fmt.Sprintf("doc:%v",i),map[string]any{"content":sentences[i],"genre":tags[i],"embedding":buffer,},).Result()iferr!=nil{panic(err)}}// Generate query embedding using HugotqueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}buffer:=floatsToBytes(queryResult.Embeddings[0])results,err:=rdb.FTSearchWithArgs(ctx,"vector_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":buffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangeresults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}rdb.FTDropIndexWithArgs(ctx,"vector_json_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err=rdb.FTCreate(ctx,"vector_json_idx",&redis.FTCreateOptions{OnJSON:true,Prefix:[]any{"jdoc:"},},&redis.FieldSchema{FieldName:"$.content",As:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"$.genre",As:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"$.embedding",As:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}fori,emb:=rangeembeddings{_,err=rdb.JSONSet(ctx,fmt.Sprintf("jdoc:%v",i),"$",map[string]any{"content":sentences[i],"genre":tags[i],"embedding":emb,},).Result()iferr!=nil{panic(err)}}// Generate query embedding for JSON search using HugotjsonQueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}jsonBuffer:=floatsToBytes(jsonQueryResult.Embeddings[0])jsonResults,err:=rdb.FTSearchWithArgs(ctx,"vector_json_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":jsonBuffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangejsonResults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}}
The code is now ready to run, but note that it may take a while to complete when
you run it for the first time (which happens because Hugot
must download the all-MiniLM-L6-v2 model data before it can
generate the embeddings). When you run the code, it outputs the following text:
ID: doc:0, Distance:2.96992516518, Content:'That is a very happy person'
ID: doc:1, Distance:17.3678302765, Content:'That is a happy dog'
ID: doc:2, Distance:43.7771987915, Content:'Today is a sunny day'
The results are ordered according to the value of the vector_distance
field, with the lowest distance indicating the greatest similarity to the query.
As you would expect, the result for doc:0 with the content text "That is a very happy person"
is the result that is most similar in meaning to the query text
"That is a happy person".
Differences with JSON documents
Indexing JSON documents is similar to hash indexing, but there are some
important differences. JSON allows much richer data modelling with nested fields, so
you must supply a path in the schema
to identify each field you want to index. However, you can declare a short alias for each
of these paths (using the As option) to avoid typing it in full for
every query. Also, you must set OnJSON to true when you create the index.
The code below shows these differences, but the index is otherwise very similar to
the one created previously for hashes:
packagemainimport("context""encoding/binary""fmt""math""github.com/knights-analytics/hugot""github.com/redis/go-redis/v9")funcfloatsToBytes(fs[]float32)[]byte{buf:=make([]byte,len(fs)*4)fori,f:=rangefs{u:=math.Float32bits(f)binary.NativeEndian.PutUint32(buf[i*4:],u)}returnbuf}funcmain(){ctx:=context.Background()rdb:=redis.NewClient(&redis.Options{Addr:"localhost:6379",Password:"",// no password docsDB:0,// use default DBProtocol:2,})rdb.FTDropIndexWithArgs(ctx,"vector_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err:=rdb.FTCreate(ctx,"vector_idx",&redis.FTCreateOptions{OnHash:true,Prefix:[]any{"doc:"},},&redis.FieldSchema{FieldName:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}// Create a Hugot sessionsession,err:=hugot.NewGoSession()iferr!=nil{panic(err)}deferfunc(){err:=session.Destroy()iferr!=nil{panic(err)}}()// Download the modeldownloadOptions:=hugot.NewDownloadOptions()downloadOptions.OnnxFilePath="onnx/model.onnx"// Specify which ONNX file to usemodelPath,err:=hugot.DownloadModel("sentence-transformers/all-MiniLM-L6-v2","./models/",downloadOptions)iferr!=nil{panic(err)}// Create feature extraction pipeline configurationconfig:=hugot.FeatureExtractionConfig{ModelPath:modelPath,Name:"embeddingPipeline",}// Create the feature extraction pipelineembeddingPipeline,err:=hugot.NewPipeline(session,config)iferr!=nil{panic(err)}sentences:=[]string{"That is a very happy person","That is a happy dog","Today is a sunny day",}tags:=[]string{"persons","pets","weather",}// Generate embeddings using HugotembeddingResult,err:=embeddingPipeline.RunPipeline(sentences)iferr!=nil{panic(err)}// Extract the embeddings from the resultembeddings:=embeddingResult.Embeddingsfori,emb:=rangeembeddings{buffer:=floatsToBytes(emb)_,err=rdb.HSet(ctx,fmt.Sprintf("doc:%v",i),map[string]any{"content":sentences[i],"genre":tags[i],"embedding":buffer,},).Result()iferr!=nil{panic(err)}}// Generate query embedding using HugotqueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}buffer:=floatsToBytes(queryResult.Embeddings[0])results,err:=rdb.FTSearchWithArgs(ctx,"vector_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":buffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangeresults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}rdb.FTDropIndexWithArgs(ctx,"vector_json_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err=rdb.FTCreate(ctx,"vector_json_idx",&redis.FTCreateOptions{OnJSON:true,Prefix:[]any{"jdoc:"},},&redis.FieldSchema{FieldName:"$.content",As:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"$.genre",As:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"$.embedding",As:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}fori,emb:=rangeembeddings{_,err=rdb.JSONSet(ctx,fmt.Sprintf("jdoc:%v",i),"$",map[string]any{"content":sentences[i],"genre":tags[i],"embedding":emb,},).Result()iferr!=nil{panic(err)}}// Generate query embedding for JSON search using HugotjsonQueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}jsonBuffer:=floatsToBytes(jsonQueryResult.Embeddings[0])jsonResults,err:=rdb.FTSearchWithArgs(ctx,"vector_json_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":jsonBuffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangejsonResults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}}
Use JSONSet() to add the data
instead of HSet(). The maps
that specify the fields have the same structure as the ones used for HSet().
An important difference with JSON indexing is that the vectors are
specified using lists instead of binary strings. The loop below is similar
to the one used previously to add the hash data, but it doesn't use the
floatsToBytes() function to encode the float32 array.
packagemainimport("context""encoding/binary""fmt""math""github.com/knights-analytics/hugot""github.com/redis/go-redis/v9")funcfloatsToBytes(fs[]float32)[]byte{buf:=make([]byte,len(fs)*4)fori,f:=rangefs{u:=math.Float32bits(f)binary.NativeEndian.PutUint32(buf[i*4:],u)}returnbuf}funcmain(){ctx:=context.Background()rdb:=redis.NewClient(&redis.Options{Addr:"localhost:6379",Password:"",// no password docsDB:0,// use default DBProtocol:2,})rdb.FTDropIndexWithArgs(ctx,"vector_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err:=rdb.FTCreate(ctx,"vector_idx",&redis.FTCreateOptions{OnHash:true,Prefix:[]any{"doc:"},},&redis.FieldSchema{FieldName:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}// Create a Hugot sessionsession,err:=hugot.NewGoSession()iferr!=nil{panic(err)}deferfunc(){err:=session.Destroy()iferr!=nil{panic(err)}}()// Download the modeldownloadOptions:=hugot.NewDownloadOptions()downloadOptions.OnnxFilePath="onnx/model.onnx"// Specify which ONNX file to usemodelPath,err:=hugot.DownloadModel("sentence-transformers/all-MiniLM-L6-v2","./models/",downloadOptions)iferr!=nil{panic(err)}// Create feature extraction pipeline configurationconfig:=hugot.FeatureExtractionConfig{ModelPath:modelPath,Name:"embeddingPipeline",}// Create the feature extraction pipelineembeddingPipeline,err:=hugot.NewPipeline(session,config)iferr!=nil{panic(err)}sentences:=[]string{"That is a very happy person","That is a happy dog","Today is a sunny day",}tags:=[]string{"persons","pets","weather",}// Generate embeddings using HugotembeddingResult,err:=embeddingPipeline.RunPipeline(sentences)iferr!=nil{panic(err)}// Extract the embeddings from the resultembeddings:=embeddingResult.Embeddingsfori,emb:=rangeembeddings{buffer:=floatsToBytes(emb)_,err=rdb.HSet(ctx,fmt.Sprintf("doc:%v",i),map[string]any{"content":sentences[i],"genre":tags[i],"embedding":buffer,},).Result()iferr!=nil{panic(err)}}// Generate query embedding using HugotqueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}buffer:=floatsToBytes(queryResult.Embeddings[0])results,err:=rdb.FTSearchWithArgs(ctx,"vector_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":buffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangeresults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}rdb.FTDropIndexWithArgs(ctx,"vector_json_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err=rdb.FTCreate(ctx,"vector_json_idx",&redis.FTCreateOptions{OnJSON:true,Prefix:[]any{"jdoc:"},},&redis.FieldSchema{FieldName:"$.content",As:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"$.genre",As:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"$.embedding",As:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}fori,emb:=rangeembeddings{_,err=rdb.JSONSet(ctx,fmt.Sprintf("jdoc:%v",i),"$",map[string]any{"content":sentences[i],"genre":tags[i],"embedding":emb,},).Result()iferr!=nil{panic(err)}}// Generate query embedding for JSON search using HugotjsonQueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}jsonBuffer:=floatsToBytes(jsonQueryResult.Embeddings[0])jsonResults,err:=rdb.FTSearchWithArgs(ctx,"vector_json_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":jsonBuffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangejsonResults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}}
The query is almost identical to the one for the hash documents. This
demonstrates how the right choice of aliases for the JSON paths can
save you having to write complex queries. An important thing to notice
is that the vector parameter for the query is still specified as a
binary string (using the floatsToBytes() method), even though the data for
the embedding field of the JSON was specified as an array.
packagemainimport("context""encoding/binary""fmt""math""github.com/knights-analytics/hugot""github.com/redis/go-redis/v9")funcfloatsToBytes(fs[]float32)[]byte{buf:=make([]byte,len(fs)*4)fori,f:=rangefs{u:=math.Float32bits(f)binary.NativeEndian.PutUint32(buf[i*4:],u)}returnbuf}funcmain(){ctx:=context.Background()rdb:=redis.NewClient(&redis.Options{Addr:"localhost:6379",Password:"",// no password docsDB:0,// use default DBProtocol:2,})rdb.FTDropIndexWithArgs(ctx,"vector_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err:=rdb.FTCreate(ctx,"vector_idx",&redis.FTCreateOptions{OnHash:true,Prefix:[]any{"doc:"},},&redis.FieldSchema{FieldName:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}// Create a Hugot sessionsession,err:=hugot.NewGoSession()iferr!=nil{panic(err)}deferfunc(){err:=session.Destroy()iferr!=nil{panic(err)}}()// Download the modeldownloadOptions:=hugot.NewDownloadOptions()downloadOptions.OnnxFilePath="onnx/model.onnx"// Specify which ONNX file to usemodelPath,err:=hugot.DownloadModel("sentence-transformers/all-MiniLM-L6-v2","./models/",downloadOptions)iferr!=nil{panic(err)}// Create feature extraction pipeline configurationconfig:=hugot.FeatureExtractionConfig{ModelPath:modelPath,Name:"embeddingPipeline",}// Create the feature extraction pipelineembeddingPipeline,err:=hugot.NewPipeline(session,config)iferr!=nil{panic(err)}sentences:=[]string{"That is a very happy person","That is a happy dog","Today is a sunny day",}tags:=[]string{"persons","pets","weather",}// Generate embeddings using HugotembeddingResult,err:=embeddingPipeline.RunPipeline(sentences)iferr!=nil{panic(err)}// Extract the embeddings from the resultembeddings:=embeddingResult.Embeddingsfori,emb:=rangeembeddings{buffer:=floatsToBytes(emb)_,err=rdb.HSet(ctx,fmt.Sprintf("doc:%v",i),map[string]any{"content":sentences[i],"genre":tags[i],"embedding":buffer,},).Result()iferr!=nil{panic(err)}}// Generate query embedding using HugotqueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}buffer:=floatsToBytes(queryResult.Embeddings[0])results,err:=rdb.FTSearchWithArgs(ctx,"vector_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":buffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangeresults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}rdb.FTDropIndexWithArgs(ctx,"vector_json_idx",&redis.FTDropIndexOptions{DeleteDocs:true,},)_,err=rdb.FTCreate(ctx,"vector_json_idx",&redis.FTCreateOptions{OnJSON:true,Prefix:[]any{"jdoc:"},},&redis.FieldSchema{FieldName:"$.content",As:"content",FieldType:redis.SearchFieldTypeText,},&redis.FieldSchema{FieldName:"$.genre",As:"genre",FieldType:redis.SearchFieldTypeTag,},&redis.FieldSchema{FieldName:"$.embedding",As:"embedding",FieldType:redis.SearchFieldTypeVector,VectorArgs:&redis.FTVectorArgs{HNSWOptions:&redis.FTHNSWOptions{Dim:384,DistanceMetric:"L2",Type:"FLOAT32",},},},).Result()iferr!=nil{panic(err)}fori,emb:=rangeembeddings{_,err=rdb.JSONSet(ctx,fmt.Sprintf("jdoc:%v",i),"$",map[string]any{"content":sentences[i],"genre":tags[i],"embedding":emb,},).Result()iferr!=nil{panic(err)}}// Generate query embedding for JSON search using HugotjsonQueryResult,err:=embeddingPipeline.RunPipeline([]string{"That is a happy person",})iferr!=nil{panic(err)}jsonBuffer:=floatsToBytes(jsonQueryResult.Embeddings[0])jsonResults,err:=rdb.FTSearchWithArgs(ctx,"vector_json_idx","*=>[KNN 3 @embedding $vec AS vector_distance]",&redis.FTSearchOptions{Return:[]redis.FTSearchReturn{{FieldName:"vector_distance"},{FieldName:"content"},},DialectVersion:2,Params:map[string]any{"vec":jsonBuffer,},},).Result()iferr!=nil{panic(err)}for_,doc:=rangejsonResults.Docs{fmt.Printf("ID: %v, Distance:%v, Content:'%v'\n",doc.ID,doc.Fields["vector_distance"],doc.Fields["content"],)}}
Apart from the jdoc: prefixes for the keys, the result from the JSON
query is the same as for hash:
ID: jdoc:0, Distance:2.96992516518, Content:'That is a very happy person'
ID: jdoc:1, Distance:17.3678302765, Content:'That is a happy dog'
ID: jdoc:2, Distance:43.7771987915, Content:'Today is a sunny day'
Learn more
See
Vector search
for more information about the indexing options, distance metrics, and query format
for vectors.