對應原題(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}
檢索條件:兩個集合有交集
這帶來三個必須解的子問題:
- 群組成員關係也要同步,而且巢狀群組要遞迴展開(group:eng 包含 group:eng-backend)
- 展開後的集合可能很大(幾百個群組),要有高效的集合交集索引
- 拒絕型 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。
🔗 相關
- 向量索引 ANN —— filtered ANN 的圖斷連問題
- 切塊與文件解析 —— ACL 標籤要跟著 chunk 一起存
- 前綴快取 Prompt Caching —— 跨使用者的快取洩漏
- RAG 評估與營運 —— groundedness 怎麼量
- 工具-LLM安全防護 —— 決策層:整體攻擊面
- 工具-資料加密與密鑰管理 —— 向量庫也是機密資料