{
  "schema_version": 2,
  "id": "develop/clients/go/vecsearch",
  "title": "Index and query vectors",
  "url": "https://redis.io/docs/latest/develop/clients/go/vecsearch/",
  "summary": "Learn how to index and query vector embeddings with Redis",
  "tags": [
    "docs",
    "develop",
    "stack",
    "oss",
    "rs",
    "rc",
    "oss",
    "kubernetes",
    "clients"
  ],
  "last_updated": "2026-10-16T16:44:37+01:00",
  "page_type": "content",
  "content_hash": "5c28d2dd4b6ff0f676657e9c4746f5427e1bddb54cc8a706e6f32799dc215563",
  "sections": [
    {
      "id": "initialize",
      "title": "Initialize",
      "role": "content",
      "text": "First, install [`go-redis`](https://redis.io/docs/latest/develop/clients/go)\nif you haven't already done so. Then, install v0.7.7 of\n[`Hugot`](https://pkg.go.dev/github.com/knights-analytics/hugot),\nwhich requires Go 1.26.5 or later, using the following command:\n\n[code example]\n\nAdd the following imports to your module's main program file:\n\nFoundational: Import required packages for vector search and embedding operations\n\n**Difficulty:** Beginner\n\n**Available in:** Go\n\n##### Go\n\n[code example]"
    },
    {
      "id": "add-a-helper-function",
      "title": "Add a helper function",
      "role": "content",
      "text": "The `Hugot` model outputs the embeddings as a\n`[]float32` array. If you are storing your documents as\n[hash](https://redis.io/docs/latest/develop/data-types/hashes) objects, then you\nmust convert this array to a `byte` string before adding it as a hash field.\nThe function shown below uses Go's [`binary`](https://pkg.go.dev/encoding/binary)\npackage to produce the `byte` string:\n\nFoundational: Create a helper function to convert float32 embeddings to binary format for hash storage\n\n**Difficulty:** Beginner\n\n**Available in:** Go\n\n##### Go\n\n[code example]\n\n\n\nNote that if you are using [JSON](https://redis.io/docs/latest/develop/data-types/json)\nobjects to store your documents instead of hashes, then you should store\nthe `[]float32` array directly without first converting it to a `byte`\nstring (see [Differences with JSON documents](#differences-with-json-documents)\nbelow)."
    },
    {
      "id": "create-the-index",
      "title": "Create the index",
      "role": "content",
      "text": "In the `main()` function, connect to Redis and delete any index previously\ncreated with the name `vector_idx`:\n\nFoundational: Connect to Redis and clean up existing vector indexes\n\n**Difficulty:** Beginner\n\n**Available in:** Go\n\n##### Go\n\n[code example]\n\n\n\nNext, create the index.\nThe schema in the example below specifies hash objects for storage and includes\nthree fields: the text content to index, a\n[tag](https://redis.io/docs/latest/develop/ai/search-and-query/advanced-concepts/tags)\nfield to represent the \"genre\" of the text, and the embedding vector generated from\nthe original text content. The `embedding` field specifies\n[HNSW](https://redis.io/docs/latest/develop/ai/search-and-query/vectors#hnsw-index)\nindexing, the\n[L2](https://redis.io/docs/latest/develop/ai/search-and-query/vectors#distance-metrics)\nvector distance metric, `Float32` values to represent the vector's components,\nand 384 dimensions, as required by the `all-MiniLM-L6-v2` embedding model.\n\nFoundational: Create a vector search index with HNSW algorithm and L2 distance metric for hash documents\n\n**Difficulty:** Intermediate\n\n**Available in:** Go\n\n##### Go\n\n[code example]"
    },
    {
      "id": "create-an-embedder-instance",
      "title": "Create an embedder instance",
      "role": "content",
      "text": "You need an instance of the `FeatureExtractionPipeline` class to\ngenerate the embeddings. Use the code below to create an\ninstance that uses the `sentence-transformers/all-MiniLM-L6-v2`\nmodel:\n\nPractical pattern: Initialize a feature extraction pipeline for generating text embeddings\n\n**Difficulty:** Beginner\n\n**Available in:** Go\n\n##### Go\n\n[code example]"
    },
    {
      "id": "add-data",
      "title": "Add data",
      "role": "content",
      "text": "You can now supply the data objects, which will be indexed automatically\nwhen you add them with [`HSet()`](https://redis.io/docs/latest/commands/hset), as long as\nyou use the `doc:` prefix specified in the index definition.\n\nUse the `RunPipeline()` method of `FeatureExtractionPipeline`\nas shown below to create the embeddings that represent the `content` fields.\nThis method takes a context and an array of strings and returns a single\n`FeatureExtractionOutput` object.\nIts `Embeddings` field contains one array of float values for each input\nstring, in the same order. These are the vectors that you need for the index. Use the `floatsToBytes()` function defined\nabove to convert this array to a `byte` string.\n\nFoundational: Generate embeddings and store hash documents with vector fields using HSet\n\n**Difficulty:** Intermediate\n\n**Available in:** Go\n\n##### Go\n\n[code example]"
    },
    {
      "id": "bulk-import-with-himport",
      "title": "Bulk import with HIMPORT",
      "role": "content",
      "text": "From Redis Open Source 8.10, you can also add the documents with the\n[`HIMPORT`](https://redis.io/docs/latest/commands/himport) command family. This creates the same\nhashes as `HSet()`, but lets Redis store them as\n[compact hashes](https://redis.io/docs/latest/develop/data-types/hashes#compact-hashes), which use\nless memory when many keys share the same field names. Redis Search indexes\nthese hashes in the same way.\n\nDeclare the field names once with\n[`HImportPrepare()`](https://redis.io/docs/latest/commands/himport-prepare), then create each\ndocument with [`HImportSet()`](https://redis.io/docs/latest/commands/himport-set), passing only\nits values, in the same order as the field names. Use a\n[pipeline](https://redis.io/docs/latest/develop/clients/go/transpipe) to send all the\nimports in one round trip:\n\nBulk ingestion: Import hash documents with vector embeddings as compact hashes using HIMPORT PREPARE and a pipeline of HIMPORT SET calls\n\n**Difficulty:** Intermediate\n\n**Builds upon:** add_data\n\n**Available in:** Go\n\n##### Go\n\n[code example]\n\n\n\n`HImportSet()` replaces any existing key, so running this code after the\n`HSet()` calls leaves you with the same three documents.\n\n> [!NOTE]\n> `HIMPORT` requires `go-redis` v9.22.0 or later, and `go-redis` marks its\n> support as experimental, so the API might change. Redis Cloud and\n> Redis Software don't support `HIMPORT`."
    },
    {
      "id": "run-a-query",
      "title": "Run a query",
      "role": "content",
      "text": "After you have created the index and added the data, you are ready to run a query.\nTo do this, you must create another embedding vector from your chosen query\ntext. Redis calculates the similarity between the query vector and each\nembedding vector in the index as it runs the query. It then ranks the\nresults in order of this numeric similarity value.\n\nThe code below creates the query embedding using `RunPipeline()`, as with\nthe indexing, and passes it as a parameter when the query executes\n(see\n[Vector search](https://redis.io/docs/latest/develop/ai/search-and-query/query/vector-search)\nfor more information about using query parameters with embeddings).\n\nVector similarity search: Find semantically similar documents by comparing query embeddings with indexed vectors using L2 distance\n\n**Difficulty:** Intermediate\n\n**Available in:** Go\n\n##### Go\n\n[code example]\n\n\n\nThe code is now ready to run, but note that it may take a while to complete when\nyou run it for the first time (which happens because `Hugot`\nmust download the `all-MiniLM-L6-v2` model data before it can\ngenerate the embeddings). When you run the code, it outputs the following text:\n\n[code example]\n\nThe results are ordered according to the value of the `vector_distance`\nfield, with the lowest distance indicating the greatest similarity to the query.\nAs you would expect, the result for `doc:0` with the content text *\"That is a very happy person\"*\nis the result that is most similar in meaning to the query text\n*\"That is a happy person\"*."
    },
    {
      "id": "differences-with-json-documents",
      "title": "Differences with JSON documents",
      "role": "content",
      "text": "Indexing JSON documents is similar to hash indexing, but there are some\nimportant differences. JSON allows much richer data modelling with nested fields, so\nyou must supply a [path](https://redis.io/docs/latest/develop/data-types/json/path) in the schema\nto identify each field you want to index. However, you can declare a short alias for each\nof these paths (using the `As` option) to avoid typing it in full for\nevery query. Also, you must set `OnJSON` to `true` when you create the index.\n\nThe code below shows these differences, but the index is otherwise very similar to\nthe one created previously for hashes:\n\nFoundational: Create a vector search index for JSON documents with OnJSON option and JSON path specifications\n\n**Difficulty:** Intermediate\n\n**Available in:** Go\n\n##### Go\n\n[code example]\n\n\n\nUse [`JSONSet()`](https://redis.io/docs/latest/commands/json.set) to add the data\ninstead of [`HSet()`](https://redis.io/docs/latest/commands/hset). The maps\nthat specify the fields have the same structure as the ones used for `HSet()`.\n\nAn important difference with JSON indexing is that the vectors are\nspecified using lists instead of binary strings. The loop below is similar\nto the one used previously to add the hash data, but it doesn't use the\n`floatsToBytes()` function to encode the `float32` array.\n\nFoundational: Store JSON documents with vector embeddings as arrays using JSONSet\n\n**Difficulty:** Intermediate\n\n**Available in:** Go\n\n##### Go\n\n[code example]\n\n\n\nThe query is almost identical to the one for the hash documents. This\ndemonstrates how the right choice of aliases for the JSON paths can\nsave you having to write complex queries. An important thing to notice\nis that the vector parameter for the query is still specified as a\nbinary string (using the `floatsToBytes()` method), even though the data for\nthe `embedding` field of the JSON was specified as an array.\n\nVector query: Execute vector similarity search on JSON documents using path aliases for cleaner queries\n\n**Difficulty:** Intermediate\n\n**Available in:** Go\n\n##### Go\n\n[code example]\n\n\n\nApart from the `jdoc:` prefixes for the keys, the result from the JSON\nquery is the same as for hash:\n\n[code example]"
    },
    {
      "id": "learn-more",
      "title": "Learn more",
      "role": "related",
      "text": "See\n[Vector search](https://redis.io/docs/latest/develop/ai/search-and-query/query/vector-search)\nfor more information about the indexing options, distance metrics, and query format\nfor vectors."
    }
  ],
  "examples": [
    {
      "id": "initialize-ex0",
      "language": "bash",
      "code": "go get github.com/knights-analytics/hugot@v0.7.7",
      "section_id": "initialize"
    },
    {
      "id": "initialize-ex1",
      "language": "go",
      "code": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"math\"\n\n\t\"github.com/knights-analytics/hugot\"\n\t\"github.com/redis/go-redis/v9\"\n)",
      "section_id": "initialize"
    },
    {
      "id": "add-a-helper-function-ex0",
      "language": "go",
      "code": "func floatsToBytes(fs []float32) []byte {\n\tbuf := make([]byte, len(fs)*4)\n\n\tfor i, f := range fs {\n\t\tu := math.Float32bits(f)\n\t\tbinary.NativeEndian.PutUint32(buf[i*4:], u)\n\t}\n\n\treturn buf\n}",
      "section_id": "add-a-helper-function"
    },
    {
      "id": "create-the-index-ex0",
      "language": "go",
      "code": "ctx := context.Background()\n\trdb := redis.NewClient(&redis.Options{\n\t\tAddr:     \"localhost:6379\",\n\t\tPassword: \"\", // no password docs\n\t\tDB:       0,  // use default DB\n\t\tProtocol: 2,\n\t})\n\n\trdb.FTDropIndexWithArgs(ctx,\n\t\t\"vector_idx\",\n\t\t&redis.FTDropIndexOptions{\n\t\t\tDeleteDocs: true,\n\t\t},\n\t)",
      "section_id": "create-the-index"
    },
    {
      "id": "create-the-index-ex1",
      "language": "go",
      "code": "_, err := rdb.FTCreate(ctx,\n\t\t\"vector_idx\",\n\t\t&redis.FTCreateOptions{\n\t\t\tOnHash: true,\n\t\t\tPrefix: []any{\"doc:\"},\n\t\t},\n\t\t&redis.FieldSchema{\n\t\t\tFieldName: \"content\",\n\t\t\tFieldType: redis.SearchFieldTypeText,\n\t\t},\n\t\t&redis.FieldSchema{\n\t\t\tFieldName: \"genre\",\n\t\t\tFieldType: redis.SearchFieldTypeTag,\n\t\t},\n\t\t&redis.FieldSchema{\n\t\t\tFieldName: \"embedding\",\n\t\t\tFieldType: redis.SearchFieldTypeVector,\n\t\t\tVectorArgs: &redis.FTVectorArgs{\n\t\t\t\tHNSWOptions: &redis.FTHNSWOptions{\n\t\t\t\t\tDim:            384,\n\t\t\t\t\tDistanceMetric: \"L2\",\n\t\t\t\t\tType:           \"FLOAT32\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t).Result()\n\tif err != nil {\n\t\tpanic(err)\n\t}",
      "section_id": "create-the-index"
    },
    {
      "id": "create-an-embedder-instance-ex0",
      "language": "go",
      "code": "// Create a Hugot session\n\tsession, err := hugot.NewGoSession(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer func() {\n\t\terr := session.Destroy()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}()\n\n\t// Download the model\n\tdownloadOptions := hugot.NewDownloadOptions()\n\tdownloadOptions.OnnxFilePath = \"onnx/model.onnx\" // Specify which ONNX file to use\n\tmodelPath, err := hugot.DownloadModel(ctx, \"sentence-transformers/all-MiniLM-L6-v2\", \"./models/\", downloadOptions)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Create feature extraction pipeline configuration\n\tconfig := hugot.FeatureExtractionConfig{\n\t\tModelPath: modelPath,\n\t\tName:      \"embeddingPipeline\",\n\t}\n\n\t// Create the feature extraction pipeline\n\tembeddingPipeline, err := hugot.NewPipeline(session, config)\n\tif err != nil {\n\t\tpanic(err)\n\t}",
      "section_id": "create-an-embedder-instance"
    },
    {
      "id": "add-data-ex0",
      "language": "go",
      "code": "sentences := []string{\n\t\t\"That is a very happy person\",\n\t\t\"That is a happy dog\",\n\t\t\"Today is a sunny day\",\n\t}\n\n\ttags := []string{\n\t\t\"persons\", \"pets\", \"weather\",\n\t}\n\n\t// Generate embeddings using Hugot\n\tembeddingResult, err := embeddingPipeline.RunPipeline(ctx, sentences)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Extract the embeddings from the result\n\tembeddings := embeddingResult.Embeddings\n\n\tfor i, emb := range embeddings {\n\t\tbuffer := floatsToBytes(emb)\n\n\t\t_, err = rdb.HSet(ctx,\n\t\t\tfmt.Sprintf(\"doc:%v\", i),\n\t\t\tmap[string]any{\n\t\t\t\t\"content\":   sentences[i],\n\t\t\t\t\"genre\":     tags[i],\n\t\t\t\t\"embedding\": buffer,\n\t\t\t},\n\t\t).Result()\n\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}",
      "section_id": "add-data"
    },
    {
      "id": "bulk-import-with-himport-ex0",
      "language": "go",
      "code": "err = rdb.HImportPrepare(ctx, \"doc\", \"content\", \"genre\", \"embedding\").Err()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tpipe := rdb.Pipeline()\n\n\tfor i, emb := range embeddings {\n\t\tpipe.HImportSet(ctx,\n\t\t\tfmt.Sprintf(\"doc:%v\", i),\n\t\t\t\"doc\",\n\t\t\tsentences[i], tags[i], floatsToBytes(emb),\n\t\t)\n\t}\n\n\tif _, err := pipe.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}",
      "section_id": "bulk-import-with-himport"
    },
    {
      "id": "run-a-query-ex0",
      "language": "go",
      "code": "// Generate query embedding using Hugot\n\tqueryResult, err := embeddingPipeline.RunPipeline(ctx, []string{\n\t\t\"That is a happy person\",\n\t})\n\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tbuffer := floatsToBytes(queryResult.Embeddings[0])\n\n\tresults, err := rdb.FTSearchWithArgs(ctx,\n\t\t\"vector_idx\",\n\t\t\"*=>[KNN 3 @embedding $vec AS vector_distance]\",\n\t\t&redis.FTSearchOptions{\n\t\t\tReturn: []redis.FTSearchReturn{\n\t\t\t\t{FieldName: \"vector_distance\"},\n\t\t\t\t{FieldName: \"content\"},\n\t\t\t},\n\t\t\tDialectVersion: 2,\n\t\t\tParams: map[string]any{\n\t\t\t\t\"vec\": buffer,\n\t\t\t},\n\t\t},\n\t).Result()\n\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfor _, doc := range results.Docs {\n\t\tfmt.Printf(\n\t\t\t\"ID: %v, Distance:%v, Content:'%v'\\n\",\n\t\t\tdoc.ID, doc.Fields[\"vector_distance\"], doc.Fields[\"content\"],\n\t\t)\n\t}",
      "section_id": "run-a-query"
    },
    {
      "id": "run-a-query-ex1",
      "language": "plaintext",
      "code": "ID: doc:0, Distance:2.96992516518, Content:'That is a very happy person'\nID: doc:1, Distance:17.3678302765, Content:'That is a happy dog'\nID: doc:2, Distance:43.7771987915, Content:'Today is a sunny day'",
      "section_id": "run-a-query"
    },
    {
      "id": "differences-with-json-documents-ex0",
      "language": "go",
      "code": "rdb.FTDropIndexWithArgs(ctx,\n\t\t\"vector_json_idx\",\n\t\t&redis.FTDropIndexOptions{\n\t\t\tDeleteDocs: true,\n\t\t},\n\t)\n\n\t_, err = rdb.FTCreate(ctx,\n\t\t\"vector_json_idx\",\n\t\t&redis.FTCreateOptions{\n\t\t\tOnJSON: true,\n\t\t\tPrefix: []any{\"jdoc:\"},\n\t\t},\n\t\t&redis.FieldSchema{\n\t\t\tFieldName: \"$.content\",\n\t\t\tAs:        \"content\",\n\t\t\tFieldType: redis.SearchFieldTypeText,\n\t\t},\n\t\t&redis.FieldSchema{\n\t\t\tFieldName: \"$.genre\",\n\t\t\tAs:        \"genre\",\n\t\t\tFieldType: redis.SearchFieldTypeTag,\n\t\t},\n\t\t&redis.FieldSchema{\n\t\t\tFieldName: \"$.embedding\",\n\t\t\tAs:        \"embedding\",\n\t\t\tFieldType: redis.SearchFieldTypeVector,\n\t\t\tVectorArgs: &redis.FTVectorArgs{\n\t\t\t\tHNSWOptions: &redis.FTHNSWOptions{\n\t\t\t\t\tDim:            384,\n\t\t\t\t\tDistanceMetric: \"L2\",\n\t\t\t\t\tType:           \"FLOAT32\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t).Result()\n\tif err != nil {\n\t\tpanic(err)\n\t}",
      "section_id": "differences-with-json-documents"
    },
    {
      "id": "differences-with-json-documents-ex1",
      "language": "go",
      "code": "for i, emb := range embeddings {\n\t\t_, err = rdb.JSONSet(ctx,\n\t\t\tfmt.Sprintf(\"jdoc:%v\", i),\n\t\t\t\"$\",\n\t\t\tmap[string]any{\n\t\t\t\t\"content\":   sentences[i],\n\t\t\t\t\"genre\":     tags[i],\n\t\t\t\t\"embedding\": emb,\n\t\t\t},\n\t\t).Result()\n\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}",
      "section_id": "differences-with-json-documents"
    },
    {
      "id": "differences-with-json-documents-ex2",
      "language": "go",
      "code": "// Generate query embedding for JSON search using Hugot\n\tjsonQueryResult, err := embeddingPipeline.RunPipeline(ctx, []string{\n\t\t\"That is a happy person\",\n\t})\n\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tjsonBuffer := floatsToBytes(jsonQueryResult.Embeddings[0])\n\n\tjsonResults, err := rdb.FTSearchWithArgs(ctx,\n\t\t\"vector_json_idx\",\n\t\t\"*=>[KNN 3 @embedding $vec AS vector_distance]\",\n\t\t&redis.FTSearchOptions{\n\t\t\tReturn: []redis.FTSearchReturn{\n\t\t\t\t{FieldName: \"vector_distance\"},\n\t\t\t\t{FieldName: \"content\"},\n\t\t\t},\n\t\t\tDialectVersion: 2,\n\t\t\tParams: map[string]any{\n\t\t\t\t\"vec\": jsonBuffer,\n\t\t\t},\n\t\t},\n\t).Result()\n\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfor _, doc := range jsonResults.Docs {\n\t\tfmt.Printf(\n\t\t\t\"ID: %v, Distance:%v, Content:'%v'\\n\",\n\t\t\tdoc.ID, doc.Fields[\"vector_distance\"], doc.Fields[\"content\"],\n\t\t)\n\t}",
      "section_id": "differences-with-json-documents"
    },
    {
      "id": "differences-with-json-documents-ex3",
      "language": "plaintext",
      "code": "ID: jdoc:0, Distance:2.96992516518, Content:'That is a very happy person'\nID: jdoc:1, Distance:17.3678302765, Content:'That is a happy dog'\nID: jdoc:2, Distance:43.7771987915, Content:'Today is a sunny day'",
      "section_id": "differences-with-json-documents"
    }
  ]
}
