在企业级场景中,构建基于 RAG(检索增强生成)的内部知识库,本质上是一个严苛的数据安全与高动态数据流管理问题,而不仅仅是 AI 算法问题。
在设计此类平台时,核心原则是:绝对不能依赖 LLM 的 Prompt 去做权限隔离和多租户切分,必须在数据库层实现确定性的硬隔离。
一、 系统整体架构设计
平台采用冷热分离、双轨并行 的云原生架构。上层通过企业级 API 网关提取身份,下层通过 Vector DB 的多租户特性 与 PostgreSQL 的行级安全(RLS) 共同保障硬隔离。
[ 客户端 (Web / 办公软件 / 开放 API) ]
│
▼ (携带用户 JWT / 租户 ID)
┌──────────────────────────────── AI 统一网关层 ────────────────────────────────┐
│ - 校验 JWT 令牌 - 提取 Tenant_ID / User_Roles - 动态路由至对应命名空间 │
└──────────────────────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────── 核心服务编排层 ────────────────────────────────┐
│ ┌───────────────────────┐ ┌───────────────────────┐ ┌──────────────────┐ │
│ │ 文件异步解析流 │ │ 混合检索引擎 │ │ 权限过滤器 │ │
│ │ (Chonkie 语义切片) │ │ (Dense + Sparse) │ │ (Early-Binding) │ │
│ └───────────────────────┘ └───────────────────────┘ └──────────────────┘ │
└──────────────────────────────────────┬────────────────────────────────────────┘
│
▼
┌───────────────────────────────── 数据存储层 ──────────────────────────────────┐
│ ┌───────────────────────────────────┐ ┌──────────────────────────────────┐ │
│ │ 对象存储 (MinIO / S3) │ │ 向量数据库 (Milvus / Pinecone) │ │
│ │ - 按 /tenant_id/ 物理路径隔离 │ │ - Namespace 隔离 (Pool 模式) │ │
│ └───────────────────────────────────┘ └──────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ 关系型数据库 (PostgreSQL + pgvector / pgvectorscale) │ │
│ │ - 存储文档元数据与层级 ACL 权限控制 (开启 RLS 行级安全) │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────────────┘
二、 核心模块深度工程设计
1. 多租户隔离方案(Multi-Tenancy)
综合考虑基础设施成本与安全合规,采用 Pool(共享实例,逻辑隔离)模式。
- 计算与网关层: 网关统一解析用户 JWT,解出
tenant_id。所有下层微服务间的 RPC 调用,都在 Context 中隐式透传该上下文。 - 向量存储层: 选用支持 Namespace / Partition 级别的向量数据库(如 Milvus、Pinecone)。每一个租户对应一个独立的 Namespace。查询时,数据库底层直接限定在特定的 Namespace 内存空间中检索,从物理和算法层面断绝跨租户污染的可能。
2. 权限过滤(Enterprise Permissions & ACL)
企业文档权限纷繁复杂(如:按部门、按角色、或者特定单文件授权)。这里必须采用 前置绑定(Early-Binding)过滤技术。
- 文档元数据建模: 在 PostgreSQL 中对每一个 Chunk(文本切片)记录其所属的
document_id,并关联一张权限表。 - 向量+权限复合查询: 严禁先检索出 Top-K,再回表过滤权限(这会导致权限低的用户查出 0 条记录,即 Late-Binding 缺陷)。
- RLS(行级安全)落地:
sql
-- 开启 PostgreSQL 的行级安全控制
ALTER TABLE doc_chunks ENABLE ROW LEVEL SECURITY;
-- 创建安全策略:只有当前用户的角色或部门在文档的 ACL 列表中,才允许检索对应的向量
CREATE POLICY doc_chunk_access_policy ON doc_chunks
FOR SELECT
USING (
tenant_id = current_setting('app.current_tenant_id')
AND
(is_public = true OR acl_roles && current_setting('app.current_user_roles')::text[])
);
3. 高质量文件上传与解析流(Ingestion Pipeline)
文件解析直接决定了 RAG 的上限。传统按固定字符大小(如 500 字)切片会严重割裂上下文。
用户上传文件,计算 SHA-256 哈希值。回表查询该租户下是否已存在相同文件,若存在则直接建立引用(秒传),无需重复解析。 使用高性能解析引擎将 PDF/Word 转换为标准的 Markdown 格式,精准提取文本、表格(保留 HTML Table 格式)和图片。 拒绝粗暴的固定字数切块。采用长短句结合的语义切片(Semantic Chunking),当文本意思发生突变(通过相邻句子的嵌入向量相似度判断)时,才划定切片边界,确保单块上下文完整。 将切片并行送往嵌入模型,生成密集向量(Dense Vector,如 BGE-M3)与稀疏向量(Sparse Vector,用于精确关键字匹配,如 BM25)。同时,将文件的所属权、部门 Read-ACL 写入 Metadata。
4. 实时更新与状态一致性(Real-time Updates & Lambda Pattern)
"文件刚修改,知识库要求几秒内同步反应,但大批量全量重建索引极度耗费算力。"
- 双轨写入架构(Lambda Pattern):
-
- 离线/批处理流: 每日凌晨对文档库进行离线全量压缩、HNSW 索引重构优化,提升白天的查询召回率。
- 实时变动流(Speed Layer): 当用户点击"更新/删除文档"时,触发 CDC(数据变更捕获,如 Debezium + Kafka)。
- 版本控制与标记删除:
-
- 删除: 在向量数据库中根据
document_id触发delete_by_expression(doc_id)。由于向量库的硬删除通常存在延迟,在关系型数据库中同步将该文档状态更新为status = 'DELETED'。在检索时,WHERE status != 'DELETED'作为一个强置硬过滤条件,实现秒级逻辑失效。 - 更新: 采用"先抹除旧切片,再追加新切片"的幂等设计,通过
version字段防止分布式并发导致的数据错乱。
- 删除: 在向量数据库中根据
三、 生产级 RAG 混合检索伪代码
以下展示了在编排层如何安全地组合"多租户、权限硬过滤、混合检索、Rerank(重排)"的核心逻辑:
python
from typing import List, Dict
import json
class EnterpriseRAGRetriever:
def __init__(self, vector_store, relational_db, reranker_model):
self.vector_store = vector_store
self.db = relational_db
self.reranker = reranker_model
async def secure_hybrid_search(
self,
query: str,
tenant_id: str,
user_roles: List[str],
top_k: int = 10
) -> List[Dict]:
"""
生产级安全混合检索
"""
# 1. 构建前置硬过滤条件 (Early-Binding):严格限制租户空间,并注入用户权限卡口
# 即使向量极为相似,只要不属于该租户或无此角色的权限,直接在底层被过滤
expression_filter = (
f"tenant_id == '{tenant_id}' AND "
f"(is_public == true OR acl_roles ANY IN {json.dumps(user_roles)})"
)
# 2. 触发密集向量与稀疏向量的混合检索 (Hybrid Search)
# 召回阶段有意扩大数量 (Overfetching, 取 top_k * 4),留给 Reranker 优化
raw_results = await self.vector_store.hybrid_search(
query_text=query,
namespace=tenant_id, # 租户命名空间隔离
filter_expr=expression_filter,
limit=top_k * 4,
dense_weight=0.7, # 偏向语义理解
sparse_weight=0.3 # 保留精确产品型号、工号等专有名词的命中
)
if not raw_results:
return []
# 3. 跨编码器精密重排 (Cross-Encoder Reranking)
# 消除密集向量可能带来的空间距离幻觉,用小模型对 Query-Chunk 重新深度打分
reranked_results = self.reranker.compute_score(
query=query,
passages=[chunk.text for chunk in raw_results]
)
# 4. 组装并截取最终的 Top-K 文本块返回
final_context = []
for idx in range(min(top_k, len(reranked_results))):
original_item = raw_results[reranked_results[idx].id]
final_context.append({
"text": original_item.text,
"doc_id": original_item.meta["document_id"],
"score": reranked_results[idx].score
})
return final_context
四、 平台落地避坑金句
"做企业内部知识库,'检索的精确度' 往往比大模型的 '聪明度' 重要十倍。
在实际落地中,有三个深水坑必须踩死:
- 严禁 Late-Binding: 绝对不要把向量检索和权限过滤分成两步走,更不能指望 LLM 遵从'请不要看研发部文档'的指令。所有权限必须化为布尔达式,作为 Pre-filter(前置硬过滤) 拍在向量引擎的脸上。
- 警惕文档更新时的'幽灵切片': 文件更新时如果没做彻底的局部幂等清理,向量库里就会充斥着半年前的旧规章和新制度的混合体,LLM 吃了这种'过期污染数据',神仙也调不好。
- 死磕表格解析: 企业的规章制度、报表里有大量的表格。不要把表格当纯文本切开!必须引入 Layout-Aware 引擎,把表格整块转化为包含完整语义的 Markdown Table 或 HTML 节点输入,否则 AI 只要一查数据指标就必然抓瞎。"