首页 > 教程攻略 > ai资讯 >langchain+qwen实现RAG数据之间的隔离

langchain+qwen实现RAG数据之间的隔离

来源:互联网 时间:2026-08-05 16:28:12

关于RAG(检索增强生成)应用中的数据隔离,其实是一个绕不开的痛点。说白了,你在系统里上传的那些资料,总不能让大家随便翻吧?你上传的阿里的财报,别人一搜就出来了,这肯定不行。

langchain+qwen实现RAG数据之间的隔离

所以,在开发这类应用时,数据隔离是硬需求。核心原则很简单:每个用户只能看到自己被授权访问的那部分数据,不能越界。这篇文章就来聊一聊,如何用LangChain、通义千问(Qwen)和Qdrant向量数据库,把这个功能落地。

主要的实现步骤

如果熟悉RAG的朋友,可以直接看核心思路。大致流程是这样的:

  • 先加载文档
  • 对文档数据进行分块
  • 分块之后,给每一块数据打上role权限标签,明确谁可以看
  • 当用户发起检索时,会根据role字段把数据过滤一遍
  • 过滤后的相关数据,连同问题一起发给大模型(这里用的是通义千问)
  • LLM处理后,返回最终答案

整个功能的核心,其实就是这个role字段的过滤逻辑。当用户提问时,我们就已经确定了他能查哪一类的文档。

向量数据库的选型

要存大量文档的数据,肯定得用向量数据库。这里选了Qdrant。原因挺直接:开源、跨平台、在Windows下也能快速部署和玩玩。很多人也会考虑Milvus,文档做得确实不错,但Windows下的开发环境配置起来相对麻烦一点,就暂时没选。

使用到的技术栈

本次用到的技术组合是:langchain + qwen + qdrant

开发前的准备

首先,你需要准备一个通义千问的API-KEY,没有这个,后面就没法玩。

开始教程

安装依赖

如果之前有过LangChain环境的,可以跳过部分依赖。主要安装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
)

定义读取文档的函数,并进行分块

这里我们定义了一个方法,读取PDF文件。在处理过程中,会为每一块文档的元数据(metadata)添加一个roles字段,这个字段就是一个权限列表,标识哪些角色可以访问这块数据。

def readPdfData(documents):
    page = []

    for document_path, roles in documents.items():
        # 加载文件
        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

在调用时,比如我们加载一份阿里的财报PDF,就为它分配一个ali的权限标记:

split_documents = readPdfData({"test.pdf": ["ali"]})

构建Qdrant向量数据库对象

我们将数据存入Qdrant。这里为了演示方便,直接将数据加载到内存中。集合名称定义为my_documents

qdrant = Qdrant.from_documents(
    split_documents,
    embeddings,
    prefer_grpc=True,
    location=":memory:",
    collection_name="my_documents",
)

往已有的集合里追加新文档

假设我们又拿到了一份茅台的财报PDF,同样为其分配一个maotai权限标记:

documents = {
    "maotai.pdf": ["maotai"],
}

split_documents = readPdfData(documents)
qdrant.add_documents(split_documents, batch_size=20)

定义检索对象

现在,我们在检索时,会利用filter参数,只返回那些metadata.roles字段中包含我们指定角色的数据。这里假设用户有maotaiali两个角色的权限:

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模型提问

当用户提问“贵州茅台?”时,系统会拿着问题去Qdrant里检索,先通过权限过滤,再找出最相关的文档内容,最后将这些内容作为上下文,连同用户的原始问题一起发送给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"])

输出结果如下:

在2023年第三季度报告中,贵州茅台的主要财务数据显示:

1. 营业收入为103,268,354,688.44元,同比增长18.48%。
2. 归属于上市公司股东的净利润为52,876,217,064.12元,同比增长19.09%。
3. ...

看起来很不错,因为我们正好有权限访问maotai.pdf

现在换个场景。我们把用户的权限修改一下,只给aali权限,不再包含maotai

user_roles = ["a", "ali"]

然后重新执行上面的提问代码,你会发现,模型给出的回答变成了:

贵州茅台在上述文本中没有被提及。

这就对了。由于我们去掉了maotai权限,即使数据就存在库里,用户也搜不到它。

最后总结

至此,我们实现了一个基于权限的数据隔离方案。核心思路很简单:在将文档存入Qdrant向量数据库时,通过metadata.roles字段定义好数据的分组和权限归属;当用户发起检索时,检索器根据用户自身携带的角色权限进行过滤,确保他只能看到自己有权访问的数据。文件归文件,用户归用户,这才是企业级RAG应用该有的样子。