{
  "id": "vecsearch",
  "title": "Index and query vectors",
  "url": "https://redis.io/docs/latest/develop/clients/ruby/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-07-31T16:56:30+01:00",
  "page_type": "content",
  "content_hash": "962272336a11f837400b00e6951e64cc40e059b3e3426c6e661f2de8133b4e9c",
  "sections": [
    {
      "id": "initialize",
      "title": "Initialize",
      "role": "content",
      "text": "Install [`redis-rb`](https://redis.io/docs/latest/develop/clients/ruby) if you\nhave not already done so. Also, install `informers` with the\nfollowing command:\n\n[code example]\n\nThe `informers` gem pulls in [`onnxruntime`](https://rubygems.org/gems/onnxruntime)\n(which ships the ONNX Runtime shared library as a native extension) and\n[`tokenizers`](https://rubygems.org/gems/tokenizers) (a Hugging Face fast-tokenizer\nbinding). Both come as pre-built binary gems for `arm64-darwin`, `x86_64-darwin`,\n`aarch64-linux`, and `x86_64-linux`, so there is no system ONNX install step on\nthose platforms."
    },
    {
      "id": "import-dependencies",
      "title": "Import dependencies",
      "role": "content",
      "text": "In a new Ruby source file, start by requiring the libraries and declaring a\nshort alias for the Query Engine namespace:\n\nFoundational: Import required libraries for vector embeddings, Redis operations, and search functionality\n\n**Difficulty:** Beginner\n\n**Available in:** Ruby\n\n##### Ruby\n\n[code example]"
    },
    {
      "id": "initialize-the-embedding-model",
      "title": "Initialize the embedding model",
      "role": "content",
      "text": "The [`Informers.pipeline`](https://github.com/ankane/informers#usage) method\nreturns a callable that generates an embedding from a section of text.\nHere, we create a pipeline that uses the\n[`all-MiniLM-L6-v2`](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2)\nmodel. This model generates vectors with 384 dimensions, regardless\nof the length of the input text, but note that the input is truncated to 256\ntokens (see\n[Word piece tokenization](https://huggingface.co/learn/nlp-course/en/chapter6/6)\nat the [Hugging Face](https://huggingface.co/) docs to learn more about the way tokens\nare related to the original text).\n\nFoundational: Initialize an informers pipeline to generate vector embeddings from text\n\n**Difficulty:** Beginner\n\n**Available in:** Ruby\n\n##### Ruby\n\n[code example]"
    },
    {
      "id": "define-a-helper-method",
      "title": "Define a helper method",
      "role": "content",
      "text": "The embedding pipeline returns the vector as an `Array` of `Float` values.\nWhen you store a vector in a hash object, you must encode this array as a\nbinary string of raw little-endian `float32` values, which is the format\nRedis Search expects. Declare a helper method `to_bytes` that packs the\narray using Ruby's [`Array#pack`](https://docs.ruby-lang.org/en/master/Array.html#method-i-pack)\ndirective `'e*'`:\n\nFoundational: Create a helper method to pack float arrays into binary strings for vector storage in hash objects\n\n**Difficulty:** Beginner\n\n**Available in:** Ruby\n\n##### Ruby\n\n[code example]"
    },
    {
      "id": "create-the-index",
      "title": "Create the index",
      "role": "content",
      "text": "Connect to Redis:\n\nFoundational: Connect to a Redis server\n\n**Difficulty:** Beginner\n\n**Available in:** Ruby\n\n##### Ruby\n\n[code example]\n\n\n\nNext, delete any index previously created with the name `vector_idx`\n(the `ft_dropindex` call raises an exception if the index doesn't already exist,\nwhich is why you need the `begin`/`rescue` block) and 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 for hash documents with HNSW algorithm and L2 distance metric\n\n**Difficulty:** Intermediate\n\n**Available in:** Ruby\n\n##### Ruby\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\nCall the pipeline with `pooling: 'mean'` and `normalize: true` to create the\nembedding that represents the `content` field, then pass the result through the\n`to_bytes` helper to get the binary string that Redis Search indexes.\n\nFoundational: Store hash documents with vector embeddings generated from text content\n\n**Difficulty:** Beginner\n\n**Available in:** Ruby\n\n##### Ruby\n\n[code example]"
    },
    {
      "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, packs it with the `to_bytes` helper,\nand passes it as a query parameter (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:** Ruby\n\n##### Ruby\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 `informers` must download the\n`all-MiniLM-L6-v2` model data before it can generate the embeddings). When you run\nthe code, it outputs the following result (the exact distances may differ slightly\non your system):\n\n[code example]\n\nNote that the 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\"*.\n\nNote also that `redis-rb` strips the key prefix (`doc:`) that the index\ndefinition specifies, so the document id returned in the result is `0` rather\nthan `doc:0`."
    },
    {
      "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:` keyword argument) to avoid typing it in full for\nevery query. Also, you must specify `Search::IndexType::JSON` 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 JSON paths and field aliases\n\n**Difficulty:** Intermediate\n\n**Available in:** Ruby\n\n##### Ruby\n\n[code example]\n\n\n\nUse [`json_set()`](https://redis.io/docs/latest/commands/json.set) to add the data\ninstead of [`hset()`](https://redis.io/docs/latest/commands/hset).\n\nAn important difference with JSON indexing is that the vectors are\nspecified using arrays instead of binary strings. Pass the `Array<Float>`\nreturned by the embedding pipeline directly, without the `to_bytes` step\nyou use for a hash.\n\nFoundational: Store JSON documents with vector embeddings as arrays (different from hash binary format)\n\n**Difficulty:** Beginner\n\n**Available in:** Ruby\n\n##### Ruby\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 `to_bytes` helper), even though the data for\nthe `embedding` field of the JSON was specified as an array.\n\nVector similarity search: Query JSON documents using vector embeddings with field aliases for simplified syntax\n\n**Difficulty:** Intermediate\n\n**Available in:** Ruby\n\n##### Ruby\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": "gem install informers",
      "section_id": "initialize"
    },
    {
      "id": "import-dependencies-ex0",
      "language": "ruby",
      "code": "require 'redis'\nrequire 'informers'\n\n# A short alias for the Query Engine namespace to keep the code below readable.\nSearch = Redis::Commands::Search",
      "section_id": "import-dependencies"
    },
    {
      "id": "initialize-the-embedding-model-ex0",
      "language": "ruby",
      "code": "# `informers` is a Ruby port of Hugging Face transformers that runs the\n# ONNX-exported `all-MiniLM-L6-v2` encoder locally through `onnxruntime`.\n# `Informers.pipeline(\"embedding\", ...)` returns a callable that maps a\n# string to a 384-element `Array<Float>`. The model data is downloaded\n# into the local Hugging Face cache on the first call.\nmodel = Informers.pipeline('embedding', 'sentence-transformers/all-MiniLM-L6-v2')",
      "section_id": "initialize-the-embedding-model"
    },
    {
      "id": "define-a-helper-method-ex0",
      "language": "ruby",
      "code": "# Redis Search stores a `FLOAT32` vector as raw little-endian bytes, with\n# no header. Ruby's `Array#pack` directive `'e'` is a little-endian\n# single-precision float, so `'e*'` packs every element of the vector.\n# The result is an ASCII-8BIT (binary) string that `redis-rb` sends\n# through unchanged.\ndef to_bytes(vector)\n  vector.pack('e*')\nend",
      "section_id": "define-a-helper-method"
    },
    {
      "id": "create-the-index-ex0",
      "language": "ruby",
      "code": "r = Redis.new",
      "section_id": "create-the-index"
    },
    {
      "id": "create-the-index-ex1",
      "language": "ruby",
      "code": "begin\n  r.ft_dropindex('vector_idx', delete_documents: true)\nrescue Redis::CommandError\n  # Index doesn't exist, so there is nothing to drop.\nend\n\nschema = Search::Schema.build do\n  text_field 'content'\n  tag_field 'genre'\n  vector_field 'embedding', 'HNSW',\n               type: 'FLOAT32', dim: 384, distance_metric: 'L2'\nend\n\ndefinition = Search::IndexDefinition.new(\n  prefix: ['doc:'],\n  index_type: Search::IndexType::HASH\n)\n\nindex = r.create_index('vector_idx', schema, definition: definition)\nputs index.name # >>> vector_idx",
      "section_id": "create-the-index"
    },
    {
      "id": "add-data-ex0",
      "language": "ruby",
      "code": "# Pass `normalize: true` so `informers` L2-normalises each embedding in\n# the ONNX graph; the L2 distances Redis reports are then directly\n# comparable across documents.\nsentence1 = 'That is a very happy person'\nr.hset('doc:0', {\n  'content' => sentence1,\n  'genre' => 'persons',\n  'embedding' => to_bytes(model.(sentence1, pooling: 'mean', normalize: true))\n})\n\nsentence2 = 'That is a happy dog'\nr.hset('doc:1', {\n  'content' => sentence2,\n  'genre' => 'pets',\n  'embedding' => to_bytes(model.(sentence2, pooling: 'mean', normalize: true))\n})\n\nsentence3 = 'Today is a sunny day'\nr.hset('doc:2', {\n  'content' => sentence3,\n  'genre' => 'weather',\n  'embedding' => to_bytes(model.(sentence3, pooling: 'mean', normalize: true))\n})",
      "section_id": "add-data"
    },
    {
      "id": "run-a-query-ex0",
      "language": "ruby",
      "code": "query_text = 'That is a happy person'\nquery_vec = to_bytes(model.(query_text, pooling: 'mean', normalize: true))\n\nres = index.search(\n  '*=>[KNN 3 @embedding $vec AS vector_distance]',\n  sort_by: 'vector_distance',\n  return_fields: ['content', 'vector_distance'],\n  params: { 'vec' => query_vec }\n)\n\nputs res.total # >>> 3\n# The index has the key prefix `doc:`, so `redis-rb` returns the logical\n# document id with that prefix removed (`0`, not `doc:0`).\nres.each do |doc|\n  puts \"#{doc.id}: #{doc['content']} (distance #{doc['vector_distance']})\"\nend",
      "section_id": "run-a-query"
    },
    {
      "id": "run-a-query-ex1",
      "language": "plaintext",
      "code": "3\n0: That is a very happy person (distance 0.114169895649)\n1: That is a happy dog (distance 0.610845208168)\n2: Today is a sunny day (distance 1.48624789715)",
      "section_id": "run-a-query"
    },
    {
      "id": "differences-with-json-documents-ex0",
      "language": "ruby",
      "code": "begin\n  r.ft_dropindex('vector_json_idx', delete_documents: true)\nrescue Redis::CommandError\n  # Index doesn't exist, so there is nothing to drop.\nend\n\njson_schema = Search::Schema.build do\n  text_field '$.content', as: 'content'\n  tag_field '$.genre', as: 'genre'\n  vector_field '$.embedding', 'HNSW', as: 'embedding',\n               type: 'FLOAT32', dim: 384, distance_metric: 'L2'\nend\n\njson_definition = Search::IndexDefinition.new(\n  prefix: ['jdoc:'],\n  index_type: Search::IndexType::JSON\n)\n\njson_index = r.create_index('vector_json_idx', json_schema, definition: json_definition)\nputs json_index.name # >>> vector_json_idx",
      "section_id": "differences-with-json-documents"
    },
    {
      "id": "differences-with-json-documents-ex1",
      "language": "ruby",
      "code": "# For a JSON document, store the embedding as a plain `Array<Float>`\n# (a JSON array), not the packed binary string used for hashes.\nr.json_set('jdoc:0', '$', {\n  'content' => sentence1,\n  'genre' => 'persons',\n  'embedding' => model.(sentence1, pooling: 'mean', normalize: true)\n})\n\nr.json_set('jdoc:1', '$', {\n  'content' => sentence2,\n  'genre' => 'pets',\n  'embedding' => model.(sentence2, pooling: 'mean', normalize: true)\n})\n\nr.json_set('jdoc:2', '$', {\n  'content' => sentence3,\n  'genre' => 'weather',\n  'embedding' => model.(sentence3, pooling: 'mean', normalize: true)\n})",
      "section_id": "differences-with-json-documents"
    },
    {
      "id": "differences-with-json-documents-ex2",
      "language": "ruby",
      "code": "# The query vector is still passed as a packed binary string, even though\n# the stored `embedding` field is a JSON array.\njson_res = json_index.search(\n  '*=>[KNN 3 @embedding $vec AS vector_distance]',\n  sort_by: 'vector_distance',\n  return_fields: ['content', 'vector_distance'],\n  params: { 'vec' => query_vec }\n)\n\nputs json_res.total # >>> 3\njson_res.each do |doc|\n  puts \"#{doc.id}: #{doc['content']} (distance #{doc['vector_distance']})\"\nend",
      "section_id": "differences-with-json-documents"
    },
    {
      "id": "differences-with-json-documents-ex3",
      "language": "plaintext",
      "code": "3\n0: That is a very happy person (distance 0.114169895649)\n1: That is a happy dog (distance 0.610845208168)\n2: Today is a sunny day (distance 1.48624789715)",
      "section_id": "differences-with-json-documents"
    }
  ]
}
