前言
继续我们《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领域原创作者。