昨天量出一件反直覺的事:讓模型自己決定查幾次,跟寫死的檢索打成平手,
而且貴 4.4 倍。最硬的證據是——兩邊撈到一模一樣的條文,
對照組算對了,agent 算錯了。問題在生成側,不在檢索側。所以今天加一個聽起來像免費正確率的東西:交卷前,讓它拿著條文自己驗一次。
結果是 0 題的淨進步、2 倍的錢。而最值得寫的不是這個數字,
是它放行了什麼。
昨天的 agent 是一個迴圈,每一輪模型只能做兩件事之一。今天多一件:
search(query) 我要查規章,用這串字去查
check(draft) 這是我的草稿,對著條文幫我驗一次 ← 今天唯一的新東西
answer(text) 我驗完了,這是最終答案
check 不是一個判斷式,是另一次 LLM 呼叫:把「問題」「它自己查到的條文原文」
「它寫的草稿」三樣送進去,要它回報兩種東西——沒有依據、與條文相反。
結果當成工具回傳塞回迴圈。
兩個刻意的限制:
因為「模型想不想驗」跟「驗了有沒有用」是兩個問題,所以跑三組:
| 代號 | 是什麼 |
|---|---|
| A | 昨天的 agent(只有 search/answer)——今天的對照組 |
| C1 | 多一個 check,用不用它模型自己決定 |
| C2 | 多一個 check,交卷前一定要驗過(沒驗就想交卷,程式退回去) |
(Day 14 以來那條寫死的管線也一起跑,當地板,下面表格裡的 B。)
| 題組 | 題數 | B 寫死管線 | A 昨天的 agent | C1 自願驗 | C2 強制驗 |
|---|---|---|---|---|---|
| 單條文 | 15 | 14/15 | 15/15 | 15/15 | 15/15 |
| 多條文 | 8 | 6/8 | 5/8 | 5/8 | 5/8 |
| 陷阱 | 5 | 4/5 | 4/5 | 4/5 | 4/5 |
| 合計 | 28 | 24/28 | 24/28 | 24/28 | 24/28 |
但只報總分是不夠的——淨值 0 可能是什麼都沒發生,也可能是救回 3 題又改壞 3 題。
所以從今天起每次改動都要拆開這一張(這是 Day 19「修完變更爛」的教訓制度化):
| 比較 | 救回(A 錯→C 對) | 改壞(A 對→C 錯) | 不變 | 淨值 |
|---|---|---|---|---|
| A → C1 | 1(301) | 1(303) | 26 | +0 |
| A → C2 | 1(301) | 1(303) | 26 | +0 |
下面三節就是這三個數字的來歷:為什麼幾乎沒動、救回的那題不算數、改壞的那題怎麼漏的。
| 組 | 有叫 check 的題數 | 被程式退件 | check 標出問題 | 標了之後答案有改 |
|---|---|---|---|---|
| C1(自願) | 1/28 | 0 | 0 | 0 |
| C2(強制) | 24/28 | 24 | 2 | 1 |
C1 那一欄先講:工具就擺在那裡,28 題它主動用了 1 次。
C2 的 24 次全部是被擋下來才驗的——模型查完就直接叫 answer,
程式回一句「交卷前請先用 check 校對這份草稿」,它才回頭去驗。
沒有一題是它自己想驗的。
驗了 24 次,22 次回「通過」。標出問題的兩次剛好是兩種失敗:
標對了(306 遲到三次會被記過嗎):
check:沒有依據:草稿提到「⋯⋯甚至記過」,但條文中並未明確提到會直接記過(第 12 條)。
模型把那句刪掉了。這是今天唯一一次「自驗 → 真的改」——但判定沒有變好,
它仍然沒有正面回答「會不會被記過」。
標錯了(307 離職沒交還財物能不能不發服務證明書):
check:與條文相反:公司不得於證明書內為不利於員工就業之記載(第 57 條)。
草稿講的是「未交還財物得暫緩發給」(第 53 條),跟「證明書內容」是兩件事。
check 抓到一句不相干的條文就喊相反,而模型沒理它——不理它是對的。
一個自驗系統的價值,等於「抓到的真問題」減掉「製造的假警報」。今天是 1 比 1。
303 問「住宿花了 3,200、發票弄丟了,可以報多少?要辦什麼手續?」
條文寫得很清楚:上限三千、單據遺失者應填具「單據遺失切結書」。
| 手續講成什麼 | 判定 | |
|---|---|---|
| A(昨天) | 填具「單據遺失切結書」 | 正確 |
| C(今天) | 填寫報銷申請表並附上相關證明文件 | 錯誤 |
「報銷申請表」這個東西規章裡沒有,是憑空生出來的——
而「沒有依據」正是我寫在 check 提示詞裡、要它第一個抓的東西。
它看著條文原文、看著這句話,回了兩個字:通過。
原因不難猜:校對的是同一個模型、看的是同一批條文。
等於讓學生自己改自己的考卷,而且改的時候連參考書都跟考試時同一本。
301 是昨天最硬的那題:條文寫「前 2 小時加給三分之一、再延長 2 小時加給三分之二」,昨天的 agent 把它塌成「加班 3 小時就加給三分之二」。今天 C 版答對了。
但看一下順序:
C2 的草稿(check 還沒跑):⋯⋯前 2 小時加給三分之一,第三小時加給三分之二。
check 說:通過
最終答案:(與草稿一字不差)
草稿一開始就是對的,check 只是蓋了個章。
那它為什麼比昨天對?因為 C1 全場只叫過 1 次 check,跟 A 的差別幾乎只剩
「工具清單多了一項」——而 28 題裡有 20 題的答案不一樣。
C2 那邊更直接:第一份草稿就已經和 A 不同的有 18 題,也就是 check 還沒開口,
答案就已經變了。
我在動手前就寫下過這個風險:C 版比 A 版多拿到三段我寫的文字——
check 的工具說明(每一輪都隨工具清單送出去)、check 的提示詞、退件訊息。所以:
我沒有辦法宣稱 301 是「自驗」救回來的。
更可能的解釋是:工具清單裡多了一個叫「校對」的東西,模型寫草稿時就比較小心。你以為你只加了一個功能,實際上你改了送進模型的每一個 token。
陷阱題 101「育嬰留職停薪最長可以留多久?」——規章裡沒有這一條。
它查了 6 次都沒查到,然後說:
育嬰留職停薪的最長期限為三年。
這個數字規章裡沒有。而 C 版在這題上跟昨天一字不差地錯,因為它把 6 步預算全花在查詢上,撞上限強制交卷、免驗。
我在計畫裡就寫了這個取捨(不拉高步數上限,因為那會同時改兩個變因),
結果比預期更難看:最需要自驗的那一題,是唯一一題輪不到自驗的——
查詢跟自驗搶的是同一份預算。
強制自驗 28 題花 0.8549 元——是昨天那個 agent 的 2 倍、是寫死管線的 8.8 倍。
貴的不是 check 那一次呼叫,是它把迴圈拉長了:檢索次數幾乎沒變(53 → 52),輸入 token 卻從 82,257 漲到 160,852。 每多一輪,前面撈回來的條文原文全部要重送一次、重新計費。
昨天寫死了六條,跑完不准改:
| # | 我猜 | 結果 |
|---|---|---|
| 1 | C2 救回 301、救不回 306 與 308 | ✅ 對(但 301 不是 check 的功勞) |
| 2 | C2 至少改壞 1 題,最可能在單條文那組 | ⚠️ 半對:改壞 1 題,出在多條文 |
| 3 | 陷阱題 101 會被 check 救回 | ❌ 錯:它連跑都沒跑到 |
| 4 | C1 主動用 check 的題數低於一半 | ✅ 對,而且是 1/28 |
| 5 | C2 是 A 的 1.8~2.2 倍 | ✅ 對:×2.0 |
| 6 | check 會標問題、模型不改 | ✅ 對,但更難堪的是它 22/24 次根本沒標 |
第 3 條錯得最有價值:我以為會被自驗攔下來的幻覺,連自驗的門都沒摸到。
今天為止,我修過檢索、prompt、尺、agent、自驗。但有一個前提從來沒被檢討過:
餵進去的語料是我自己寫的乾淨 markdown——標題工整、沒有頁首頁尾、沒有錯字。切法之所以有效,是因為語料剛好長成它喜歡的樣子。
企業實際丟進 RAG 的東西不長那樣:是 PDF、是掃描件、是拍歪的照片。明天把同一份規章換成三種形態(PDF 文字層、掃描頁 OCR、加了雜訊的掃描頁),用完全一樣的系統、一樣的 28 題、一樣的尺再跑一次,看答對率掉幾題、掉在哪裡。