RAG 在企业知识库中的应用:权限隔离和搜索结果个性化
RAG 在企业知识库中的应用权限隔离和搜索结果个性化一、CEO 和实习生用同一个 AI 知识库搜索查到的东西一模一样企业知识库和公开知识库最大的区别是不是所有信息对所有人都是可见的。CEO 搜索Q3 营收目标应该看到最新的财务预测数据。普通员工搜索同样的词应该看到公司的核心价值观是客户第一。但如果权限控制没做好或者搜索结果没有根据身份做个性化信息就有可能越权泄露或不对等展示。传统的企业知识库搜索ES 关键词匹配还能靠数据库的权限字段做过滤但一旦引入 RAG向量检索 LLM 生成权限问题就变得更复杂——因为 LLM 的生成结果是新内容它可能把多条不同权限级别的文档内容糅合在一起输出。如何保证生成的答案只看得到该用户有权查看的信息是企业 RAG 的核心工程挑战。二、权限感知的企业 RAG 架构核心设计是检索前过滤 检索后校验的双重保障这个架构有两道过滤检索后过滤硬性拦截看不到的文档完全不出现在候选集里字段级脱敏软性保护能看到文档但不能看到全部字段。两道过滤的组合相比单纯依赖数据库权限更加安全。三、Python 实现带权限隔离的 RAG 检索import numpy as np from typing import List, Dict, Optional, Set, Any from dataclasses import dataclass from enum import Enum class DocPermission(Enum): 文档密级 PUBLIC public # 全员可见 INTERNAL internal # 内部员工可见 CONFIDENTIAL confidential # 特定团队可见 SECRET secret # 仅高管可见 class UserRole(Enum): 用户角色 EMPLOYEE employee MANAGER manager DIRECTOR director EXECUTIVE executive ADMIN admin dataclass class User: 用户信息 user_id: str role: UserRole department: str team: str # 显式授权的文档 ID 列表 authorized_docs: Set[str] None def __post_init__(self): if self.authorized_docs is None: self.authorized_docs set() def permission_level(self) - int: 返回数字权限等级越大权限越高 role_levels { UserRole.EMPLOYEE: 1, UserRole.MANAGER: 2, UserRole.DIRECTOR: 3, UserRole.EXECUTIVE: 4, UserRole.ADMIN: 5, } return role_levels.get(self.role, 0) dataclass class EnterpriseDocument: 企业文档 doc_id: str content: str title: str department: str # 所属部门 permission: DocPermission # 可见角色等级要求 min_role_level: int 1 # 允许访问的团队列表空 所有团队 allowed_teams: List[str] None # 敏感字段列表这些字段在低权限时需要脱敏 sensitive_fields: List[str] None embedding: Optional[np.ndarray] None class PermissionAwareRetriever: 带权限隔离的检索引擎 def __init__(self): self.documents: Dict[str, EnterpriseDocument] {} # 倒排索引: 用户ID - 可见文档ID self.permission_cache: Dict[str, Set[str]] {} def index_document(self, doc: EnterpriseDocument): 索引文档 self.documents[doc.doc_id] doc # 使权限缓存失效 self.permission_cache.clear() def get_visible_docs(self, user: User) - Set[str]: 获取用户可见的文档 ID 集合 # 缓存命中 if user.user_id in self.permission_cache: return self.permission_cache[user.user_id] visible set() user_level user.permission_level() for doc_id, doc in self.documents.items(): # 规则 1文档密级检查 if doc.permission DocPermission.PUBLIC: visible.add(doc_id) continue if doc.permission DocPermission.INTERNAL: if user_level 1: # 所有正式员工 visible.add(doc_id) continue if doc.permission DocPermission.CONFIDENTIAL: # 需要同部门 且 权限等级足够 if (user.department doc.department and user_level doc.min_role_level): visible.add(doc_id) continue if doc.permission DocPermission.SECRET: # 仅高管 或 显式授权 if (user.role UserRole.EXECUTIVE or doc_id in user.authorized_docs): visible.add(doc_id) continue # 规则 2团队白名单 if doc.allowed_teams: if user.team in doc.allowed_teams: visible.add(doc_id) self.permission_cache[user.user_id] visible return visible def search( self, query: str, query_embedding: np.ndarray, user: User, top_k: int 10, ) - List[Dict[str, Any]]: 权限感知的向量检索 # 1. 获取用户可见文档 visible_docs self.get_visible_docs(user) if not visible_docs: return [] # 2. 只在可见文档中做向量检索 candidates [] for doc_id in visible_docs: doc self.documents.get(doc_id) if doc is None or doc.embedding is None: continue similarity self._cosine_similarity( query_embedding, doc.embedding ) candidates.append((doc, similarity)) # 3. 排序取 Top-K candidates.sort(keylambda x: x[1], reverseTrue) top_docs candidates[:top_k] # 4. 字段级脱敏 results [] for doc, score in top_docs: content self._apply_field_mask(doc, user) results.append({ doc_id: doc.doc_id, title: doc.title, content: content, score: score, department: doc.department, }) return results def _apply_field_mask( self, doc: EnterpriseDocument, user: User ) - str: 对敏感字段做脱敏处理 if not doc.sensitive_fields: return doc.content user_level user.permission_level() content doc.content for field in doc.sensitive_fields: if user_level 4: # 高管以上可见原始数据 continue elif user_level 3: # 总监级区间化 import re pattern re.compile( f{field}[:]\\s*([\\d,.]) ) content pattern.sub( f{field}: [数据脱敏-区间], content ) else: # 其他级别完全脱敏 import re pattern re.compile( f{field}[:].*?(\\n|$) ) content pattern.sub( f{field}: [数据未授权]\n, content ) return content def _cosine_similarity( self, a: np.ndarray, b: np.ndarray ) - float: return float(np.dot(a, b) / ( np.linalg.norm(a) * np.linalg.norm(b) 1e-8 )) def search_with_personalization( self, query: str, query_embedding: np.ndarray, user: User, top_k: int 10, ) - List[Dict[str, Any]]: 带个性化的搜索权限 部门偏好 results self.search( query, query_embedding, user, top_k * 2 ) # 个性化 boosting同部门的文档加权 for result in results: if result[department] user.department: result[score] * 1.2 # 部门内文档提权 20% results.sort(keylambda x: x[score], reverseTrue) return results[:top_k]四、边界分析与 Trade-offs权限缓存的失效策略用户的权限、团队、授权文档可能随时变化入职、离职、调岗、项目权限变更。权限缓存需要设置合理的过期时间建议 5 分钟或者通过事件驱动用户的角色变更时主动清除该用户的缓存。检索精度 vs 安全的权衡如果用户只能看到 20% 的文档那向量检索的候选集就小了 80%。极端情况下新员工只能看公开文档检索结果可能只有寥寥几条。解决方案对于检索结果过少的情况 3 条提示用户未搜索到足够信息建议细化关键词或申请文档权限。LLM 生成内容的安全审核即使检索时做了权限过滤LLM 仍可能在生成过程中脑补出敏感信息。生成后需要做关键词匹配检测——如果输出中包含营收毛利工资等敏感词且不在引用文档中出现说明是模型幻觉应该拦截。这个审核可以用规则关键词列表不需要额外的 AI 模型。跨文档聚合的安全隐患用户可能没有权限看财务文档 A 和人事文档 B但有权限看项目文档 C。如果项目文档 C 中引用了预算 500 万这个信息来自 A用户可能通过 C 间接获取到 A 的信息。这需要文档入库时做引用链路标注检索时检测间接引用并降权。五、总结企业 RAG 的权限隔离不是加个 if 判断那么简单需要检索前后双重过滤检索阶段通过权限缓存过滤可见文档硬拦截检索后通过字段级脱敏保护敏感信息软保护。权限模型建议用角色等级 文档密级 团队归属的三维模型而不是简单的 RBAC。缓存策略是性能和安全的关键平衡点——建议 5 分钟过期 事件驱动失效的组合方式。最容易被忽略的是跨文档引用导致的信息间接泄露这在金融和法律等强合规行业是绝对的红线。