對應原題(Common ‧ RAG and Retrieval)

  • Design permission-aware retrieval: users must never see content they can’t access in the source system.
    — Asked at: Microsoft, Databricks, Glean, Palantir
  • How do you attribute every claim in a generated answer to a specific retrieved span?
    — Asked at: Perplexity, Harvey, Abridge

🎯 什麼情境該想到我

當你「要把 RAG 放進企業內部、而資料本來就有權限分層」的時候。
★ 題目寫的是 “must never” —— 這是安全需求不是功能需求,設計原則必須是 fail-closed(出錯時拒絕,不是放行)。

⚙️ Part 1:權限感知檢索

四種架構

做法機制評價
後過濾先 ANN 取 top-k,再逐筆查權限濾掉❌ 不可用。top-50 濾完可能剩 2 筆;而且權限高的使用者和低的使用者看到的「前 50 名」不同,結果品質天差地別
前過濾 / filtered ANN把 ACL 當 metadata,在圖/cell 走訪時就套用✅ 主流。但要處理高選擇性過濾下 HNSW 的圖斷連(見 向量索引 ANN)
分區索引每個租戶/權限群組一份獨立索引✅ 隔離最強。租戶數少時最佳;租戶多會爆炸
混合粗粒度分區(租戶)+ 細粒度過濾(文件 ACL)✅ 企業級的實務答案

★ ACL 從哪來:兩難

權限真正的來源是來源系統(Google Drive、SharePoint、Jira、Confluence、S3…),而且隨時在變。

A. 查詢時即時驗證B. 把 ACL 快取進索引
正確性✅ 永遠最新⚠️ 會過期
延遲❌ 每次查詢打來源系統✅ 快
來源系統負載❌ 會被打爆✅ 無
可行性小規模可以大規模只能這樣

實務做法(Glean 那類產品的形狀):

索引時:每個 chunk 存下「允許的 principal 集合」
   allowed: [user:alice, group:eng, group:eng-backend]

查詢時:把使用者的身分展開成集合(含巢狀群組)
   alice → {user:alice, group:eng, group:eng-backend, group:all-staff}

檢索條件:兩個集合有交集

這帶來三個必須解的子問題:

  1. 群組成員關係也要同步,而且巢狀群組要遞迴展開(group:eng 包含 group:eng-backend)
  2. 展開後的集合可能很大(幾百個群組),要有高效的集合交集索引
  3. 拒絕型 ACL(deny 優先於 allow) 不能用單純的集合交集表達,要額外處理

★ 撤銷延遲:這題真正的核心風險

Alice 今天被移出 group:finance,她多久之後才真的搜不到財務文件?

這個數字必須被明確定義並承諾。可選的設計:

方案延遲代價
定期全量同步(每小時)最差 1 小時便宜,但一小時的洩漏窗口通常不可接受
事件驅動增量同步(webhook)秒到分鐘需要來源系統支援、要處理漏事件
高敏感內容查詢時即時驗證~0只對標記為 sensitive 的子集做,成本可控
分層:一般內容用快取、敏感內容即時驗證—✅ 實務答案

fail-closed 原則:ACL 查不到、同步失敗、來源系統逾時 → 一律視為無權限,不是放行。

★ 其他洩漏管道(這裡是高分點)

只擋檢索是不夠的。至少還有六條路:

管道風險對策
生成端洩漏即使只餵有權文件,模型仍可能從預訓練知識或其他使用者的對話中洩漏輸出過濾;對話隔離
★ 快取洩漏跨使用者共用的 prompt 前綴快取或答案快取會跨越權限邊界快取 key 必須包含權限上下文(見 前綴快取 Prompt Caching)
★ 向量本身embedding 可被反演出近似原文(embedding inversion)。向量庫就是機密資料,不是「已經匿名化的數字」向量庫比照原文做存取控制與加密
存在性洩漏「查無相關文件」vs「有 3 筆但你沒權限」——差別本身就是資訊兩種情況回覆一致
日誌與 trace可觀測性系統常常沒有做權限隔離trace 內容比照原文分級
錯誤訊息/計數回傳「共 17 筆結果」但只顯示 3 筆不回傳未過濾的計數

測試(必要,不是加分項)

建立 negative test set:「使用者 X 絕對不該在任何查詢下看到文件 Y」。

  • 每次部署都跑,當作 CI gate
  • 測試集必須包含真實的過濾條件——沒有過濾的測試完全測不出 HNSW 圖斷連與後過濾的問題
  • 對抗性測試:用同義詞、繞道問法、多輪誘導試圖撈出無權內容

⚙️ Part 2:引用歸因

三種強度

強度做法保證
弱:生成時標記prompt 要求模型輸出 [1] [2]❌ 模型會標錯編號、會編造不存在的引用。最便宜也最不可信
中:事後比對生成完,把每句話用 NLI 或相似度對回來源⚠️ 可加在任何系統上,但可能對不上;且 NLI 模型自己也會錯
強:解碼層保證由 API/解碼機制保證引用的 span 確實存在於提供的文件中(如 Anthropic 的 Citations)✅ span 的真實性有保證(但「是否支持」仍需另外驗,見下)

★ 無論哪種,都必須做「可驗證性檢查」

def verify_citation(answer_claim, cited_doc_id, start, end, docs):
    doc = docs[cited_doc_id]
    span = doc.text[start:end]
    # ① span 真的存在嗎(偏移量對不對)
    if not span or span not in doc.text:
        return "INVALID_SPAN"
    # ② 這段話真的支持這個主張嗎(NLI entailment)
    if nli_entailment(premise=span, hypothesis=answer_claim) < THRESHOLD:
        return "UNSUPPORTED"
    return "OK"

★ 「有引用」≠「被支持」,這是這題最重要的一句話。
模型很可能引用了一段真實存在但根本不支持該主張的文字——看起來有憑有據,實際上是幻覺。
要量的是 groundedness(可支持性),不是「有沒有標編號」。

粒度階梯

文件級      → 「答案來自《API 指南》」        幾乎沒有驗證價值
段落級      → 「來自第 3 節」                  可用
句子級      → 「來自這一句」                    好
★ span 級  → 「來自第 412–487 個字元」        可點擊跳轉、可高亮、可驗證

Harvey(法律)、Abridge(醫療)這類高風險領域的要求是 span 級——律師要能點開看到合約原文那一行,醫師要能看到病歷原文。「大概在這份文件裡」在這些領域等於沒有引用。

歸因失敗時要 abstain

設計原則:無法歸因的主張,就不要說。

生成 → 逐主張驗證
        ├─ 全部通過        → 輸出
        ├─ 部分不通過      → 移除該句 或 標記為「未經來源支持」
        └─ 關鍵主張不通過  → ★ 改回「我在提供的資料中找不到答案」

這需要產品端配合:「找不到」必須是一個可接受的答案。如果 KPI 是「回答率」,這個設計就會被繞過。

⚠️ 注意 / 什麼時候不適用

  • 權限不能事後補。索引 schema 沒留 ACL 欄位,之後要加就得重建全部索引。第一天就要設計進去。
  • 別用「反正模型不會說出來」當防線。有權限才餵給模型,而不是餵了再叫它別說。
  • 多租戶的前綴快取是最容易被忽略的洩漏點,而且是靜默的。
  • 引用的偏移量會因為正規化而錯位(去空白、Unicode 正規化、OCR 修正)。要保留原文與處理後文字的對應。
  • NLI 驗證有成本與誤判。閾值太嚴會把正確答案也濾掉,要用自己的資料校準。
  • 摘要類任務天然難以逐句歸因(合成多段資訊的句子)。這類任務要改用「整體 groundedness 評分」而非逐 span 歸因。
  • 引用會被使用者當成背書。介面上要讓引用真的可點、可驗證——不可點的引用會製造虛假的信任感,比沒有引用更危險。

🧪 我實際套用的紀錄

  • 2026-09-20:從面試題庫展開。待辦:任何企業 RAG 的第一天就把 ACL 欄位設進索引 schema。

🔗 相關