私有化RAG并不是把开源大模型下载到服务器上跑起来那么简单。很多团队在试点阶段用公共API构建检索增强生成,确实能快速看到问答效果,但一旦涉及合同、财务、研发设计文档,数据经过第三方模型服务商就等同于离开企业控制范围。真正的私有化方案需要把文档解析、嵌入生成、向量存储、检索和生成推理全部放在企业自己的网络边界内,并且为不同部门的数据设置隔离边界,否则只是模型本地化,文档安全仍然可能被一次越权检索击穿。本文拆解一套可落地的私有化RAG架构,重点讨论文档在切分、向量化、检索和生成四个环节中如何保持安全隔离。

要理解安全隔离,先要明确RAG的数据流路径。原始文档从企业文件系统进入解析服务,解析出的文本块被嵌入模型转换成向量,向量连同元数据一起写入向量数据库。用户提问时,检索服务先根据身份信息圈定可见范围,再执行相似度查询,取回候选片段后交给生成模型合成答案。这个链条上任何一个环节对外发送数据,都会破坏隔离效果。因此设计私有化RAG时,不能只看模型是否本地部署,还要逐项审查文档解析、向量化和重排阶段是否存在隐蔽的第三方调用。
私有化RAG的整体架构边界
私有化RAG的架构通常包含文档接入层、解析清洗层、嵌入服务、向量数据库、检索服务、重排服务和大模型推理服务。每个组件都应部署在企业内网或云上专属VPC中,组件之间的通信建议走内部域名和mTLS,禁止任何流量通过公网回环到第三方服务。这样即使某个内部服务被入侵,攻击者也无法直接把文档内容回传到外部地址。
如果企业已有统一身份认证系统,最好把RAG的检索入口与现有权限系统打通。文档在入库时携带知识库ID、部门ID、密级标签等元数据,检索时根据当前用户的身份先圈定可见文档范围,再执行向量相似度搜索。这样即使向量库中已经存在未授权数据,检索语句也不会触及这些记录。权限信息应通过请求头或上下文对象在服务间传递,不能依赖检索服务自行猜测用户身份。
数据流可以概括为:原始文档进入对象存储或文件系统,解析服务读取后输出纯文本和元数据,嵌入模型将文本块转换为向量,写入向量数据库时同时记录文档权限字段,检索服务接收用户问题后先进行权限过滤,再调用向量库返回候选块,重排后交给生成模型合成答案。整个链路中日志系统要记录每次检索的查询、命中文档ID、用户身份和返回片段哈希,便于事后审计。
用户问题 -> 认证网关 -> 检索服务 -> 权限过滤 -> 向量数据库 -> 候选文本块
-> 重排服务 -> 提示词拼接 -> 私有大模型 -> 答案 + 引用来源
这种架构的关键不是某一个开源组件,而是每一跳都保留权限上下文。很多安全事件恰恰发生在组件边界,例如嵌入服务直接读取全量文档库,或者向量数据库开放了无条件查询接口。因此架构评审时应该把每个数据访问点都列出来,确认它是否具备身份传递能力。对于跨部门共享的知识库,还需要在检索侧支持多级权限合并,避免因为权限模型过于简单而影响正常业务协同。
文档解析与向量化隔离设计
文档解析阶段最容易引入外部依赖。例如PDF解析、OCR识别、Office格式转换,如果直接调用在线API,原始文档就会离开内网。私有化部署应选择可以本地运行的解析引擎,如PyMuPDF处理PDF文本层、Tesseract处理扫描件OCR、LibreOffice处理Word和Excel转换。解析出的文本在进入嵌入模型前,建议先做一次敏感信息扫描,将身份证号、手机号、银行卡号等按规则脱敏,避免这些内容以明文进入向量库。
嵌入模型的选型也要区分场景。使用公共嵌入API虽然简单,但文档内容会发送到外部服务器。私有化方案可以部署BGE、M3E、GTE等开源嵌入模型,配合Transformers或ONNX Runtime在GPU或CPU上推理。文本块的切分策略会直接影响检索效果,常见做法是按标题层级切分,同时保留上下文重叠。下面是一个基于LangChain的本地切分与嵌入示例,确保所有计算都在内网完成。
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceBgeEmbeddings
from langchain_community.vectorstores import Milvus
# 本地嵌入模型,不访问外部API
embedding_model = HuggingFaceBgeEmbeddings(
model_name="BAAI/bge-large-zh-v1.5",
model_kwargs={"device": "cuda"},
encode_kwargs={"normalize_embeddings": True}
)
splitter = RecursiveCharacterTextSplitter(
chunk_size=512,
chunk_overlap=64,
separators=["\n\n", "\n", "。", ";", " "]
)
docs = splitter.split_documents(loaded_documents)
# 写入向量库时携带权限元数据
for doc in docs:
doc.metadata["dept_id"] = current_dept_id
doc.metadata["security_level"] = "internal"
vectorstore = Milvus.from_documents(
docs,
embedding_model,
collection_name="rag_documents",
connection_args={"host": "192.168.10.20", "port": "19530"}
)
上面的代码中,嵌入模型权重部署在内网,连接参数指向内网Milvus实例,文档元数据同时写入部门ID和密级。这样后续检索阶段才能以元数据为条件进行过滤。如果向量库不支持元数据过滤,就要考虑在应用层维护一份文档ID到权限标签的映射,但这种方式在高并发下容易成为瓶颈,建议选择原生支持标量过滤的向量数据库。
另外,针对扫描件和图片型文档,OCR结果往往包含噪声,需要额外的版面分析和表格还原。私有化工具可以组合PaddleOCR和Unstructured,重点是不让图片传输到外部OCR服务。解析后的文本建议保留原始文件哈希和页码,便于溯源。某些企业还会在文档入库前做一次自动分类,比如将合同类、研发类、制度类文档分别写入不同的知识库集合,进一步缩小检索范围。
检索阶段权限过滤与重排
向量检索本身只看语义相似度,不会理解用户是否有权查看某一段内容。如果直接把用户问题交给向量库做TopK查询,可能返回未授权文档片段。正确的做法是在查询向量前,先从身份系统获取当前用户的可见范围,再转换成向量库支持的过滤表达式。这个步骤必须放在检索请求发出之前,否则向量库已经返回了越权内容,后续再拦截就变成了被动补救。
假设企业使用部门维度做隔离,用户同时属于多个部门,过滤条件可以是dept_id in ["dept_a","dept_b"]。如果密级要求更高,可以再加security_level >= 3这样的条件。下面的示例展示如何根据用户身份构造带过滤条件的检索请求。
def build_filter(user_info):
allowed_depts = user_info["departments"]
min_level = user_info["min_security_level"]
filter_expr = (
f"dept_id in {allowed_depts} "
f"and security_level >= {min_level}"
)
return filter_expr
results = vectorstore.similarity_search_with_score(
query=query_vector,
k=10,
expr=build_filter(current_user)
)
# 只把通过权限校验的片段送入重排
authorized_chunks = [r for r in results if r.metadata["dept_id"] in current_user["departments"]]
这段代码展示的是双重校验:向量库先按过滤条件缩小范围,应用层再根据元数据做一次兜底判断。为什么要做两次?因为部分向量数据库的过滤表达式可能存在语法差异,或者历史数据写入时漏标了权限字段,应用层兜底可以降低越权风险。重排阶段最好也在本地完成,可以使用BGE Reranker或CrossEncoder模型对候选片段重新打分,避免把候选内容发送到外部排序服务。
审计日志同样不可忽略。每一次检索都应记录用户ID、查询原文、命中片段哈希、最终引用片段哈希和生成答案的时间戳。日志可以写入独立的审计库,避免被普通运维人员修改。对于高密级文档,还可以设置匹配阈值,低于阈值不允许生成答案,防止模型用幻觉补充未检索到的内容。审计记录在保留周期上建议不少于六个月,涉密场景可延长至一年以上。
部署形态与性能调优
私有化RAG的部署规模取决于文档量和并发量。对于百GB级文档库,向量数据库建议采用分布式部署,例如Milvus、Qdrant或Elasticsearch的向量插件。嵌入模型和重排模型可以独立成服务,通过gRPC或HTTP内部接口调用,避免和生成模型争抢GPU资源。生成模型则根据企业硬件条件选择7B到70B的开源模型,并用vLLM或TensorRT-LLM提升吞吐。
资源隔离也要按业务线划分。可以为不同部门创建独立的向量集合,或者使用同一个集合加元数据过滤。独立集合的隔离性更强,缺点是跨部门知识无法共享,维护成本高;元数据过滤更灵活,但对开发和审计要求更高。中等规模企业通常采用按知识库建集合、按部门加过滤条件的混合策略。
# 内网Docker Compose编排示例
services:
embedding:
image: registry.internal/rag-embedding:1.4
environment:
- MODEL_PATH=/models/bge-large-zh
volumes:
- /data/models:/models
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
vector-db:
image: milvusdb/milvus:v2.4.0
ports:
- "19530:19530"
- "9091:9091"
volumes:
- /data/milvus:/var/lib/milvus
llm:
image: registry.internal/rag-llm:1.4
command: ["--model", "/models/qwen2-7b-instruct", "--port", "8000"]
volumes:
- /data/models:/models
性能调优时,优先关注嵌入模型的批量大小和向量维度。嵌入服务可以设置动态批处理,将多个文档块合并成一次前向计算,提高GPU利用率。向量库索引类型选择HNSW或IVF_PQ需要根据召回率与内存占用做测试。对于中文长文档,切分长度建议控制在256到512词之间,重叠窗口保留10%到15%。如果检索延迟过高,可以在生成前增加一层粗排,只把前20条送入重排模型。
最后还要考虑模型版本管理和数据更新流程。文档入库后如果原始文件被替换,需要同步更新向量记录并保留旧版本引用,避免问答系统引用已作废内容。可以在元数据中加入版本号和生效时间,检索时只返回当前生效版本。整个私有化RAG系统上线后,应定期进行红队测试,尝试用提示词诱导模型越过权限边界,并根据测试结果收紧系统提示词和权限过滤规则。只有把安全验证纳入发布流程,私有化RAG才能从演示项目变成真正可用的企业级文档隔离方案。

