资讯动态

ACL 过滤检索实现:可见性模型、向量库 filter 与权限实时同步

发布时间:2026/10/18 8:03:30 来源:尧图企业网站定制
个人主页 for_ever_love__ 欢迎各位大佬莅临其他栏目: 大模型开发从0到1 其他栏目: iOS项目总结大全 其他栏目: 我想学python了 其他栏目: iOS UI 文章目录ACL 过滤检索实现可见性模型、向量库 filter 与权限实时同步一、可见性数据模型四种粒度二、检索时的过滤判断向量库 filter 怎么写2.1 简单场景tenant group 两个维度2.2 展开模式的两种表示与 trade-off2.3 检索层伪代码生产骨架三、权限变更的实时同步秒级生效路径四、ACL 过滤对召回率的影响与补偿五、无法回避的两个坑5.1 元组爆炸5.2 组继承的语义复杂六、落地检查清单七、小结ACL 过滤检索实现可见性模型、向量库 filter 与权限实时同步“文档 A 的权限从公开改成了部门可见为什么同事还能搜到”——ACL 过滤是 RAG 权限体系的最后一公里做不好就是安全事故。上一篇讲了多租户的大架构这篇钻到底层实现可见性数据模型怎么设计、向量库 filter 怎么写才又快又对、权限变更怎么做到秒级生效、以及 ACL 过滤对召回率的影响与补偿。所有细节都围绕一个目标用户永远只能检索到自己有权看的内容且这个约束不依赖业务代码自觉。一、可见性数据模型四种粒度ACL 数据模型的设计起点是回答谁能看。四种粒度覆盖 95% 场景┌────────────────────────────────────────────┐ │ 1. 公开 public —— 所有登录用户可见 │ │ 2. 租户 tenant —— 同租户可见 │ │ 3. 部门/组 group —— 指定组成员可见 │ │ 4. 个人 user —— 指定用户可见含 owner│ └────────────────────────────────────────────┘ 组合 allow / deny 列表一个文档的 ACL 记录{doc_id:doc_123,tenant:alpha,acl:{visibility:mixed,allow_groups:[grp_finance,grp_board],allow_users:[u_lisi],deny_users:[u_wangwu]},owner:u_zhangsan,updated_at:2026-10-16T09:00:00Z}两个设计决策deny 压倒 allow权限判断先查 deny 列表黑名单里的谁都看不到是安全惯例可见性预展开入库时把文档可见的最终用户集合或组集合算好存 metadata——检索时只做集合相交判断不做实时 ACL 计算。理由权限判断逻辑复杂组继承、临时授权、交叉授权每次检索现场算既慢又容易和实际不一致。展开是以空间换一致性和速度。# 入库时预展开defresolve_visibility(doc_acl,group_members_resolver):把组/个人 ACL 展开成最终用户集合usersset(doc_acl.get(allow_users,[]))forgindoc_acl.get(allow_groups,[]):users|group_members_resolver(g)users-set(doc_acl.get(deny_users,[]))returnusers# chunk metadata 里存展开结果注意集合大的情况用组 ID 折中chunk.metadata[acl_users]sorted(users)# 或 [grp_finance, grp_board] 组级集合太大怎么办全员可见的公共文档acl_users会膨胀。分级处理visibility: public的不存展开集合检索时单独走公共通道小众文档存展开用户集中间态存组 ID。没有一种表示能通吃按文档粒度选表示。二、检索时的过滤判断向量库 filter 怎么写不同向量库的 filter 能力决定实现方式以 Qdrant / Milvus 为例。2.1 简单场景tenant group 两个维度# 用户 u_lisitenantalpha属于 [grp_finance, grp_board]且有个人授权 doc_xfilt{$or:[{tenant:alpha,visibility:tenant},# 租户级{tenant:alpha,acl_groups:{$any:[grp_finance,grp_board]}},# 组级{acl_users:{$any:[u_lisi]}},# 个人级含 owner],deny_users:{$not_any:[u_lisi]},# 黑名单}Qdrant 的 filter 表达式是结构化 JSON$or/$any/$not_any组合足够表达复杂 ACL。要点acl_users字段用数组类型Qdrant payload 支持 list$any语义是交集非空——个人级过滤的核心deny_users独立作为一个must_not条件先黑后白filter 在检索时下推到向量索引层执行不是查完再过滤——下推才能保证 top_k 语义正确。2.2 展开模式的两种表示与 trade-off表示metadata检索过滤适用用户级展开acl_users: [u1, u2, ...]acl_users $any [user]文档少、受众窄部门文档组级 公共标记acl_groups visibility组相交判断受众大、组织层级深组级表示的风险用户组关系变更把人加进组后组级文档无需重写 metadata——这是它比用户级展开优越的地方用户级展开要把老用户移除、新用户加入每篇文档都要改。权限变更的传播成本组级 用户级。折中方案推荐默认组级 个人授权单独标记只有个人显式授权的文档才写acl_users。权限变更绝大多数发生在组层面个人授权是少数场景。2.3 检索层伪代码生产骨架defacl_search(query,user_ctx,top_k8):# 1. 组过滤条件用户所属组 公开/租户通道base_or[{visibility:public},{tenant:user_ctx.tenant,visibility:tenant},{tenant:user_ctx.tenant,acl_groups:{$any:user_ctx.groups}},]# 2. 个人授权通道单独授予的文档ifuser_ctx.personal_grants:# u_lisi 被单独授权的 doc_x 集合base_or.append({doc_id:{$any:user_ctx.personal_grants}})# 3. 黑名单must_not[{deny_users:{$any:[user_ctx.user_id]}}]filt{$or:base_or}returnvector_store.search(query,filterfilt,must_notmust_not,ktop_k)注意personal_grants的来源用户上下文里带来自权限中心的个人授权文档列表——权限中心同步不是向量库存。ACL 的真相在权限系统向量库只是执行过滤的末端。三、权限变更的实时同步秒级生效路径ACL 最难的运维点是权限变了检索立刻变。典型事故员工转岗原部门文档还搜得到文档紧急撤回已分享的检索还在命中。同步链路设计权限中心真相源HR 系统 / 文档系统 / 管理后台 │ ├─ 权限事件流webhook/MQdoc.acl.changed / user.group.changed │ ├─ 写入侧处理器 │ ├─ doc.acl.changed → 更新该文档所有 chunk 的 metadatafilter 条件 │ │ 组级变更 → 改 acl_groups 数组用户级变更 → 改 acl_users 数组 │ └─ user.group.changed → 只更新用户上下文缓存组关系不需要动文档 │ └─ 用户上下文档刷新周期 5 分钟或事件驱动失效三个关键设计组级变更零文档更新把人加进组只刷用户的组列表缓存——这是组级表示2.2在运维上的决定性优势。文档级 ACL 要逐 chunk 改 metadata百万 chunk 的库慢到不可用文档 ACL 变更走增量更新利用上一篇的 upsert 流水线改 metadata——不重 embedding、只改 payload几十毫秒完成撤回类紧急场景文档权限设为仅自己可见或物理删除走软删 缓存失效通道目标 30 秒生效——合规场景法院取证撤回的时效红线。校验机制权限变更事件 文档 metadata 的实际值每天跑一次对账抽样 1000 篇不一致告警。和删除残留断言一样——安全属性不能配了就当生效。四、ACL 过滤对召回率的影响与补偿这是 ACL 检索绕不开的代价必须量化认知问题向量检索的 top-k 是在全库最近邻里选的ACL 过滤在检索后剔除无权文档——过滤后可能只剩 3 个结果甚至 0 个。过滤比例越高召回崩塌越狠全库 Top-10 ──过滤保留 30%──→ 有效结果 3 个 → 答案质量下降三种补偿手段过采样检索时要top_k × (1 / 预估可见比例)比如过滤后通常剩 20%就取 top_k40 再过滤留 8。预估比例可从历史检索日志统计各租户/组的过滤留存率filter 下推如果向量库支持Qdrant/Milvus 的 filtered search 在取 KNN 时就带上过滤条件找到的本来就是过滤后的最近邻——比过采样更优雅top_k 语义保留。过采样是 filter 无法下推时的降级方案ACL 感知的召回评估评估集按用户 A 的可见集构造测该用户视角下的命中率——不是全库命中率。同一个查询公共知识充足的租户命中率高冷启动租户命中率低这是 ACL 系统的固有现象要在评估时分层看。五、无法回避的两个坑5.1 元组爆炸用户级展开存acl_users数组文档受众广时数组几千人——payload 体积膨胀检索时$any大数组匹配变慢。规则个人授权只用于小众文档广域文档永远走组级/租户级通道。5.2 组继承的语义复杂企业组的现实人属于部门 → 事业部 → 公司多级文档授权给事业部时它下面的部门自动继承吗——这取决于权限中心的语义不是 RAG 层能决定的。RAG 层要做的组集合由权限中心算好含继承展开RAG 只消费最终集合。别在检索层实现组继承逻辑——那是权限系统的职责跨系统实现必然不一致。六、落地检查清单可见性四级模型public/tenant/group/user deny 优先级metadata 表示默认组级 个人授权单独标公共文档走 visibility 标记filter 下推到向量检索层Qdrant filter 表达式filter 无法下推时过采样补偿权限变更事件流 → 文档 metadata 增量更新 用户组缓存失效组级变更零文档更新用户上下文缓存刷新 5 分钟紧急撤回通道 30 秒软删 缓存失效每日权限对账抽样 不一致告警ACL 感知的召回评估按用户视角分层评估组继承语义由权限中心实现RAG 只消费最终集合。七、小结ACL 过滤检索的五句话可见性四级模型public/tenant/group/user deny 优先deny 是安全底线组级表示为默认个人授权单独标记——组级变更只刷用户缓存不动文档这是权限实时生效的关键filter 必须下推到向量检索层Qdrant/Milvus 的结构化 filter 直接表达 ACL下推不了就过采样代价是 top_k 语义失真ACL 的真相在权限中心RAG 是过滤执行末端组继承、个人授权、deny 语义都在权限中心算好RAG 消费最终集合安全属性靠持续验证每日对账抽样、不一致即告警——ACL 配了不等于生效和删除残留断言同理。下一篇讲引用溯源怎么让生成的每句话都能点回原文出处。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑