iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0
AI Engineering

不是模型太慢,是你沒算過這筆帳:地端 LLM 工程實戰 30 天系列 第 27

Day 27 - 企業知識庫:更正只有 2 段,舊說法還有 12 段

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20260907/201835500gEnHwAr6o.png
昨天那四個「資料源」是我拿自己的文章切出來的。拆開 DB、API 與 legacy 之前,有個更基本的問題:這些片段,還是對的嗎?

我拿同一份語料問了一題:「T2 這台機器有幾張顯卡?」

Day 12 我更正過:T2 是單張 RTX 4070 Ti 12GB,不是雙卡 24GB,並宣告前面提到「我的雙卡 24GB」的每一處一併更正。

但沒有一個字被改回去。 已發佈的內容凍結,勘誤只能寫在後面的篇章——Day 01 那張機器表到今天還印著「2× RTX 4070 Ti」。同一件事,索引裡 12 段講舊說法、2 段是更正。

https://ithelp.ithome.com.tw/upload/images/20260907/20183550A295FTtlXQ.png
圖 1:同一件事換四種問法,取前五筆時有兩種只拿到舊說法。

檢索器沒有「哪一份是現行版本」這個欄位

四種問法的更正都進得了 top-10。但 k 收到 5,四種都只看得到其中一邊,而其中兩種看到的是舊說法。檢索器沒有壞,新舊說法都在索引裡——它只是沒有版本這個概念,也不知道該把哪一邊放前面。

而數量會影響結果——是進到 context 裡的數量。2026 年一份研究把同一段低可信度內容貼進 context 兩次(不換來源、不加資訊),13 顆開放權重模型原本偏好政府與報社的傾向全都翻了:差別不在有幾個來源同意,在那段話出現幾次。

索引裡的 12 比 2 不會原樣搬進 context。數量可能影響取回結果,但本次沒有單獨量過重複數量的影響。

這裡量的是檢索名次,尚未量模型答對率——那是明天的事。

我加了時間衰減,把更正推得更遠

最直覺的修法:分數乘上時間衰減。我掃了六個半衰期。

https://ithelp.ithome.com.tw/upload/images/20260907/20183550gyw48ScK8U.png
圖 2:六個半衰期、四題,24 格裡只有一格比不衰減好。

半衰期 6 天時,更正從第 2 名掉到第 180 名。衰減獎勵的是「最近寫的」,不是「最新的版本」——更正住在第 12 天,而第 13 到 24 天有兩百多段跟這題無關的內容,被扣得比它少。

換成硬過濾(只留第 12 天之後)就有效了,更正排到第 1、2、6、2 名——代價是語料少了一半。

一、時間戳寫進 metadata,不會自己去改分數。 我把 Qdrant 裡那批 payload 的日期整批往後推一百年、向量一個字不動,分數逐位相同。ANN 算的就是向量距離,metadata 的數值欄位一格都不進分數。唯一的例外是把 metadata 一起餵去 embedding(LlamaIndex 預設就是)——那改的是被編碼的文字,不是排序規則。

二、先取候選再套衰減,時間權重的射程就是候選池。 我起了一台 Elasticsearch 9.2:一份現行版的相似度刻意壓到約第 500 名,同一條 gauss 衰減,k=100 撈不到它,k=600 才撈得到。ES 的 profile 把話講完了——knnfunction_score 之前已被改寫成一份長度 k 的清單,清單外的文件不存在,乘什麼都沒用

⚠️ 這是「先用 ANN 取候選、再套時間衰減」這條路的性質,不是產品做不到:改用 script_score 逐筆算向量相似度,一樣結合得了時間衰減,代價是官方那句「all matched documents are linearly scanned」。

時間權重能調整排序,卻不會自行辨認現行版本;版本與取代關係仍得明確記錄,然後拿它當過濾條件。

落到這份語料就是:Day 01 那張表不必刪,它其他內容還有效——但顯卡那一格要標上「已被 Day 12 取代」,Day 12 那段要標上它取代了誰。查現況只取沒被取代的,查歷史才放行舊版。

**發布日期、生效日期、取代關係是三個欄位,不是一個時間戳。**上面那條 day ≥ 12 只是示範——它的門檻來自我已經知道答案,不是來自資料。

「那我重建索引」——你以為你重建了

Day 22 立過一條規矩:L1 Parsing 到 L3 Embedding 改一項就要重建索引。那就照做,換一顆 embedding 重跑。

https://ithelp.ithome.com.tw/upload/images/20260907/20183550xRDo6Fhjzn.png
圖 3:三個出口,三種要自己動手驗的東西。

第一趟報 skipped 10,新模型的 embedding 函式被呼叫 0 次,向量與舊模型逐位相同。文件的 key 是內容與 metadata 各自 hash 再 hash 一次(langchain_core/indexing/api.py),沒有 embedding 版本這一格

解法寫在官方 docstring 裡,只是寫成提示不是警告:force_update。但它只補得了「向量沒重算」那一格——維度也變了的話,它是引信不是解法:那個例外要等到你加上它才炸出來。

同一台狀態機還有兩個出口。cleanup="full" 配上一個四個來源只抓到兩個的 loader,沒抓到的會被當成「已刪除」清掉;換成 scoped_full 刪 0 份——但它保護的只是這一趟完全沒出現的來源。把失效換成「某個來源出現了、只回了三段裡的一段」,scoped_fullfull 刪得一樣多:它分得出誰沒來,分不出誰來得不完整。

而 sha1 會跳一句碰撞警告叫你換強一點的演算法;照著換成 sha256,cleanup=None 那一趟索引就從 10 份變 20 份,其他三種不翻倍但整批砍掉重寫一次。

這不是哪一套框架的 bug。LlamaIndex 的 node hash 同樣不含 embedding;Azure 的 indexer 靠來源時間戳判斷改動,於是在 ADLS Gen2 上,單靠改目錄名不會更新底下 blob 的時間戳,也就不會觸發重新索引。只要拿一個代理訊號(內容指紋、來源時間戳)判斷要不要重算,訊號沒涵蓋的東西就是看不見。

而這條路跟上一節的結論打架:版本或生效日期寫進 metadata,那份 metadata 就會進 hash。 在本次的預設 hash 流程下,只加一個欄位,key 就從 fa8442ea… 變成 2dfa535b…,record manager 從此認為那些文件都是新的。

⚠️ 但這不代表你被綁死:source_id_key 可以指向既有欄位,不一定需要修改 metadata。

所以哪些東西不該進這台狀態機

前面三節的麻煩只發生在「把資料複製一份進索引」那一格。企業資料至少四種形狀,兩種不必進去:

資料形狀 進索引的是什麼 權限從哪來 變了怎麼知道
非結構文件wiki、SOP、規章 全文;表格自成 chunk ACL 跟著文件進索引 你自己的狀態機(上一節)
結構化 DBwarehouse schema、表描述必要時另建值索引 下推原生授權引擎 數值現查;schema 與值索引另行同步
即時系統庫存、額度、產線 不進,包成 tool 現查 呼叫端帶使用者身分 現查,不必知道
Legacy主機、老 DB 看情況 多半只剩粗粒度群組 有可用日誌走 CDC;否則定期匯出、比對快照

第一列還了 Day 22 欠的表格那筆,正解很土:Uber 在政策問答上試遍幾套 PDF loader 都找不到通用解,最後把來源整批搬到 Google Docs、自己寫 loader——至少在那個 case,決定表格切不切得完整的是來源格式,不是 parser。

第二列那句「必要時」不能省:問題帶到產品名稱時(「冰茶賣多少」——表裡寫的是 Ice Tea 還是 spiced tea?),還得把使用者的叫法對上資料庫裡的值。若走「另外建一份值索引」那條,那份複本就要同步——新鮮度又被請回來了。

第三列的邊界 Microsoft 畫成了產品:federated connector 走 MCP、查詢時才抓、唯讀,送審時每個 tool 都要掛 readOnlyHint。我原本照它 overview 的比較表寫「不支援自訂」,結果那張表到今天還寫著 No,隔壁的 how-to 頁八月中就在教你怎麼建——連微軟自己的文件都有一格沒更正。

至於建圖,判準是來源本身半結構化、而且問題真的要多跳。⚠️ 沒有任何一家寫過「什麼時候不該建圖」,這條是我歸納的,請當假設拿去驗;微軟把 GraphRAG 轉成維護模式與公開 benchmark 的分界,都放在文末。

權限:兩種做法,以及 post-filter 會餓死 top-k

版本更新得了,權限也得跟著更新——同樣是「來源端改了,不代表索引端已經生效」。

在 prompt 裡寫「這位使用者是 A 部門的,不要回答 B 部門資料」不是權限控制,是禮貌請求。這裡比較兩種常見做法:文件側複製 ACL 進索引,檢索階段就濾掉;DB 側下推原生授權引擎,權限只有一份、不必同步,代價是資料得在受治理的倉庫裡。

不過這一派也有自己的坑,而且官方自己寫了範例:欄位與過濾函式的型別不符時,關掉 ANSI mode 會讓轉型失敗的值靜靜變成 NULL、不報錯;如果那個條件恰好放行 NULL,不該顯示的資料就出去了。

複製那一派的代價,Microsoft 在會進索引的那種 connector 上寫得很清楚:權限改了「可能要 24 小時才反映」,而且只在全量爬取時更新

fail 的方向也不是天生就對。Kendra 的規則是「沒有 ACL 的文件視為 public」,2026 年的接班人 Bedrock Managed Knowledge Base 反過來寫「缺少權限資訊視為受限」——同一朵雲、同一問題、相反的預設值(後者只對啟用了 ACL 的資料源成立)。兩代倒是講了同一句話:這是過濾,不是身分驗證。

然後是撈多深。權限過濾若發生在檢索之後,可見比例越低剩得越少。

https://ithelp.ithome.com.tw/upload/images/20260907/20183550PHLX2ZK0eS.png
圖 4:取回深度不變,同文件片段共用 ACL 後,湊滿率下降 13.0–38.5 個百分點。

在「每個片段各自決定可見與否」的假設下,要有 95% 把握湊滿 top-5,得撈到 K ≈ 9.15 ÷ p——四種可見比例下,片段獨立模型的模擬湊滿率是 95.0%–99.5%,與設定目標一致——但模擬沿用同一個獨立假設,不能據此保證真實 ACL 下也湊得滿。

同一份文件的片段共用 ACL 時,片段彼此獨立的假設就不成立。 可見比例 10%、同樣取回 92 筆,湊滿率從 95.0% 掉到 63.5%——少了 31.5 個百分點。

今天的實驗需要什麼

不用 GPU,bge-m3sentence-transformers 跑 CPU 就夠。門檻是一份你知道答案已經改過的題目,三題起步。

⚠️ 但別把「舊版還在索引裡」或「文件全部 skipped」直接當成錯誤——它們都可能是對的。要驗的是三件事:問現況時有沒有拿到現行版本換過模型之後向量有沒有真的重算來源漏抓的那一趟有沒有誤刪

小結

  • 企業知識庫的 bug 不噴錯誤,它回報成功。 三種操作、三種不一樣的成功回報,沒有一趟為這三個結果發過告警。
  • 時間權重能調整排序,現行版本仍得靠版本與取代關係判斷。 時間戳不進向量分數,先取候選再衰減的射程就是候選池;而在本次的預設 hash 流程下,修改 metadata 的文件會得到新的 key。
  • 四種形狀有兩種主要不靠索引(DB 進 schema 與表描述、即時系統現查),避開那台狀態機、換來 schema 漂移;權限的兩種做法都是過濾不是身分驗證,複製那種有同步延遲,下推那種有自己的靜默失效。

明天 Day 28〈把答案直接餵給它還是答錯:讓不大的模型答得準〉:今天講的是候選池裡有什麼,明天把正確的段落直接塞給 8B、12B——它照樣答錯,而且錯的形狀是完全無視那一段。

咱們明天見。

附註一:換 collection 記得連 namespace 一起換

上面那個維度衝突真的發生時,換掉 collection 只做了一半:record manager 的 namespace 沒跟著換,10 份會全部 skip 進一個空的 collection——索引回報一切正常,庫裡什麼都沒有。

附註二:那個 48.9% 的分母

Day 22 引過 Uber Genie 的 helpfulness 48.9%,欠了一句「分母為什麼可疑」。(這不是昨天那個 48.9%——那個是我量的召回率。)它是四顆 Slack 按鈕的使用者自選回報。Uber 逐字定義了那四顆,卻沒有一句話說 48.9% 是怎麼算的——「helpfulness」這個詞全文只出現一次,就在那個百分比旁邊,跟定義按鈕的那一節從頭到尾沒有交集。

同一家公司一年後示範了該怎麼做:uReview 報「平均 65% 的評論在同一次改動裡被處理」時,同一段補了人類基準——人寫的評論只有 51%48.9% 要能判讀,得先知道分子、分母與回報範圍;人類基準則是另一項有用的比較。

這篇的條件與來源

機器 T1(M4 Max 128GB),全程 CPU。語料與切法沿用 Day 25,corpus_sha256 逐位相同。所有數字僅代表這份語料與這組設定。

檢索 day01.mdday24.md / 451 片段 / BAAI/bge-m3(rev 5617a9f6)/ CPU / chunk 300 token
衝突標記 只收「主張作者那台 N 卡機是雙卡/24GB」的句子,談別台的不收,stale 是下限
時間衰減 score × 0.5 ** ((24 − day) / 半衰期)day 取檔名編號;硬過濾 day ≥ 12。⚠️ 綁在「更正比無關內容舊」這個形狀
射程實驗 Elasticsearch 9.2.0 單節點 / 2,000 份 / 8 維 dense_vector + HNSW / function_scoreknn
索引狀態機 langchain-core 1.6.1(當期 1.6.2)/ chromadb 1.5.9 / 10 份 / 確定性假 embedding。維度不符的錯誤是 chromadb 丟的,換別的向量庫可能不報
post-filter 200 個模擬使用者;「成群」=逐 dayNN.md 指派可見性。是模擬不是量測,真實區塊大小自己量
K ≈ 9.15/p 【推算】Poisson 近似下使 P(X ≥ 5) ≥ 0.95 的 λ* = 9.1535。⚠️ 取 9.15 只有 94.99%;精確二項解在 p=0.1 是 89
用在哪 出處
衝突票數與名次、半衰期掃描、硬過濾、四個 IndexingResult 與後續條件、Qdrant 推日期、k 決定射程、post-filter 模擬 一手實測,research/day27/(ES 9.2.0 與 Qdrant 1.19 跑在容器裡)
force_updatecleanup 四模式、source_id_key 吃字串或 callable、metadata 進 hash langchain-coreindex() docstring 與 indexing/api.py
LlamaIndex 的 node hash、預設把 metadata 併進要 embedding 的文字;Azure indexer 的時間戳與目錄改名 官方文件與原始碼(hash 那半是讀碼推論,metadata 那條實跑過)
Uber 放棄 PDF loader 改自寫 Google Docs loader;Genie 四顆按鈕與 48.9%(「helpfulness」全文只出現一次);uReview 的 65% 與基準 51% uber.com/blog 的 enhanced-agentic-rag(2025-05-29)、genie-…-copilot(2024-10-10)、ureview(2025-08-12)【皆廠商自述】
帶值提問要把使用者的叫法對上資料庫的值;另建值索引是其中一條路,複本要填 TARGET_LAG Snowflake《Improve literal search…》
ACL 變更可能要 24 小時、只在全量爬取更新 Microsoft《Search and validate indexed connector content》(08-23)。⚠️ 只涵蓋會進索引的 synced connector
federated 走 MCP、唯讀(tool 要掛 readOnlyHint)、可自訂 Microsoft 的 set-up-custom-federated-connectors(08-17)與 submit-federated-connector(08-29)。⚠️ 同文件集的 overview 比較表(08-13)仍寫不支援自訂,本文取較新的兩頁
「沒有 ACL 視為 public」/「缺少權限視為受限」、「過濾不是授權」 Amazon Kendra;Bedrock Managed Knowledge Base 的 ACL-aware retrieval
row filter 失效要三件事同時成立:欄位與函式參數型別不符、ANSI mode 關閉、條件放行 NULL Databricks《Filters and masks》的官方 bug 範例(salary_filter(dept INT) 掛在 STRING 欄位)
「差別不在幾個來源同意,在出現幾次」 Whose Facts Win? v3。13 顆開放權重模型、合成來源、強制選擇題設定(早期版本對其中一顆有例外)
GraphRAG 轉維護模式 github.com/microsoft/graphrag 的 WARNING(2026-08-14;archived: false,08-21 仍發 v3.1.2)
三家的時間衰減結構上就在檢索之後:Qdrant 無 prefetch 回 400、Weaviate 稱 post-retrieval rescorer、Milvus 型別寫死 RERANK;ES 的 function_score 是 query,script_score 還能逐筆算向量相似度並結合衰減 四家官方文件;Qdrant 的 400 與 ES 包 match_all 對 2,000 筆全算分為一手實測。ES 對代價的原句是 “all matched documents are linearly scanned”
建圖的分界(延伸閱讀) GraphRAG-Bench(arXiv:2506.05690 v3,ICLR 2026)。同一個小說語料上,MS-GraphRAG(local)的 ACC 在事實檢索輸給加 rerank 的基本 RAG(49.29 對 60.92),複雜推理反超(50.93 對 42.93)、情境摘要拉最開(64.40 對 51.30);換到醫療語料四個難度全輸。⚠️ 全是 ACC、GPT-4o-mini、local 模式(global 分數完全不同);有報導把 HippoRAG2 與 MS-GraphRAG 混成同一欄

上一篇
Day 26 - RAG 分域檢索:搜對區了,召回率還是從 96.2% 掉到 89.4%
下一篇
Day 28 - RAG 明明撈到答案,小模型為什麼還會抓錯數字?
系列文
不是模型太慢,是你沒算過這筆帳:地端 LLM 工程實戰 30 天30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言