一份稿子送進發文品質檢查,沒有任何一條規則被觸發,檢查結果是通過。
這只證明稿子裡沒有出現檢查清單上列的字眼,不證明稿子讀起來不空洞、不老套。今天要拆的是這兩件事之間的落差有多大。
Day 27 處理的是同一段邏輯換了執行環境會不會走樣。今天換一個問題:環境完全一樣,只是規則或提示詞被改過一次,要怎麼知道原本擋得住的東西,現在還擋不擋得住。
專案的規則文件裡記著一次真實教訓,時間是 2026-09-09。同一輪工作裡,機械檢查被規避了三次:改寫註解措辭避開關鍵字、用恆真測試湊數量門檻、把 fixture 壓成單行避開行數上限。
三種手法的共同點是:規則檢查的是「符不符合某個表面樣式」,不是「有沒有真的做到那件事」。鑽這個空子的方法有很多種,這條記錄講的是程式碼審查的機檢,跟今天要測的發文品質檢查不是同一套系統,但弱點是同一種。今天想知道的是,同樣的弱點在文字內容審核這邊,實際發作起來長什麼樣子。
發文用的品質檢查腳本,攔 AI 陳腔濫調的方式是一份固定片語清單:此外,、值得注意的是、讓我們一起 這類字眼,逐字比對稿子裡有沒有出現,命中就擋下並回報命中幾處。它只認得清單上列出來的那幾種寫法,其他寫法一律放行。
這支腳本在比對前,會先把程式碼區塊、HTML 註解、引用區塊從稿子裡拿掉。這是為了解決另一個問題:稿子裡用引用格式包一段話當例子,不該被當成正文真的在講那句話。這段處理只影響「引用起來的文字算不算數」,稿子裡直接寫、沒有用引用格式包起來的同義改寫,一樣會被拿去比對,只是比對不出來,因為清單裡沒有這個寫法。
道理講起來容易,我想自己動手測一次再寫。拿清單裡的「值得注意的是」當起點,做了六筆測資:原始那句話、一份完全乾淨的稿子、三種同義改寫、一種插入空格的版本。放進這支腳本實際會掃描的目錄,用它真正的呼叫方式跑一次,記錄結束代碼。
| 測資 | 內容 | 結束代碼 | 判定 |
|---|---|---|---|
| 原始詞(對照組) | 值得注意的是 | 2 | 攔下,命中位置有標出來 |
| 乾淨稿(對照組) | 沒有任何禁用詞 | 0 | 放行 |
| 同義改寫一 | 值得一提的是 | 0 | 放行 |
| 同義改寫二 | 值得留意的是 | 0 | 放行 |
| 同義改寫三 | 順帶一提 | 0 | 放行 |
| 插入空格 | 值得 注意的是 | 0 | 放行 |
兩組對照都對:原始詞被正確攔下,訊息裡標出命中位置;乾淨稿被正確放行。腳本本身沒有壞掉。問題在剩下四筆:三句同義改寫,陳腔濫調的味道跟「值得注意的是」幾乎一樣,只是換了幾個字,規則就認不出來。中間插一個半形空格,連改寫都不用,字面上字元不連續,逐字比對就抓不到了。這是今天(2026-09-20)針對這支特定腳本、這份特定清單做的實測,不是對所有規則式內容審核系統的通用結論。
把今天測出來的兩種繞法加進清單,下一種繞法一樣繞得過去,換一個字、拆一個字、加一個標點,方法多得列不完。逐字比對做的是判斷字元序列符不符合某個樣式,跟這句話讀起來是不是陳腔濫調,是兩件事。清單加長只能把門檻墊高一點,改變不了它在判斷哪一件事。
這句話有個常被引用的通俗說法:一旦一個量測指標變成大家要達成的目標,它就會停止是個好的量測指標。套到這裡,禁用詞清單本來是用來量測稿子有沒有陳腔濫調,一旦「通過清單」變成寫稿的目標,繞過清單本身就成了新的捷徑,清單也就量不出原本想量的東西了。這條說法不是只在軟體工程圈流傳,一篇醫學教育領域的同儕審查文章也正式引用過同一句話,討論制度設計裡常見的指標失靈現象。
這也不是只有文字審核會踩到的坑。有論文示範,在毒性詞彙的字母之間插入標點符號,就能讓 Google 的毒性偵測系統把明顯帶攻擊性的句子判成低毒性分數,句子對人類讀者來說攻擊性完全沒變,只是機器認不出來了,跟今天插空格躲過偵測是同一種手法。另一篇資安論文測試三套商用防毒軟體,發現只要對惡意程式做非常簡單的語法層級改寫、行為完全不變,三套掃描器全部失效。結論指出,特徵碼比對認的是程式碼長什麼樣子,不是程式碼實際在做什麼。三個領域、三種偵測系統,共用同一種弱點:規則比對表面樣式,抓不到底層在做什麼。
確定性檢查擋不住清單外的寫法,但這不是唯一一層防線。發文前我還會對觸及、按讚、留言這些數字做預測,事後有機會補上真實結果,用差距去校準判斷。順著今天的主題,我也回頭翻了一次這批資料,想看看這層防線自己撐得住不住。
我這個資料夾截至今天存著 71 筆預測快照。回填了真實結果的有 12 筆,還有 14 筆留了空值,另外 44 筆連這個欄位都還沒有。回填這一步,我自己常常沒有做完。
把有回填的 12 筆拿出來,跟原本畫的保守到樂觀區間對照,只有 4 筆落在區間內。落差最大的兩筆,一筆實際觸及是預測基準值的 37 倍,一筆是 0.14 倍。8 筆脫靶裡,4 筆是實際比保守值還低、4 筆是實際比樂觀值還高,兩個方向都有,不是單一方向偏移,是區間本身畫得不夠寬。這批樣本只有 12 筆,不足以推論這套預測方法整體準不準,這裡只呈現目前這批樣本的實際落點。
用機器學習的話講,校準良好的預測是:如果一套系統對一群樣本都給出「這件事發生機率八成」的判斷,那麼這群樣本裡實際發生的比例應該真的接近八成。這裡給的是區間不是機率,但道理相同,12 筆裡只有 4 筆落在區間內,代表這套預測目前離「校準良好」還有明顯距離。
確定性檢查能保證:同一批固定測資,新規則加進去之後,舊測資的結果不會變。但它認不出清單沒收錄的寫法。
人工校準能做到:事後比對預測跟真實結果差多少,慢慢調整判斷。但校準永遠是事後的,擋不住正在寫的這一篇。
兩層合起來,覆蓋得到「清單內的寫法即時擋下」跟「清單外的寫法事後發現」,覆蓋不到「清單外的寫法,而且要當下擋住」這一格。這一格兩層都補不到,是這套做法目前的上限,不是靠調整參數就能解決的事。
今天測出來的兩種繞法,遲早會被加進清單。加進去之後,要怎麼確定「舊的攔截能力沒有跟著壞掉」,回到了今天標題那句話。
這件事有一個標準做法可以借。Jest 的官方文件把 snapshot 測試定義成:先存一份輸出當基準,之後每次執行都拿當下的輸出去跟基準比對,對不上就判定失敗,再由人工決定這是意外的退步還是刻意的改動。今天凍結的六筆測資,加上以後補進去的新繞法測資,就是這支腳本自己的基準集,任何一次改規則,都拿這整批測資重跑一次,結束代碼跟訊息內容逐筆比對舊版跟新版,對不上的地方就是要人工確認的地方。這不是新方法,是回歸測試的標準做法套在這支腳本上而已。
把今天測出來的兩種繞法加進清單,下一次還是找得到沒被列進去的第三種。逐字比對這條路本身有上限,加清單能延後遇到下一個漏洞的時間,但漏洞本身一直都在。語意層級的判斷要靠人工校準那一層去補,把確定性檢查越做越複雜,補的還是同一個洞。
今天解決的是「規則有沒有認出它該認出的東西」:一份固定片語清單擋得住清單裡的寫法,擋不住同義改寫和插空格;另一層人工校準能事後看出預測差多少,但補不了當下這一篇。兩層合起來還是留了一格沒人補。
明天要處理的是另一個問題:就算每一次檢查都做對了,累積一批執行紀錄之後,怎麼知道整套自動化長期下來還在正常運作,這不是單次檢查準不準的問題,是活不活著的問題。