|
我们在开发RAG增加检索应用时,总会遇到数据隔离的问题,比如,你上传的资料数据,不能让别人检索到,不然会存在数据安全问题。所以我们得为每个用户的数据进行隔离, 他们都不应该看到对方的数据,除非数据已经授权或者分配权限给他们访问。
本文主要实现该功能,只有进行了分配权限才能访问数据,该功能在企业或者RAG增强检索当中是比较常用的。
主要的实现步骤不清楚RAG增强检索的可以看之前的文章 1.先加载文档 2.对文档数据进行分块 3.分块之后增加role权限字段,用于过滤权限 4.在检索文档数据时,会按role的值进行过滤 5.把过滤好后的文档数据,加上用户提的问题一起发给llm模型处理(通义千问) 6.LLM处理后,返回结果
该功能实现的核心是增加role 权限字段过滤,在用户提问的时候,就分配好了他能检索哪方面的数据。
矢量数据库的选型为了以后能够检索大量的文档,我们需要把文档数据存储起来,这里会用到矢量数据库。
矢量数据库目前暂定为qdrant,选它的原因主要是因为它是开源的,能兼容多平台,在windows系统下也能快速安装,使用起来很方便。 也挺想用milvus矢量数据库的,它也是开源的,文档也挺全面,在windows下使用相对麻烦,就放弃了。
使用到的技术langchain+qwen+qdrant
开发前的准备通义千问API-KEY一个
开始教程安装依赖# 如果缺少了依赖请参考之前的文章,这里只安装了qdrant相关的 pip install --upgrade --quiet qdrant-client 引入依赖from langchain_community.chat_models.tongyi import ChatTongyi from langchain_community.vectorstores import Qdrant from langchain_community.document_loaders import PyPDFLoader from langchain_community.embeddings import DashScopeEmbeddings from langchain_text_splitters import RecursiveCharacterTextSplitter from qdrant_client.http import models as rest from langchain.prompts import PromptTemplate from langchain.chains.retrieval_qa.base import RetrievalQA
import os
qwen_api_key = "你的通义千问API-KEY" os.environ["DASHSCOPE_API_KEY"] = qwen_api_key 构建向量化对象embeddings = DashScopeEmbeddings( model="text-embedding-v1", dashscope_api_key=qwen_api_key ) 定义读取文档函数,并进行文档分块读取文档数据的方法,并定义角色metadata.roles字段,分块后的文档都会有该字段,表示这个文档有哪些角色可以访问。 def readPdfData(documents): page = []
for document_path, roles in documents.items(): # 读取当前目录下文件名为test.pdf和byd.pdf的文件 pdf_loader = PyPDFLoader(document_path, extract_images=True) text_splitter = RecursiveCharacterTextSplitter( chunk_size=1000, chunk_overlap=200, add_start_index=True )
loaded_documents = pdf_loader.load_and_split()
for doc in loaded_documents: doc.metadata["roles"] = roles # 对文档进行分块 split_documents = text_splitter.split_documents(loaded_documents) page.extend(split_documents) return page 读取test文件,并设置角色过滤字段roles为ali,意思是检索文档数据时,参数中roles字段包含ali 的时候才能访问这个文档数据 # 该pdf为阿里的财报 split_documents = readPdfData({"test.pdf": ["ali"]}) 构建qdrant矢量数据库对象存储数据的集合名称为my_documents,构建的方式是把文档数据加载到内存,仅用于测试,想构成存储到磁盘或者使用本地服务器的话参考官方提供的例子。 # 内存方式 qdrant = Qdrant.from_documents( split_documents, embeddings, prefer_grpc=True, location=":memory:", # 加载到内存 collection_name="my_documents", ) 在之前的集合追加文档读取maotai文件,并也为该文件分配了maotai权限 # 该文档为茅台的财报,可多个pdf documents = { "maotai.pdf": ["maotai"], }
split_documents = readPdfData(documents)
# 追加新的文档 qdrant.add_documents(split_documents, batch_size=20) 定义检索对象 检索文档数据库时,只会检索metadata.roles字段为maotai、ali的数据,其它数据都会过滤掉。 user_roles = ["maotai", "ali"]
qdrant_retriever = qdrant.as_retriever( search_kwargs={ "filter": rest.Filter( must=[ rest.FieldCondition( key="metadata.roles", match=rest.MatchAny(any=user_roles) ) ] ) } ) 构建大语言模型对象 llm=ChatTongyi(model="qwen-plus") 向qwen模型提问 根据用户提的问题,先去文档数据库中检索, 检索时会按我们定义的user_roles过滤掉数据,拿到相似的结果后,会根据用户的问题+相似的结果,一起发送给qwen模型,模型处理后会把最终的结果返回给我们。 prompt_template = """ Question: {question} 使用源回答问题。如果没有答案,请说"文本中没有答案".
Source: {context}
### Response: """ # 构建提问模板 prompt = PromptTemplate( template=prompt_template, input_variables=["context", "question"] )
# 构建过滤请求对象 retrieval_qa = RetrievalQA.from_chain_type( llm=llm, chain_type="stuff", retriever=qdrant_retriever, return_source_documents=True, chain_type_kwargs={"prompt": prompt}, )
# 发送请求 response = retrieval_qa.invoke({"query": "贵州茅台?"}) print(response["result"]) 以下是返回的结果,可以看出我们已经能访问到maotai.pdf文件的数据了 在2023年第三季度报告中,贵州茅台的主要财务数据显示:
1. 营业收入为103,268,354,688.44元,同比增长18.48%。 2. 归属于上市公司股东的净利润为52,876,217,064.12元,同比增长19.09%。 3. 流动资产中,货币资金为70,641,010,014.72元,拆出资金为95,625,606,731.69元。 4. 负债和所有者权益总计为262,076,424,771.47元,所有者权益合计为225,018,799,759.41元。
此外,报告还列出了贵州茅台的前10名无限售条件股东,其中中国贵州茅台酒厂(集团)有限责任公司是最大股东,持有679,211,576股。其他股东包括香港中央结算有限公司、贵州省国有资本运营有限责任公司等。 还是提同样的问题,我们把该访问maotai.pdf的角色授权roles字段改为a,这里我们就不会访问到数据了,因为我们没有maotai权限,我们定义了只有这个权限才能访问该文件。
把上面【定义检索对象】这个代码改为以下代码,执行,然后再重新执行【向qwen模型提问】中的代码进行提问 # 改为a权限 user_roles = ["a", "ali"]
qdrant_retriever = qdrant.as_retriever( search_kwargs={ "filter": rest.Filter( must=[ rest.FieldCondition( key="metadata.roles", match=rest.MatchAny(any=user_roles) ) ] ) } ) 最终返回的结果如下: 贵州茅台在上述文本中没有被提及。
最后总结:本次我们实现了用户之间的数据隔离,在存储文档时我们用到了qdrant开源矢量数据库,通过保存文档时定义权限字段,用户在检索文档时,只有请求参数中含有我们的文档权限时,才会访问到我们的数据,主要是通过检索时按指定字段来过滤实现该功能的。
|