我建了一個 AI 知識機器人,結果發現它一直在對社群成員說謊

Quick Check
对还是错:AI 工具将在 2 年内完全取代 SEO 的需求。
重點摘要(TLDR): 我的 RAG 聊天機器人對索引裡明明有的課程,一直回答「我沒有這方面的資料」。原因是:建立索引和線上查詢走了兩條不同的嵌入路徑,一條是 Hugging Face 託管模型,另一條是缺少 API token 時自動啟用的量化 ONNX 回退。兩邊的向量對不上,正確的匹配分數掉到 0.55 閾值以下,被直接丟掉。修復方式:不再讓回退靜默發生、用同一條路徑嵌入所有內容、降低閾值並加入 LLM 層的相關性過濾。
最初的目標
目標很單純:做一個能從課程內容回答問題的聊天機器人,讓大家直接提問,不必翻找好幾個小時的影片。
我建了一個 RAG(Retrieval-Augmented Generation,檢索增強生成)聊天機器人,作為隨時可用的知識庫。
技術架構很標準:把課程內容切成文字片段(chunk),用 paraphrase-multilingual-MiniLM-L12-v2(Hugging Face 上的多語言 sentence-transformers 模型,會把文字對應到 384 維向量)生成嵌入向量(embedding),把向量存進 Pinecone 向量資料庫(vector database),聊天應用部署在 Vercel。
上線前我查了索引統計。向量都在,資料是真的,紙面上一切都沒問題。
所以我上線了。當時我還不知道,它正在悄悄丟掉好答案。
使用者開始遇到的狀況
症狀是一句客氣的「沒有資料」。課程明明講過的主題,機器人卻回答它沒有這方面的資料,換個方式提問也一樣。
到這一步,我不再假設是使用者問錯,打開了終端機。
開始排查
第一步是最明顯的:確認索引裡真的有這些內容。
我直接呼叫 Pinecone 的統計 API,向量數量和我索引的一致。資料確實在索引裡,這不是資料遺失的問題。
所以我跑了一次直接向量查詢——不透過聊天機器人介面,而是寫了一個測試腳本,取一位成員輸入過的原始問題(關於外國人如何在美國成立 LLC),在我本地機器上把它轉換成嵌入向量,然後直接打到 Pinecone,不設任何閾值過濾,要求返回前 10 名結果。
正確的課程確實出現在結果裡,但它的相似度分數(similarity score)遠低於生產環境的閾值。
生產環境的 MIN_SCORE 閾值是 0.55。
答案存在,機器人也找到了。然後因為分數太低過不了篩選,機器人把它丟掉了。
這解釋了表面症狀,卻沒解釋為什麼一堂明確相關的課程分數這麼低。多語言模型本來就應該能處理英文問題對應中文內容,這正是 paraphrase-multilingual-MiniLM-L12-v2 的用途。
我開始深挖嵌入管道(embedding pipeline)本身。
三條嵌入路徑,一個破掉的索引
這裡有一件我建系統時沒有充分認識到的事:paraphrase-multilingual-MiniLM-L12-v2 在程式碼裡有三種執行方式,而且它們並不等價。
路徑一:Hugging Face 託管推理 API。 帶上 Bearer token 傳送文字,取回一個 384 維向量。只要 token 有設定,生產環境用的就是這條路徑。
路徑二:本地執行 SentenceTransformer。 在 Python 裡用 sentence-transformers 函式庫載入模型,下載權重並在你的機器上推理。和託管 API 是同樣的權重、同樣的運算,所以向量應該一致。
路徑三:透過 fastembed 使用量化 ONNX(ONNX-Q)版本。 Qdrant 為 fastembed 函式庫發布了這個模型的量化 ONNX 版本。它速度快、記憶體用量低,但運算和路徑一、二不完全相同,產生的向量會有些微差異。
最快的確認方式,是把同一個句子分別用每條路徑嵌入,再用餘弦相似度(cosine similarity)比較輸出。
託管 API 和本地 SentenceTransformer 是同一個模型、同樣的權重,結果應該幾乎完全一致。
差異出現在量化 ONNX 版本。
不同,可測量地不同。不是災難性的差異,向量不是隨機雜訊,大體上落在相近的區域;但差異足以讓一條路徑建立的向量和另一條路徑產生的查詢向量之間,相似度分數比應有的低。不至於讓結果掉出前 10 名,卻足以把邊緣匹配推到 0.55 閾值以下。
現在我理解了機制。問題是:兩條路徑是怎麼混在一起的?
答案尷尬得簡單:缺少 HF_TOKEN。嵌入程式碼有回退邏輯:token 不存在時,就改用量化 ONNX 模型當作「優雅回退(graceful fallback)」。快速、安靜,在這個場景中完全錯誤。
一個環境有 token,另一個沒有。用一條路徑建立的向量,被另一條路徑嵌入的查詢拿來搜尋。兩個略有差異的向量空間、同一個 Pinecone 索引、0.55 的閾值,結果就是知識庫悄悄埋掉了它其實有的答案。

為什麼跨語言查詢讓問題更嚴重
這個問題還有另一個層次,讓 bug 更難被發現,實際危害更大。
即使嵌入路徑完全一致,英文問題對應中文內容,分數也可能比同語言匹配低。多語言模型在跨語言語義對齊上做得不錯,但同等相關的跨語言配對,分數未必能和同語言配對一樣高;只用英文測試調出來的閾值,正好沒考慮到這個落差。
那堂本該回答問題的課程同時被兩個問題擊中:嵌入路徑不一致,加上跨語言本來就有的分數落差。兩者疊加,分數掉到 0.55 閾值以下,用英文提問的人完全看不到它。
這裡特別值得台灣和華語圈創業者注意:如果你正在建一個同時服務中英文使用者的 AI 知識庫,或課程、手冊有部分是中文內容,很可能會碰到這個跨語言分數落差。差別只在於你是在上線前發現,還是等到使用者反映才知道。
如果我的知識庫全部是英文內容,漂移帶來的影響會更小,也許我還能被這個 bug 瞞更久。多語言內容讓問題更早浮出水面——這是整件事唯一的一線曙光。
修復方案(三個步驟)
我沒有繞過問題打補丁,而是回到根本原因處理。
修復一:在每個環境設定 HF_TOKEN,並讓回退不再靜默。
補上 token 是第一步。現在只要 HF_TOKEN 缺失,回退時會記錄一條明確指出缺少 token 的警告,而不是悄悄切換路徑。如果重來一次,我會直接把缺少 token 設為硬性錯誤(hard error),讓腳本在碰到索引之前就停下。靜默回退到不同的模型路徑,不應該出現在生產環境的嵌入管道裡。
修復二:讓建立索引和查詢走同一條嵌入路徑。
索引裡的每個向量,都必須來自和查詢相同的模型路徑。混雜的向量無法逐篇修補;如果索引是在漂移期間建立的,就在兩端都確認正確路徑之後重建。路徑一致之後,大家問過的那些課程又能被找回來了。
修復三:降低檢索閾值,並加入 LLM 層的相關性過濾器。
即使修好漂移,只用同語言測試調出來的閾值,對多語言知識庫來說仍然太嚴苛。我降低了最低分數,停止丟棄邊緣匹配。但降低閾值而不做補償,會把更多雜訊傳給語言模型。
解決方案是:在向量檢索之後,讓語言模型對每個候選 chunk 做二元相關性判斷,相關或不相關,再組成最終答案。這用語義判斷取代了粗糙的向量閾值過濾。較低的檢索底線加上 LLM 層過濾,覆蓋率更好,同時把離題的片段擋在答案之外。
如果你也在跑 RAG,這三個檢查今天就該做
不管你的 RAG 聊天機器人跑在 Vercel、Railway、Fly.io 還是自架環境,以下三項檢查值得現在就去做。
檢查一:模型路徑一致性測試(Model Path Consistency Test)。
寫一個簡短腳本。取一個句子,用系統使用的每一條程式碼路徑去嵌入它:本地開發環境、CI 環境、生產環境。計算所有配對之間的餘弦相似度,結果應該幾乎完全一致。如果任何一對明顯偏差,你就有嵌入漂移,索引不可靠。每次重新索引之前,都把它當作預飛檢查(pre-flight check)執行。
檢查二:跨語言閾值校準(Cross-Lingual Threshold Calibration)。
如果你的知識庫包含多種語言的內容,在生產環境設定閾值之前,要明確地針對跨語言配對測試你的相似度閾值。取一段語言 A 的已知相關內容,用語言 B 查詢,測量實際分數。如果你的閾值在截斷這些結果,就降低閾值,並在下游用 LLM 相關性過濾器補償。不要假設你的多語言模型對跨語言匹配和同語言匹配給出同樣的分數——它不會,而且只用同語言配對校準閾值,對多語言知識庫來說會產生過度嚴苛的結果。
檢查三:LLM 層的相關性過濾器(LLM-Level Relevance Filter)。
低檢索閾值帶來更多候選 chunk,也就是更多雜訊。正確的回應不是把閾值調回去——而是在檢索之後加入語義過濾。把候選 chunk 傳給語言模型,用一個簡單的提示詞讓它判斷每個 chunk 是否與查詢相關,在組成最終答案之前過濾掉不相關的。這能捕捉向量相似度無法可靠區分的語義近似錯誤(semantic near-misses),並在檢索網撒得更廣的情況下維持答案品質。
常見問題(FAQ)
RAG 聊天機器人裡的嵌入漂移(embedding drift)是什麼意思?
嵌入漂移是指在索引建立時和查詢時所產生的向量表示(vector representation)之間的不匹配。當嵌入模型、模型版本,或模型執行路徑在索引和查詢之間有所不同,產生的向量就會佔據向量空間的不同區域。查詢向量和索引向量之間的餘弦相似度分數會比預期更低,導致相關內容跌落檢索閾值以下,從機器人的回答中消失——即使這些內容確實物理性地存在於索引之中。
我怎麼知道我的 RAG 系統是否有嵌入一致性問題?
最可靠的測試是:取一個你知道在知識庫裡的句子,用你的生產查詢路徑將它嵌入,然後直接和索引裡那個句子的已索引版本計算餘弦相似度。分數應該非常接近 1.0。如果明顯偏低,你的索引和查詢路徑使用的是不同的向量表示。你也可以間接判斷:如果你的機器人持續說它沒有你明確涵蓋的主題的資料,而直接 Pinecone 查詢顯示那些向量確實存在,但分數比預期低,漂移很可能就是原因。
為什麼量化 ONNX 模型會產生和全精度模型不同的向量?
量化(quantization)以較低的數值精度儲存模型。以 ONNX Runtime 為例,它的量化是 8 位元線性量化,把 32 位元浮點數值對應到 8 位元整數;ONNX Runtime 官方文件也說明,量化不是無損轉換,可能影響模型準確度。對很多任務來說差距可以接受;但在 RAG 系統裡拿量化向量和全精度查詢向量比較時,這個差距足以把相似度分數推到檢索閾值以下。
多語言模型能處理英文查詢對應中文內容的情況嗎?
可以,而且效果不錯,但同等相關的跨語言配對,分數可能比同語言配對低。只用英對英測試配對校準的閾值,對多語言知識庫來說可能過於嚴苛。要針對真實的跨語言配對測試閾值,並根據實際測量的分數設定,而不是借用單一語言測試的假設。
防止嵌入管道出現靜默回退行為,最安全的方式是什麼?
大聲失敗,立即失敗。任何在依賴項缺失時會回退到不同模型、API 或執行環境的程式碼路徑,都應該拋出帶有具體、可行動訊息的硬性錯誤——而不是靜默地用不同實作繼續執行。在資料攝入管道(data ingestion pipeline)中,靜默回退是最危險的失敗模式,因為它產生看起來合理的輸出:向量被生成了,索引被填充了,沒有錯誤被記錄,一切看起來正常運作。等你注意到問題時,你可能已經有一個完整的生產索引建立在錯誤的向量空間上。把嵌入 API token 缺失當作資料庫連線缺失一樣對待:立即停止,明確告訴操作者在管道能夠執行之前需要修復什麼。
修復嵌入漂移之後,我需要重建整個 Pinecone 索引嗎?
需要。如果你的索引是用和查詢環境不同的嵌入路徑建立的,漂移期間索引的每一個向量,相對於你的查詢都在錯誤的向量空間裡。只重新索引你認為受影響的文件是不夠的,因為問題是系統性的。刪除命名空間(namespace),在兩端都確認正確的憑證和模型路徑之後,重新完整執行嵌入管道,用模型路徑一致性測試驗證,然後恢復生產環境。重建是一次性的代價;前面描述的一致性檢查成本很低,而且在建立索引之前就能抓到問題。
延伸閱讀:RAG 系統開發指南。想了解我的工作方式,或請我幫忙檢查你的 RAG 系統,歡迎聯絡我。
Where Are You Right Now?
你的业务目前在 AI 方面最大的挑战是什么?


