RAG是目前大语言模型从工具走向生产力实践的最热门的方式,它可以实现从海量的文本数据中检索相关的信息,并用于生成高质量的文本输出。而聊到RAG,我们就很难避开使用RAG的基础设施-向量数据库。

今天我将带领大家,以最为基础的CRUD入手来看看向量数据库应该如何使用。考虑到目前市面上的向量数据库众多,每个数据库的操作方式也无统一标准。本文将基于LangChain提供的VectorStore类中的统一操作方法,以chroma向量数据库作为示例进行演示。

向量数据库-新增

LangChain的VectorStore类是一个通用的向量数据库的接口,它可以对接不同的底层向量数据库,如chroma、faiss、annoy等,实现统一的操作方法和API。VectorStore类还提供了一些高级的功能,如语义检索、最大边际相关性(MMR)等,可以帮助我们更好地利用向量数据库的能力。

要想向向量数据库中新增数据,我们首先需要创建一个VectorStore对象,并在创建时配置好embedding function,即用于将原始数据转换为向量的函数。如下所示:

# 通过HuggingFace创建embedding_function
embeddings = HuggingFaceEmbeddings(model_name=model)
# 创建VectorStore的具体实现类Chroma对象,并指定collection_name和持久化目录
vector = Chroma(collection_name = 'cname', embedding_function=embeddings,persist_directory='/vs')

创建好VectorStore对象后,我们就可以使用add_documents方法来向向量数据库中插入数据了。add_documents方法接受doc对象的list作为参数,doc对象的page_content为要插入的文本内容,同时,我们还可以在doc对象中添加一些其他的元数据,用于后续的查询或过滤。例如,我们可以向向量数据库中插入以下doc对象:

# 先将文本拆分并转化为doc
loader = TextLoader(url,autodetect_encoding = True)

docs = loader.load()

text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)
# 插入向量数据库
vector.add_documents(documents=splits)

add_documents方法会返回一个id列表,这个id是doc的索引,用于唯一标识插入的doc对象。我们可以根据需要记录这个id,并与原始文件关联,一般来说,一个file对应多个doc,例如,我们可以将一个长文本拆分为多个段落,然后将每个段落作为一个doc插入到向量数据库中,这样可以提高检索的效率和精度。

向量数据库-删除

要想从向量数据库中删除数据,我们可以使用delete方法,delete方法接受一个id或一个id列表作为参数,然后根据id来删除对应的doc对象。例如,我们可以使用以下代码来删除id为1的doc对象:

# 删除id为1的doc对象
vector.delete('1')

如果我们不知道要删除的doc对象的id,但是知道它的一些元数据,我们可以先使用metadata中的字段来查询到id,然后再使用delete方法来批量删除。例如,我们可以使用以下代码来删除所有category为功能的doc对象:

# 根据file_id的条件,查询到所有符合的doc对象的id
reuslt = vector.get(where={"file_id":file_id}) 
# 使用delete方法,批量删除这些id对应的doc对象
if reuslt['ids'] :
    vector.delete(reuslt['ids'])

向量数据库-更新

VectorStore类没有提供专门的更新方法,因为更新一个doc对象相当于先删除它,然后再插入一个新的doc对象。因此,我们可以使用delete和insert方法来实现文档的更新。例如,我们可以使用以下代码来更新id为2的doc对象,将它的source从文档改为官网:

# 删除id为2的doc对象
vector.delete('2')

# 插入新的doc对象
vector.add_documents(new_doc)

向量数据库-查询

VectorStore类提供了多种查询方法,用于根据不同的需求和场景来检索向量数据库中的数据。查询方法主要分为两种类型:similarity和mmr。similarity类型的查询方法是基于向量之间的相似度来进行检索的,它可以接受一个字符串或一个向量作为查询,然后返回最相似的doc对象以及相似度。mmr类型的查询方法是基于最大边际相关性(MMR)来进行检索的,它可以接受一个字符串或一个向量作为查询,然后返回一个多样化的doc对象列表,这些doc对象既与查询相关,又尽量不相似。

接下来,我们将介绍以下四个查询方法:

  • similarity_search:这个方法是最基本的相似度查询方法,它接受一个字符串作为查询,以及一个可选的top_n参数,用于指定返回的doc对象的数量,默认为4,然后返回最相似的doc对象或它们的id。例如,我们可以使用以下代码来查询与“语义检索”最相似的doc对象:
# 查询与“语义检索”最相似的doc对象
docs = vector.similarity_search("语义检索")

# 打印查询结果
for doc in docs:
    print(doc)
  • similarity_search_with_score:这个方法与similarity_search类似,但是它会同时返回向量之间的距离,距离越小表示越相似。例如,我们可以使用以下代码来查询与“语义检索”最相似的doc对象,并打印它们的分数:
# 查询与“语义检索”最相似的doc对象,并返回分数
docs_with_score = vector.similarity_search_with_score("语义检索")

# 打印查询结果和分数
for doc_with_score in docs_with_score:
    print(doc_with_score[0], doc_with_score[1])
  • similarity_search_with_relevance_scores:这个方法与similarity_search_with_score类似,但是它会将分数转换为一个介于0和1之间的相关度评分,这个评分表示查询和doc对象之间的语义相关程度,评分越高越相似。代码与similarity_search_with_score类似,不再额外示例

  • max_marginal_relevance_search:这个方法是基于最大边际相关性(MMR)来进行查询的,最大边际相关性(MMR)是一种用于检索或摘要的方法,它既考虑了查询和文档之间的相似度,又考虑了文档之间的多样性,从而避免返回重复或冗余的结果。它接受一个字符串或一个向量作为查询,以及一个可选的top_n参数,用于指定返回的doc对象的数量,默认为10,然后返回一个多样化的doc对象列表,这些doc对象既与查询相关,又尽量不相似。这个方法可以用于生成摘要、提取关键信息、避免重复内容等场景。例如,我们可以使用以下代码来查询与“LangChain”相关的但不相似的doc对象:

# 查询与“LangChain”相关的但不相似的doc对象
docs = vector.max_marginal_relevance_search("LangChain")

# 打印查询结果
for doc in docs:
    print(doc)

结语

**CRUD只是操作向量数据库的基础手段,想要用好RAG必须在CRUD的基础上掌握语义检索的相关原理。**比如文档拆分时需要按语义尽可能的拆分为小的单元,而在召回时,则需要基于召回的单元尽可能的补充完整的窗口上下文,才能在最终使用LLM时得到尽可能好的结果。这些都需要在crud的基础上执行一些额外操作,我将在下次给大家讲述,希望你持续关注。

以上就是本文的全部内容,我希望你能从中学到一些有用的知识,也欢迎你在评论区留下你的反馈或问题。谢谢你的阅读,下次再见。😊