ChatGPT 私房菜:向量数据库 Redis,文档搜索,一键上手部署

Redis 增强特性,支持向量搜索

前言

继续我们《ChatGPT 私房菜》系列的文章。

由于 ChatGPT 上下文长度的限制,对于检索问答类内容,更好的方法是使用 openai enbedding,对现有的数据文档,进行矢量化处理,并存入向量数据库。新的提问过来,先使用 embedding 处理一次,然后根据数据库提供的最小距离搜索,找到匹配的答案。

Redis 是 web 开发中,最常用的技术之一。用于应对流量洪峰,速率限制等场景。加载了 Redis-search 和 Redis-JSON 扩展,使 Redis 得以支持结构化数据的搜索。

准备条件

Redis 用于存储向量数据,官方文档看这里:

https://redis.io/docs/interact/search-and-query/search/vectors/

实现搜索和存储向量数据,需要 Redis 启动时载入 Redis Search 和 Redis JSON 两个模块。

1,redis json 模块

模块仓库地址:

https://github.com/RedisJSON/RedisJSON/

简单体验 JSON 读写功能的,可使用提供的 docker 镜像:

docker run -p 6379:6379 –name redis-stack redis/redis-stack:latest

如果是生产环境,Linux 平台下,下载对应平台的 so 文件,在 redis 服务启动指令中,指定加载 JSON 模块:

redis-server –loadmodule ./target/release/librejson.so

正常启动后,在命令行使用 redis-cli 连接 redis。简单的操作示例如下:

1
2
3
4
5
6
> JSON.STRLEN animal $
1) "3"
> JSON.STRAPPEND animal $ '" (Canis familiaris)"'
1) "22"
> JSON.GET animal $
"[\"dog (Canis familiaris)\"]"

2,redis search 模块

Redis JSON 模块提供了非结构化数据的存储,以及读写功能。存入 Redis 的大量 JSON 文件,根据某个嵌套的键值进行搜索,就要用到 Redis search 模块。不仅如此,Redis search 增强的搜索功能,支持全文检索、向量相似形检索。

Redis search 仓库地址:

https://github.com/RediSearch/RediSearch/

官方文档使用了一整个篇幅,详细介绍了这个模块的安装、使用、编码。参照这里:

https://redis.io/docs/interact/search-and-query/

本地快速体验,使用 docker 镜像:

1
docker run -p 6379:6379 redis/redis-stack-server:latest

数据向量化

准备好 Redis 模块,并本地启动成功。可以开始以下的数据处理。

1,安装 Python 依赖

推荐你本地使用虚拟环境,进行以下代码测试。安装依赖:

1
pip install redis openai python-dotenv openai[datalib]

在环境变量内设置 openai 的 api key,比保证安全。

1
export OPENAI_API_KEY=sk-xxxxxxxx

2,创建文本向量

向量是一组多维的数组,数组元素为 float 类型数据。

首先引入项目需要的类文件:

1
2
3
4
5
6
import openai
import os
from dotenv import load_dotenv

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

使用 openai embedding 向量模型,并封装为函数:

1
2
3
def get_vector(text, model="text-embedding-ada-002"):
    text = text.replace("\n", " ")
    return openai.Embedding.create(input = [text], model = model)['data'][0]['embedding']

为了演示,使用了简单的 text 文本替代。你本地使用的文档,一般都比较长,且较为复杂。

1
2
3
4
5
6
text_1 = """Revised figures"""
text_2 = """the Boston Indoor Games"""
text_3 = """Search engine"""
doc_1 = {"content": text_1, "vector": get_vector(text_1)}
doc_2 = {"content": text_2, "vector": get_vector(text_2)}
doc_3 = {"content": text_3, "vector": get_vector(text_3)}

其中 doc_x 就是向量化之后的数据。下一步,我们会将这些向量数据,存储到 Redis 内,以便检索。

3,使用 Redis

使用以下代码,在 Python 内连接 Redis:

1
2
3
4
5
from redis import from_url

REDIS_URL = 'redis://localhost:6379'
client = from_url(REDIS_URL)
client.ping()

向量数据的相似搜索,创建 redis 键,并指定索引类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from redis.commands.search.field import TextField, VectorField
from redis.commands.search.indexDefinition import IndexDefinition, IndexType

schema = [ VectorField('$.vector', 
            "FLAT", 
            {   "TYPE": 'FLOAT32', 
                "DIM": len(doc_1['vector']), 
                "DISTANCE_METRIC": "COSINE"
            },  as_name='vector' ),
            TextField('$.content', as_name='content')
        ]

idx_def = IndexDefinition(index_type=IndexType.JSON, prefix=['doc:'])
client.ft('idx').create_index(schema, definition=idx_def)

之后,把上一节 doc_x 矢量数据,存入创建的索引内:

1
2
3
client.json().set('doc:1', '$', doc_1)
client.json().set('doc:2', '$', doc_2)
client.json().set('doc:3', '$', doc_3)

至此,对文本数据的的向量化处理,就完成了。回顾一下:

  • 使用 openai embedding 对文本做矢量化处理;
  • 创建 redis json 索引,并指定索引结构;
  • 将矢量化数据写入 redis 索引;

高级搜索

准备好向量数据,本节介绍搜索的方法。

1,语义化搜索

使用 KNN 搜索方式,根据给定的一段文本,搜索相似的文档:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from redis.commands.search.query import Query
import numpy as np

text_4 = """Paula Radcliffe"""

vec = np.array(get_vector(text_4), dtype=np.float32).tobytes()
q = Query('*=>[KNN 3 @vector $query_vec AS vector_score]')\
    .sort_by('vector_score')\
    .return_fields('vector_score', 'content')\
    .dialect(2)    
params = {"query_vec": vec}

results = client.ft('idx').search(q, query_params=params)
for doc in results.docs:
    print(f"distance:{round(float(doc['vector_score']),3)} content:{doc['content']}\n")

输出结果类似这样:

1
2
3
distance:0.188 content:Revised figures ...
distance:0.268 content:the Boston Indoor Games ...
distance:0.287 content:Search engine ...

2,混合搜索

与 KNN 搜索类似,只需要修改搜索的方式:

1
2
3
4
q = Query('@content:recession => [KNN 3 @vector $query_vec AS vector_score]')
    .sort_by('vector_score')
    .return_fields('vector_score', 'content')
    .dialect(2)

最后

本文使用 openai 的向量化处理,Redis JSON 和搜索功能,演示了如何实现一个简单的文本相似性搜索。复杂的文档数据,也可以支持,将目标 JSON 文件、图片、音频进行向量化,即支持文档的高级搜索。

我是@程序员小助手,专注编程知识,圈子动态的IT领域原创作者。