部署与推理

多租户隔离:演示之后才到达的那个需求

每个多租户 RAG 最终都会发现同一件事:检索之后过滤结果不是隔离。过滤必须约束搜索本身,而不是裁剪它的输出。

这个模式

演示跑在一个客户的文档上,一切正常。然后第二个客户接入,这时有人问了一个本该最先问的问题:租户 A 的用户,有没有可能看到属于租户 B 的片段?

此时最常见的答案是「在检索结果进入提示词之前加一层过滤」。它在测试里看起来是对的,结构上却是错的。

为什么后过滤不是隔离

近似最近邻搜索返回的是最相近的 k 个向量。如果你随后删掉用户无权看的那几个,剩下的就少于 k 个——而按定义,被你删掉的恰恰是语义上最相近的一批。在低权限查询下,检索器会静默退化到最差情况,答案质量变成了「用户被允许看多少」的函数。这个性质上线之后非常糟糕。

更糟的是,被删掉的内容依然跨越了信任边界:它被取出来、打过分、保存在内存里,而同一个进程稍后要渲染提示词。即使用户从未看到它,安全评审也有充分理由反对。

该怎么做

约束搜索本身。把权限谓词下推进向量库的过滤层,让无权访问的片段根本不成为候选,同时保持 k 的真实含义:你拿到的 k 条是用户真正有权看的 k 条。这正是那些为这类场景设计的存储把 payload 过滤做进内核、而不是外挂一层的原因。

然后决定谓词从哪来。每次查询都解析用户完整的文档 ACL 太贵,所以多数系统预先算出每个用户的组或标签集合,再按它过滤。取舍是时效性:一次权限撤销要到下次刷新才生效。把这个窗口明确写下来,并且短到你能为之辩护。

评审会问的三件事

第一,索引损坏或局部重建时隔离是否依然成立——要 fail closed,不要 fail open。第二,每个答案「检索到了什么」的审计轨迹,而不只是「生成了什么」。第三,按租户加密,或者一份说明为什么逻辑隔离已经足够的论证。这三份答案准备好,评审就从几周缩短到一次会议。

可以立刻做的事

  • 过滤检索结果不是隔离——要约束搜索,不要裁剪输出
  • 后过滤会让答案质量变成「用户被允许看多少」的函数
  • 预先计算每个用户的权限标签,并明确且可辩护地说明时效窗口
  • 准备好三个答案:fail-closed、检索审计轨迹、按租户加密