Day 14~16 的題目有一個我一直沒講的共同點:每一題都只要一條條文就答得完。
在那個前提下,Day 15 的結論成立得很漂亮——「答對率的天花板就是 recall@k」,
撈得到就答得對。
今天把題組換成 8 題缺一條就算不出來的綜合題。第一張表就把那個結論推翻了。
| k | 單條 recall | 聯集 recall | 答對率 | 輸入 tokens |
|---|---|---|---|---|
| 1 | 50% | 0% | 1/8 | 2,239 |
| 3 | 81% | 62% | 5/8 | 4,863 |
| 5 | 81% | 62% | 4/8 | 7,579 |
| 10 | 88% | 75% | 5/8 | 14,334 |
| 15 | 94% | 88% | 3/8 | 20,791 |
k 從 3 開到 15,聯集 recall 多了 26 個百分點,輸入 token 貴了 4.3 倍,
答對率掉了兩題。把更多正確的條文塞進 context,不等於模型會用它們。
(語料:虛構的《員工工作規則》59 條,同 Day 9~16。生成用 gpt-4o-mini、
temperature=0、prompt 全程固定在 P1 基本版——今天唯一的變因在檢索側,
跟 Day 16 剛好相反。)
第 301 題「平日加班 3 小時,加班費怎麼算?」,第 19 條在每個 k 都是第 1 名。
同一條條文、同一個模型、同一句 prompt。多給了 12 條,答案從「分段算」塌成
一個錯的比例,計算基準整句消失。第 304 題也一樣:k=3 答得清楚,k=15 縮成一句。
Day 16 量到「加一句引用要求,答案會擠掉限制條件」。今天量到的是同一個現象的
另一個觸發源:context 變長,輸出也會縮——k=15 的輸出 token(377)比 k=3
(387)還少。你付了 4.3 倍的輸入,買到的是更短的答案。
反轉是真的。但我把 8 題的答案原文逐字讀過一遍之後,發現更難堪的事:
這 8 題裡,我的尺判錯了 5 題。
第 308 題是最糟的一個。問「颱風停班我改在家上班,要不要簽到?算不算出勤?」
正確答案要兩條:第 16 條(改居家辦公以正常出勤論)+ 第 15 條
(遠距工作日仍應完成線上簽到)。
第 15 條五個 k 值全部沒撈到,聯集 recall 是 0%。而模型五次都回答:
該日以正常出勤論,因此當天不需要簽到。
facts 是「正常出勤」「簽到」,兩個關鍵詞都在句子裡。我的尺五次都判它對。
規章的答案是「要簽到」,模型答的是「不用簽到」,中間只差一個「不」字,
而子字串比對看不見否定。
這不是新發現。Day 14 的 README 就寫著這把尺「只看有沒有講到,不看有沒有講錯」,forbid 欄位就是用來補這個洞的——而我這 8 題的 forbid 全部留空。
| 題號 | 判定 | 實際情況 |
|---|---|---|
| 304 | k=5、k=15 ❌ | 模型寫「無需發給資遣費」,我的 fact 是「不發給資遣費」。差一個字 |
| 305 | 五個 k 全 ❌ | k=3 答「尚未滿六個月,因此沒有特休假」——這是全場最好的答案,被判錯 |
| 306 | 五個 k 全 ❌ | 答「會被書面提醒,但不會直接記過」。完全正確,但沒說出「申誡」兩個字 |
| 307 | 五個 k 全 ❌ | 答「公司得暫緩發給」。正確,但沒講「七個工作日」——因為問題根本沒問時限 |
305、306、307 是同一個病:我把「條文裡有的事實」當成「正確答案必須包含的事實」。
問「會不會被記過」,正確答案是「不會,先書面提醒」,它沒有義務背出懲處清單。
而第 301 題的病更根本:它根本不是多條文題。第 19 條原文寫著
「加班費之計算基準為第 33 條所定之本薪加計經常性給與」——
條文自己把答案交叉引用過來了。所以 k=3 沒撈到第 33 條也答得對。
順帶更正 Day 15 補充實驗的一句話:我當時寫這題「k=3 沒撈到第 33 條卻答對」是
判定寬鬆造成的僥倖。那句是錯的。真正的原因是條文互相引用,
而我出題時沒有去讀第 19 條的最後一句。
腳本裡有一節「判定自我檢查」:把每題必要條文的原文送進 grader,8 題全過。
我以為這證明了尺沒問題。
它只證明了一件事:尺不會誤殺照抄原文的答案。
它完全沒有檢查「一個簡短而正確的答案會不會被誤殺」——而那才是模型實際會寫的東西。
單條文題可以用關鍵事實比對,因為正確答案幾乎就是條文本身。
多條文題的正確答案是一段推理(「兩條合起來所以三個月還沒有特休」),
推理的表達方式有無限多種,關鍵詞只抓得到其中一種。
所以第一張表要打個折來看:判定沒有爭議的只有 301、302、303,
其中只有 301 是 k=15 掉的。反轉看得到,但有多少是真的、多少是尺的雜訊,
今天答不出來。
題目變難的時候,先壞掉的不是模型,是你的判定。
固定 k=10,同一批 context 只換排列順序,輸入 token 三組完全相同(14,334):
| 排法 | 答對率 |
|---|---|
| 相似度序(預設) | 5/8 |
| 反序(最相關放最後) | 3/8 |
| 必要條文塞中間 | 3/8 |
掉的是第 303、304 題,而且掉法很具體:303 反序時把「超過部分自行負擔」
整句丟了,304 塞中間時連「三日」都不見了。同樣的資訊、同樣的 token,
只換位置就掉兩題。8 題太小不足以下定論,但這個變因你在自己的系統上值得測一次。
reranker(候選 15 → gpt-4o-mini listwise 重排取 3):
| 組別 | 聯集 recall | 答對率 | 生成成本 | 重排成本 |
|---|---|---|---|---|
| 純向量 k=3 | 62% | 5/8 | 0.030 元 | — |
| 純向量 k=15 | 88% | 3/8 | 0.104 元 | — |
| rerank 15→3 | 75% | 4/8 | 0.030 元 | 0.101 元 |
重排挑得比向量準(聯集 recall 62% → 75%),答得比 k=3 差。
而且那筆帳:重排本身比它省下來的生成貴 3.4 倍,加總後比直接 k=15 還貴一點。
因為它要先把 15 條完整讀一遍才能排序——候選池的 token 一樣要付,
只是換到另一行帳上。8 次重排的輸出總共只有 40 個 token(就是「1,3,7」),
這是一筆幾乎全是輸入的帳單。
最諷刺的是第 301 題:重排把第 33 條(候選第 9 名)丟掉了,
挑進來的第 20、26 條是補休規定,跟算錢無關。然後它把加成比例答反了。
今天三個變因——加大 k、換條文順序、加 reranker——沒有一個買到更高的答對率。
但真正的收穫是第二節:我在單條文題上用了三天的那把尺,換成多條文題就壞了,
而且我的自我檢查設計成看不見這件事。
成本:首跑計費 embedding 258 + 生成輸入 99,739/輸出 2,838 tokens
≈ 新台幣 0.517 元(72 次生成 + 8 次重排)。全部從零跑約 0.622 元。
如果你的 RAG 評估只有一個「答對率」數字,去把 20 個答案原文逐字讀一遍。
你會找到你的 308。
明天 Day 18:換判定方式。關鍵事實比對從 Day 14 撐到今天,八題壞五題,
該退休了。要換成逐項 rubric、還是終於請 LLM 當裁判(本系列前四天一直拒絕的
那個選項)——以及裁判自己要怎麼驗。
GitHub:https://github.com/wp900622/TrustRAG(day17_rag/)