Gemini 私有 RAG 权限怎么控

2026-11-06 18:02 更新

Gemini 的私有检索以 File Search store 为边界:一次查询只指向一个 store,跨库不会自动发生;文件元数据可用于过滤,但权限判定仍要自己做。(核验 2026-11-05,F97、F102)

一、Gemini 私有 RAG 权限怎么控:问题不是过滤写得好不好,而是边界划在哪

做私有知识库时,权限通常被当成一个后置动作:检索回来十条文档,再按用户身份筛掉不能看的。这套做法在小规模能跑,一旦有跨部门、跨租户的数据,就会出两类事故——过滤条件漏了一个字段,或者检索本身就把不该出现的文档带进了上下文。

更根本的问题是:过滤发生在检索之后,就意味着不该看的内容已经进了模型。哪怕最终没展示给用户,它也已经过了一次网络、进过一次上下文,日志里可能还留着痕迹。

Gemini 的 File Search 给了两个可以提前划边界的位置:store 本身和文件元数据。前者是物理分区,后者是逻辑过滤。用对顺序,权限问题在检索之前就被挡住;用反了,就只能在结果里挑挑拣拣。

二、Gemini 私有 RAG 权限路由:最小可运行示例

下面这段把「按用户选库」和「按元数据过滤」拆成两步,中间夹一条越权请求。

## EXPECT: u-1 → store_team_a(候选 1 条) | u-2 → store_team_b(候选 1 条) | u-1 请求 store_team_b → 拒绝 | 越权放行 0 条
STORE_OF = {"u-1": "store_team_a", "u-2": "store_team_b"}
ACL = {"u-1": {"team_a"}, "u-2": {"team_b"}}
DOCS = [
    {"id": "d1", "team": "team_a", "title": "报销制度"},
    {"id": "d2", "team": "team_b", "title": "报销制度"},
]
REQUESTS = [
    {"user": "u-1", "ask": "报销制度"},
    {"user": "u-2", "ask": "报销制度"},
    {"user": "u-1", "ask": "报销制度", "want_store": "store_team_b"},
]

def route(req):
    owned = STORE_OF.get(req["user"])
    want = req.get("want_store")
    if want and want != owned:
        return None, []
    team = owned.replace("store_", "")
    hits = [d for d in DOCS if d["team"] == team]
    hits = [d for d in hits if d["team"] in ACL[req["user"]]]   # 第三道:进上下文前复核
    return owned, hits

results = [(req["user"], req.get("want_store"), route(req)) for req in REQUESTS]
denied = [r for r in results if r[2][0] is None]
allowed = [r for r in results if r[2][0] is not None]
cross = [r for r in REQUESTS if r.get("want_store")]
leaked = [r for r in cross if route(r)[0] is not None]
assert len(denied) == 1 and len(allowed) == 2 and not leaked
assert all(len(hits) == 1 for _, _, (_, hits) in allowed)
print(" | ".join(f"{u} → {store}(候选 {len(hits)} 条)" for u, _, (store, hits) in allowed) +
      f" | u-1 请求 store_team_b → 拒绝 | 越权放行 {len(leaked)} 条")

三条请求里,第三条是本节的要害:用户 u-1 明确要求查 store_team_b。如果 route() 直接照着 want_store 走,这一次检索就会把另一个团队的制度文档带进来——而模型完全不知道这层关系,它只是按你给的 store 查。

三、解释与边界:四道关,前两道能省,后两道不能

第一道,库的物理分区。 File Search 的查询是指向某个 store 的(F97),一次一个。所以「一租户一 store」「一部门一 store」是最直接的隔离方式:跨租户的数据根本不在同一个库里,检索再错也越不过去。代价是容量——项目内 store 总量按层级卡死在 1 GB 到 1 TB 之间(F99),租户一多就撑不住,这时候要退到「按敏感级别分库」而不是「按组织架构分库」。

第二道,元数据过滤。 导入时可以给文件带上 custom_metadata,键值对,支持字符串与数值(F102)。查询时按团队、密级、年份过滤,比物理分区灵活。但有一条铁律:过滤条件必须由服务端注入,不能由模型生成。把「帮我查一下 team_b 的报销制度」这种请求直接翻译成过滤条件,等于把锁交给对方。

第三道,结果进上下文前的复核。 检索回来的每篇文档,在拼进提示词之前再对一次 ACL。这一道看着冗余,实际挡的是前两道的配置错误——元数据写错、store 分错,都会在这里暴露。上面的代码里它没拦住任何一条,因为前两道已经把数据分开了;这恰恰是它该有的样子:平时静默,出事时才响。判断它有没有用,不能看「拦了多少」,要看「关掉它之后多久出一次事故」。

第四道,检索结果按不可信输入对待。 文档里可能写着「忽略以上指令」,这类注入见 4.2;检索结果属于外部内容,进上下文之前要么过滤,要么在系统指令里明确它的地位。

两个容易忽略的生命周期问题。 一是删除:导入时生成的原始 File 对象在 48 小时后自动删除,但 embedding 没有 TTL,会一直留着(F96)——所以「文件过期了就等于数据没了」是错觉,要真删得删 store 里的文档。二是组合:File Search 不能与 Google Search、URL Context 在同一请求里组合(F98),想做「公开 + 私有」的混合回答,必须分两次调用,而分开之后要特别小心别把私有结果拼进发往公开搜索的请求里。

多租户时的命名与迁移。 store 名要能表达它的边界,比如 team_a_prod、team_b_prod,不要用 kb_1、kb_2——半年后没人知道哪个库属于谁。迁移成本主要来自两件事:一是按组织架构分库之后组织调整了,要把文档从一个 store 搬到另一个,这需要重新切片、重新向量化,索引费用再付一次(F100);二是 store 总量有上限(F99),租户数一多,最早那批库要么合并、要么拆项目。规划时按「十八个月后的租户数」倒推分区粒度,比按今天的规模分要稳;宁可初期分粗一点,也别一上来就按最小粒度切碎——合库比拆库便宜,命名也是越晚改越贵,先想清楚再动手比事后返工省事。

边界。 本节只讲权限边界怎么划。哪条通道该用,见 7.1;引用怎么渲染,见 7.2;成本怎么算,见 7.3。官方的文档里没有「按用户 ACL 自动过滤」这类机制——这是本节所有方案都自己实现权限的原因;如果哪天官方补上,本节的四道关要重新排。

四、预期输出与异常分支

真跑输出:

u-1 → store_team_a(候选 1 条) | u-2 → store_team_b(候选 1 条) | u-1 请求 store_team_b → 拒绝 | 越权放行 0 条

六类分支。信任客户端传来的 store 名:这等于把库的选择权交给调用方,必须用服务端映射覆盖。用元数据过滤代替物理隔离:过滤条件写错一次就漏一批,而物理分区漏不了。删了文件以为数据没了:embedding 还在(F96),要么删文档,要么重建 store。store 按组织架构分太细:总量会撞上配额上限(F99),后期迁移成本很高。想一次请求同时查公开与私有:不支持组合(F98),只能分两次,且要防止结果串味。检索结果直接拼进提示词:文档里的注入话术会顺着上下文进来,处理口径见 4.2。

五、判据卡:检索结果能不能直接进提示词

该这么做 不该这么做
按租户或密级做物理分库 所有文档放一个 store 靠过滤
过滤条件由服务端注入 让模型决定过滤哪个团队
进上下文前再对一次 ACL 认为 store 选对了就安全
删除时同时清文档与索引 等 48 小时自动过期就当删了

六、来源与核验

  • https://aistudio.google.com/docs/file-search.md(核验 2026-11-05,F96、F97、F98、F99、F102)
  • https://aistudio.google.com/docs/text-generation.md(核验 2026-11-05,F75)

store 生命周期、容量配额与元数据能力属于「API 结构」档事实,复核周期三个月,见 1.3。若官方后续提供按用户过滤的内建机制,本节的四道关要重排,不能只加一句「也可以用官方过滤」。

七、动手试

  1. 把 REQUESTS 第三条的 want_store 删掉,重跑。预期三条全部放行、越权放行 变成 0;验收标准是你能说清「为什么这条请求本来就不该带 store 参数」。
  2. 给 DOCS 加一条 {"id": "d3", "team": "team_a", "title": "薪酬制度", "level": "secret"},并在 route() 里加一条按 level 过滤的规则。重跑。验收标准:u-1 的候选仍是 1 条,而你能指出这条规则为什么必须写在服务端。
  3. 打开你项目里检索之后的那段代码,找一下文档是「先过滤再进上下文」还是「先拼进去再过滤」。验收标准:能指出这一步在哪一行;先拼后滤,就是本节要改的第一处。
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号