|
| 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 | +[`sentence-transformers`](https://pypi.org/project/sentence-transformers/) |
| 33 | +library to generate vector embeddings to store and index with |
| 34 | +Redis Query Engine. |
| 35 | + |
| 36 | +## Initialize |
| 37 | + |
| 38 | +Install [`redis-py`]({{< relref "/develop/clients/redis-py" >}}) if you |
| 39 | +have not already done so. Also, install `sentence-transformers` with the |
| 40 | +following command: |
| 41 | + |
| 42 | +```bash |
| 43 | +pip install sentence-transformers |
| 44 | +``` |
| 45 | + |
| 46 | +In a new Python source file, start by importing the required classes: |
| 47 | + |
| 48 | +```python |
| 49 | +from sentence_transformers import SentenceTransformer |
| 50 | +from redis.commands.search.query import Query |
| 51 | +from redis.commands.search.field import TextField, TagField, VectorField |
| 52 | +from redis.commands.search.indexDefinition import IndexDefinition, IndexType |
| 53 | + |
| 54 | +import numpy as np |
| 55 | +import redis |
| 56 | +``` |
| 57 | + |
| 58 | +The first of these imports is the |
| 59 | +`SentenceTransformer` class, which generates an embedding from a section of text. |
| 60 | +Here, we create an instance of `SentenceTransformer` that uses the |
| 61 | +[`all-MiniLM-L6-v2`](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2) |
| 62 | +model for the embeddings. This model generates vectors with 384 dimensions, regardless |
| 63 | +of the length of the input text, but note that the input is truncated to 256 |
| 64 | +tokens (see |
| 65 | +[Word piece tokenization](https://huggingface.co/learn/nlp-course/en/chapter6/6) |
| 66 | +at the [Hugging Face](https://huggingface.co/) docs to learn more about the way tokens |
| 67 | +are related to the original text). |
| 68 | + |
| 69 | +```python |
| 70 | +model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2") |
| 71 | +``` |
| 72 | + |
| 73 | +## Create the index |
| 74 | + |
| 75 | +Connect to Redis and delete any index previously created with the |
| 76 | +name `vector_idx`. (The `dropindex()` call throws an exception if |
| 77 | +the index doesn't already exist, which is why you need the |
| 78 | +`try: except:` block.) |
| 79 | + |
| 80 | +```python |
| 81 | +r = redis.Redis(decode_responses=True) |
| 82 | + |
| 83 | +try: |
| 84 | + r.ft("vector_idx").dropindex(True) |
| 85 | +except redis.exceptions.ResponseError: |
| 86 | + pass |
| 87 | +``` |
| 88 | + |
| 89 | +Next, we create the index. |
| 90 | +The schema in the example below specifies hash objects for storage and includes |
| 91 | +three fields: the text content to index, a |
| 92 | +[tag]({{< relref "/develop/interact/search-and-query/advanced-concepts/tags" >}}) |
| 93 | +field to represent the "genre" of the text, and the embedding vector generated from |
| 94 | +the original text content. The `embedding` field specifies |
| 95 | +[HNSW]({{< relref "/develop/interact/search-and-query/advanced-concepts/vectors#hnsw-index" >}}) |
| 96 | +indexing, the |
| 97 | +[L2]({{< relref "/develop/interact/search-and-query/advanced-concepts/vectors#distance-metrics" >}}) |
| 98 | +vector distance metric, `Float32` values to represent the vector's components, |
| 99 | +and 384 dimensions, as required by the `all-MiniLM-L6-v2` embedding model. |
| 100 | + |
| 101 | +```python |
| 102 | +schema = ( |
| 103 | + TextField("content"), |
| 104 | + TagField("genre"), |
| 105 | + VectorField("embedding", "HNSW", { |
| 106 | + "TYPE": "FLOAT32", |
| 107 | + "DIM": 384, |
| 108 | + "DISTANCE_METRIC":"L2" |
| 109 | + }) |
| 110 | +) |
| 111 | + |
| 112 | +r.ft("vector_idx").create_index( |
| 113 | + schema, |
| 114 | + definition=IndexDefinition( |
| 115 | + prefix=["doc:"], index_type=IndexType.HASH |
| 116 | + ) |
| 117 | +) |
| 118 | +``` |
| 119 | + |
| 120 | +## Add data |
| 121 | + |
| 122 | +You can now supply the data objects, which will be indexed automatically |
| 123 | +when you add them with [`hset()`]({{< relref "/commands/hset" >}}), as long as |
| 124 | +you use the `doc:` prefix specified in the index definition. |
| 125 | + |
| 126 | +Use the `model.encode()` method of `SentenceTransformer` |
| 127 | +as shown below to create the embedding that represents the `content` field. |
| 128 | +The `astype()` option that follows the `model.encode()` call specifies that |
| 129 | +we want a vector of `float32` values. The `tobytes()` option encodes the |
| 130 | +vector components together as a single binary string rather than the |
| 131 | +default Python list of `float` values. |
| 132 | +Use the binary string representation when you are indexing hash objects |
| 133 | +(as we are here), but use the default list of `float` for JSON objects. |
| 134 | + |
| 135 | +```python |
| 136 | +content = "That is a very happy person" |
| 137 | + |
| 138 | +r.hset("doc:0", mapping={ |
| 139 | + "content": content, |
| 140 | + "genre": "persons", |
| 141 | + "embedding": model.encode(content).astype(np.float32).tobytes(), |
| 142 | +}) |
| 143 | + |
| 144 | +content = "That is a happy dog" |
| 145 | + |
| 146 | +r.hset("doc:1", mapping={ |
| 147 | + "content": content, |
| 148 | + "genre": "pets", |
| 149 | + "embedding": model.encode(content).astype(np.float32).tobytes(), |
| 150 | +}) |
| 151 | + |
| 152 | +content = "Today is a sunny day" |
| 153 | + |
| 154 | +r.hset("doc:2", mapping={ |
| 155 | + "content": content, |
| 156 | + "genre": "weather", |
| 157 | + "embedding": model.encode(content).astype(np.float32).tobytes(), |
| 158 | +}) |
| 159 | +``` |
| 160 | + |
| 161 | +## Run a query |
| 162 | + |
| 163 | +After you have created the index and added the data, you are ready to run a query. |
| 164 | +To do this, you must create another embedding vector from your chosen query |
| 165 | +text. Redis calculates the similarity between the query vector and each |
| 166 | +embedding vector in the index as it runs the query. It then ranks the |
| 167 | +results in order of this numeric similarity value. |
| 168 | + |
| 169 | +The code below creates the query embedding using `model.encode()`, as with |
| 170 | +the indexing, and passes it as a parameter when the query executes |
| 171 | +(see |
| 172 | +[Vector search]({{< relref "/develop/interact/search-and-query/query/vector-search" >}}) |
| 173 | +for more information about using query parameters with embeddings). |
| 174 | + |
| 175 | +```python |
| 176 | +q = Query( |
| 177 | + "*=>[KNN 3 @embedding $vec AS vector_distance]" |
| 178 | +).return_field("score").dialect(2) |
| 179 | + |
| 180 | +query_text = "That is a happy person" |
| 181 | + |
| 182 | +res = r.ft("vector_idx").search( |
| 183 | + q, query_params={ |
| 184 | + "vec": model.encode(query_text).astype(np.float32).tobytes() |
| 185 | + } |
| 186 | +) |
| 187 | + |
| 188 | +print(res) |
| 189 | +``` |
| 190 | + |
| 191 | +The code is now ready to run, but note that it may take a while to complete when |
| 192 | +you run it for the first time (which happens because RedisVL must download the |
| 193 | +`all-MiniLM-L6-v2` model data before it can |
| 194 | +generate the embeddings). When you run the code, it outputs the following result |
| 195 | +object (slightly formatted here for clarity): |
| 196 | + |
| 197 | +```Python |
| 198 | +Result{ |
| 199 | + 3 total, |
| 200 | + docs: [ |
| 201 | + Document { |
| 202 | + 'id': 'doc:0', |
| 203 | + 'payload': None, |
| 204 | + 'vector_distance': '0.114169985056', |
| 205 | + 'content': 'That is a very happy person' |
| 206 | + }, |
| 207 | + Document { |
| 208 | + 'id': 'doc:1', |
| 209 | + 'payload': None, |
| 210 | + 'vector_distance': '0.610845386982', |
| 211 | + 'content': 'That is a happy dog' |
| 212 | + }, |
| 213 | + Document { |
| 214 | + 'id': 'doc:2', |
| 215 | + 'payload': None, |
| 216 | + 'vector_distance': '1.48624813557', |
| 217 | + 'content': 'Today is a sunny day' |
| 218 | + } |
| 219 | + ] |
| 220 | +} |
| 221 | +``` |
| 222 | + |
| 223 | +Note that the results are ordered according to the value of the `vector_distance` |
| 224 | +field, with the lowest distance indicating the greatest similarity to the query. |
| 225 | +As you would expect, the result for `doc:0` with the content text *"That is a very happy person"* |
| 226 | +is the result that is most similar in meaning to the query text |
| 227 | +*"That is a happy person"*. |
| 228 | + |
| 229 | +## Learn more |
| 230 | + |
| 231 | +See |
| 232 | +[Vector search]({{< relref "/develop/interact/search-and-query/query/vector-search" >}}) |
| 233 | +for more information about the indexing options, distance metrics, and query format |
| 234 | +for vectors. |
0 commit comments