如何用 redis-py 的 JSON 与 RediSearch 向量索引搭建向量相似检索流程
如何用 redis-py 的 JSON 与 RediSearch 向量索引搭建向量相似检索流程【免费下载链接】redis-pyRedis Python client项目地址: https://gitcode.com/GitHub_Trending/re/redis-py这篇文章解决一个具体任务把一批 JSON 文档含文本字段存入 Redis为其中的描述文本生成向量embedding再用 RediSearch 建一个 JSON 向量索引最后用 redis-py 执行 KNN、混合过滤与 Range 三类向量相似查询。完整可运行的参考实现是仓库中的 search_vss.py本文的所有命令与代码均取自该脚本及 search_vector_similarity_examples.ipynb。准备环境安装 redis-py 并启动带 JSON 与 Search 模块的 Redis向量索引依赖 RedisJSON 与 RediSearch 两个模块。按照 README.md 的说法Redis 8.0 及以上版本的官方镜像已内置这些模块docker run -p 6379:6379 -it redis:latest如果使用 Redis 8.0 以下的版本需要改用 redis-stack 镜像docker run -p 6379:6379 -it redis/redis-stack:latest然后安装 redis-py。search_vss.py 还引用了numpy、pandas、requests、sentence_transformers这几个包分别用于向量编码、结果整理、数据集下载和 embedding 模型需要一并安装pip install redis numpy pandas requests sentence-transformersdocs/redismodules.rst 说明访问 Redis 模块命令要求安装对应的 Redis 模块上面的容器方式就是为了让 JSON 和 Search 命令可用。连接 Redis 并用 pipeline 写入 JSON 文档参考脚本从数据集加载 11 条自行车文档每条包含model、brand、price、type、description等字段然后逐条写入 Redisimport json import requests import redis URL (https://raw.githubusercontent.com/bsbodden/redis_vss_getting_started /main/data/bikes.json ) response requests.get(URL, timeout10) bikes response.json() client redis.Redis(hostlocalhost, port6379, decode_responsesTrue) res client.ping() # True pipeline client.pipeline() for i, bike in enumerate(bikes, start1): redis_key fbikes:{i:03} pipeline.json().set(redis_key, $, bike) res pipeline.execute() # [True, True, True, True, True, True, True, True, True, True, True]decode_responsesTrue让响应返回字符串而不是 bytesping()返回True说明连接正常。写入后可以用json().get抽查单条文档、用keys确认数量res client.json().get(bikes:010, $.model) # [Summit] keys sorted(client.keys(bikes:*)) # [bikes:001, bikes:002, ..., bikes:011]生成 embedding 并写回 JSON 文档向量不是 Redis 生成的而是先用模型把每条description编码成浮点数组再写回同一个 JSON 文档的$.description_embeddings路径。参考脚本使用SentenceTransformer的msmarco-distilbert-base-v4模型得到的向量维度是 768import numpy as np from sentence_transformers import SentenceTransformer descriptions client.json().mget(keys, $.description) descriptions [item for sublist in descriptions for item in sublist] embedder SentenceTransformer(msmarco-distilbert-base-v4) embeddings embedder.encode(descriptions).astype(np.float32).tolist() VECTOR_DIMENSION len(embeddings[0]) # 768 pipeline client.pipeline() for key, embedding in zip(keys, embeddings): pipeline.json().set(key, $.description_embeddings, embedding) pipeline.execute() # [True, True, True, True, True, True, True, True, True, True, True]VECTOR_DIMENSION是动态取出来的后面建索引时必须与它一致。写入后抽查一条文档可以确认description_embeddings是一个 768 维的数组文档示例输出中该字段以-0.538114607334137, -0.49465855956077576, ...等值开头。如果你换成其他 embedding 模型维度会不同VECTOR_DIMENSION与索引定义都要跟着变本文以 768 维为主线。创建 RediSearch JSON 向量索引这一步把普通字段和向量字段一起放进 schema并用IndexDefinition声明索引对象是 JSON 文档前缀bikes:import time from redis.commands.search.field import ( NumericField, TagField, TextField, VectorField, ) from redis.commands.search.index_definition import IndexDefinition, IndexType from redis.commands.search.query import Query schema ( TextField($.model, no_stemTrue, as_namemodel), TextField($.brand, no_stemTrue, as_namebrand), NumericField($.price, as_nameprice), TagField($.type, as_nametype), TextField($.description, as_namedescription), VectorField( $.description_embeddings, FLAT, { TYPE: FLOAT32, DIM: VECTOR_DIMENSION, DISTANCE_METRIC: COSINE, }, as_namevector, ), ) definition IndexDefinition(prefix[bikes:], index_typeIndexType.JSON) res client.ft(idx:bikes_vss).create_index(fieldsschema, definitiondefinition) # OK time.sleep(2)几个要点非向量字段用 JSONPath$.price、$.type指向文档内部字段并给每个字段起as_name查询时按这个名字引用。VectorField的第一个参数是向量所在的 JSONPath第二个是索引算法。源码 field.py 中VectorField.__init__说明algorithm可以是FLAT、HNSW或SVS-VAMANA并且向量字段不允许设置sortable或no_index会抛DataError。DIM必须与 embedding 实际维度一致DISTANCE_METRIC用COSINE表示按余弦距离排序。create_index返回OK表示索引创建成功time.sleep(2)是参考脚本里等待后台索引完成的做法。注意导入路径参考脚本用的是redis.commands.search.index_definition小写下划线这是当前源码中实际存在的模块名search_json_examples.ipynb 中的建索引代码也采用同样的 JSON 索引定义方式。验证索引构建结果建完索引后用ft(...).info()检查文档数和索引失败数info client.ft(idx:bikes_vss).info() num_docs info[num_docs] indexing_failures info[hash_indexing_failures] # print(f{num_docs} documents indexed with {indexing_failures} failures) # 11 documents indexed with 0 failures文档给出的示例结果是 11 条文档全部入库、0 次失败。如果你的数据集条数不同以num_docs与实际文档数一致、hash_indexing_failures为 0 作为判断标准hash_indexing_failures非 0 说明有文档的字段类型与 schema 不匹配需要回到数据本身排查。执行向量相似查询KNN、混合查询与 Range 查询向量查询必须通过.dialect(2)打开查询方言 2notebook 原文To use a VSS query, you must specify the option.dialect(2)。查询向量以 bytes 形式通过第二个参数传入。先把一批查询文本编码好作为查询向量来源queries [ Bike for small kids, Best Mountain bikes for kids, Cheap Mountain bike for kids, Female specific mountain bike, Road bike for beginners, Commuter bike for people over 60, Comfortable commuter bike, Good bike for college students, Mountain bike for beginners, Vintage bike, Comfortable city bike, ] encoded_queries embedder.encode(queries)KNN 查询取最相似的 Top-Kquery ( Query((*)[KNN 3 vector $query_vector AS vector_score]) .sort_by(vector_score) .return_fields(vector_score, id, brand, model, description) .dialect(2) ) res ( client.ft(idx:bikes_vss) .search( query, {query_vector: np.array(encoded_queries[0], dtypenp.float32).tobytes()}, ) .docs )KNN 3 vector $query_vector表示在名为vector的向量字段上取最相似的 3 条AS vector_score把距离值别名为vector_score。参考脚本在整理结果时把相似度换算为round(1 - float(doc.vector_score), 2)其文档示例输出的一行是| Best Mountain bikes for kids | 0.54 | bikes:003...。注意文档中的分数是按示例数据跑出来的换数据集后数值会不同不要把它当成固定预期。混合查询先过滤再向量排序把传统字段过滤和 KNN 放在同一条命令里例如只在Peaknetic品牌的文档中做向量检索hybrid_query ( Query((brand:Peaknetic)[KNN 3 vector $query_vector AS vector_score]) .sort_by(vector_score) .return_fields(vector_score, id, brand, model, description) .dialect(2) )这是文档示例中给出的第二种查询形态notebook 里对应的 HASH 索引版本用(tag:{ foo })[KNN 2 ...]表达同样的“过滤 KNN”组合。Range 查询按距离阈值筛选如果不想固定取 Top-K而是要“距离在某个半径内的所有文档”用VECTOR_RANGErange_query ( Query( vector:[VECTOR_RANGE $range $query_vector] {$YIELD_DISTANCE_AS: vector_score} ) .sort_by(vector_score) .return_fields(vector_score, id, brand, model, description) .paging(0, 4) .dialect(2) ) res client.ft(idx:bikes_vss).search( range_query, {range: 0.55, query_vector: np.array(encoded_queries[0], dtypenp.float32).tobytes()}, ).docs$range就是查询参数里传的距离阈值示例中为0.55$YIELD_DISTANCE_AS: vector_score让每条结果携带距离值。参考脚本用这条查询跑的文档示例输出是| Bike for small kids | 0.52 | bikes:001 | Velorim |...同样属于示例数据的结果。三种查询形态的选型依据以文档为准KNN 用于取 Top-KRange 用于按距离阈值过滤混合查询同时包含传统过滤条件与 VSS三者可以在同一个 Redis 命令中组合。限制与注意事项dialect 必须显式设置所有向量查询都要.dialect(2)否则 KNN / VECTOR_RANGE 语法不会被解析。向量字段的字段类型约束向量字段不能设sortable或no_indexVectorField的算法只接受FLAT、HNSW、SVS-VAMANA三种见 field.py。维度必须一致索引里的DIM与 embedding 实际维度不一致会导致写入或查询不符合预期建索引前先用VECTOR_DIMENSION len(embeddings[0])动态取维度。两种存储载体本文按标题走 JSON 文档IndexType.JSON JSONPath路线search_vector_similarity_examples.ipynb 演示的是 HASH 文档路线IndexType.HASHhset向量以embedding.tobytes()存入 hash 字段。两条路线的查询语法相同区别只在数据写入方式和IndexDefinition的index_type。重建索引如果更换数据集或 embedding 模型notebook 中的做法是先dropindex(delete_documentsTrue)删掉旧索引再重建delete_documentsTrue会连数据一起删除执行前确认这是你想要的。【免费下载链接】redis-pyRedis Python client项目地址: https://gitcode.com/GitHub_Trending/re/redis-py创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考