Java开发者的Python快速实战指南:探索向量数据库之文本搜索

发布时间 2023-11-29 14:09:43作者: 努力的小雨

前言

如果说Python是跟随我的步伐学习的话,我觉得我在日常开发方面已经没有太大的问题了。然而,由于我没有Python开发经验,我思考着应该写些什么内容。我回想起学习Java时的学习路线,直接操作数据库是其中一项重要内容,无论使用哪种编程语言,与数据库的交互都是不可避免的。然而,直接操作MySQL数据库似乎缺乏趣味性,毕竟每天都在写SQL语句。突然我想到了我之前写过的一系列私人知识库文章,于是我想到了向量数据库,毕竟这是当前非常热门的技术之一。

如果AI离开了向量数据库,就好像失去了灵魂一样。市面上有很多向量数据库产品,我选择了最近腾讯推出的向量数据库,并且我还有一张免费试用卡,趁着还没过期,我决定写一些相关文章。而且我看了一下,这个数据库对于新手来说非常友好,因为它有可视化界面。对于一个新手来说,能够看到实际效果是最客观的。就像当初学习SQL时,如果没有Navicat这个可视化工具,就会感觉力不从心一样。

向量数据库

向量数据库具有将复杂的非结构化数据转化为多维逻辑坐标值的能力,简而言之,它可以将我们所了解的所有事物转化为可计算的数字。一旦数据进入数学领域,我们就能够对其进行计算。此外,向量数据库还可以作为一个外部知识库,为大型模型提供最新、最全面的信息,以应对需要及时回答的问题。同时,它也能够赋予大型语言模型长期记忆的能力,避免在对话过程中产生"断片"的情况。可以说,向量数据库是大型语言模型的最佳合作伙伴。

如果你对任何内容有任何疑问,请点击以下官方文档链接查看更多信息:https://img-bss.csdnimg.cn/1113tusoutuanli.pdf

虽然这是官方的文档,里面存在许多错误,我已经积极提供了反馈,但可惜没有得到有效处理。尽管如此,这并不会妨碍我们的阅读。文档最后还有一个官方的案例代码仓库,对于有兴趣的同学可以直接滑动到最后进行查阅。不过,对于新手而言,可能并不太友好,原因在于代码量较大,很难一下子消化。就好比刚学习Java的时候,要看别人的业务逻辑一样,即使有大量注释,也会感到吃力。

好的,废话不多说,我们直接进入正题吧。如果你还有未领取的,可以免费领取一下。

腾讯官方体验地址:https://cloud.tencent.com/product/vdb

建立数据库连接

领取完毕后,你需要创建一个新的免费示例,这个过程不难,大家都会。成功之后,你需要开启外网访问,否则无法进行本地的测试和联调。在开启外网访问时,需要将外网白名单ip设置为0.0.0.0/0,这将接受所有IP的请求。

image

好的,接下来我们需要获取数据库的登录名和密码。这些信息将用于连接和管理数据库。

image

创建数据库

import tcvectordb
from tcvectordb.model.enum import FieldType, IndexType, MetricType, ReadConsistency

#create a database client object
client = tcvectordb.VectorDBClient(url='http://*******', username='root', key='1*******', read_consistency=ReadConsistency.EVENTUAL_CONSISTENCY, timeout=30)
# create a database
db = client.create_database(database_name='db-xiaoyu')

print(db.database_name)

# list databases
db_list = client.list_databases()


for db in db_list:
         print(db.database_name)

好的,我们现在开始替换所需的内容,完成数据库的创建。一旦数据库创建完成,我们还需要创建集合,而不是传统的表,因为在向量数据库中,它们被称为集合。因此,我们接下来要创建集合。

创建集合

创建集合和创建表的过程类似,但前提是集合需要存储向量,而表用于存储数据。在这里,我们选择使用集成了embedding的集合。如果不使用集成的embedding,你需要使用其他embedding模型来输出向量,然后将其输入到集合中进行存储。除非你想手动输入向量值,否则这是必要的。

设计索引(不是设计 Collection 的结构)

在使用向量对应的文本字段时,不建议建立索引。这样做会占用大量内存资源,而且没有实际作用。除了向量对应的文本字段外,如果需要进行业务过滤,也就是在查询时需要使用where条件,那么必须单独为这个条件字段定义一个索引。也就是说,你需要用哪个字段进行过滤,就必须为该字段定义一个索引。向量数据库支持动态模式(Schema),在写入数据时可以写入任意字段,无需提前定义,类似于MongoDB。目前,主键id和向量字段vector是固定且必需的,字段名称也必须一致,否则会报错。

在之前讲解私人知识库的时候,我会单独引入其他embedding模型,因为向量数据库没有继承这些模型。不过,腾讯已经将embedding模型集成在了他们的系统中,这样就不需要来回寻找模型了。需要注意的是,为了确保一致性,你选择的embedding模型后面的vector字段要设置为768维。

db = client.database('db-xiaoyu')
# -- index config
index = Index(
    FilterIndex(name='id', field_type=FieldType.String, index_type=IndexType.PRIMARY_KEY),
    VectorIndex(name='vector', dimension=768, index_type=IndexType.HNSW,
                metric_type=MetricType.COSINE, params=HNSWParams(m=16, efconstruction=200)),
    FilterIndex(name='author', field_type=FieldType.String, index_type=IndexType.FILTER),
    FilterIndex(name='bookName', field_type=FieldType.String, index_type=IndexType.FILTER)
)

# Embedding config
ebd = Embedding(vector_field='vector', field='text', model=EmbeddingModel.BGE_BASE_ZH)

# create a collection
coll = db.create_collection(
    name='book-xiaoyu',
    shard=1,
    replicas=0,
    description='this is a collection of test embedding',
    embedding=ebd,
    index=index
)
print(vars(coll))

我们已经成功创建了数据库和集合,并且现在让我们来看一下它们的结构。实际上,它们的原理与MySQL和其他数据库相似,只是存储的内容和术语发生了变化。我们可以将其视为数据库操作。

image

插入/替换数据

当插入数据时,如果集合中已经存在具有相同ID的文档,则会删除原始文档并插入新的文档数据。需要注意的是,很多字段我们都没有指定,例如page、text等。你可以继续添加这些字段,因为它们类似于MongoDB。但请注意,text字段必须与你在配置embedding时指定的字段相同,否则无法将其转换为向量。

coll = db.collection('book-emb')
# 写入数据。
# 参数 build_index 为 True,指写入数据同时重新创建索引。
res = coll.upsert(
            documents=[
                Document(id='0001', text="话说天下大势,分久必合,合久必分。", author='罗贯中', bookName='三国演义', page=21),
                Document(id='0002', text="混沌未分天地乱,茫茫渺渺无人间。", author='吴承恩', bookName='西游记', page=22),
                Document(id='0003', text="甄士隐梦幻识通灵,贾雨村风尘怀闺秀。", author='曹雪芹', bookName='红楼梦', page=23)  
            ],
            build_index=True
        )

当我们完成数据插入后,我们可以立即执行查询操作。但请注意,如果你将 "build_index" 字段设置为 "false",即使插入成功,查询时也无法检索到数据。因此,如果要立即生效并能查询到数据,你必须将其设置为 "true"。这个是重建索引的过程

查询数据

这里的查询可以分为精确查询和相似度查询两种。精确查询是指除了向量字段外的其他字段查询条件都是精确匹配的。由于我们在建立索引时已经对作者(author)和书名(bookName)建立了索引,所以我们可以直接对它们进行数据过滤,但是我不会在这里演示。现在我将演示一下模糊查询,即对向量字段匹配后的结果进行查询,并同时加上过滤条件。

doc_lists = coll.searchByText(
                 embeddingItems=['天下大势,分久必合,合久必分'],
                 filter=Filter(Filter.In("bookName", ["三国演义", "西游记"])),
                 params=SearchParams(ef=200),
                 limit=3,
                 retrieve_vector=False, # 不返回向量
                 output_fields=['bookName','author']
             )
# printf
for i, docs in enumerate(doc_lists.get("documents")):
                for doc in docs:
                        print(doc)

除了上面提到的Python的写法,我们还可以通过界面来进行精确查询。只需要在界面中填写where后的条件即可。

image

要进行模糊查询,可以直接使用text文字进行查询,或者定义过滤字段来进行查询优化。

image

总结

剩下的删除数据这部分我就不演示了。今天先跟向量数据库熟悉一下界面操作,感觉就像在使用Kibana查询ES数据一样。不知道你们有没有类似的感觉。好了,今天我们先只关注文本操作,下一期我会尝试处理图像或者视频数据。总的来说,相比Java,Python的SDK使用起来更加舒适。如果你曾经使用过Java SDK与平台接口对接,就会发现Python SDK上手更快。