iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0
AI Engineering

AI 的駕馭之道:一個 AI Code Reviewer 的養成、評測與邊界實錄系列 第 15

Day 15|Skill 的評測(下):盲測放榜,等等!標準答案好像不對勁 🤔

  • 分享至 

  • xImage
  •  

簡短回顧

昨天我們把 50 個案例全部跑完,原始分數拿了最後一名;然後把 1,395 條 claim(人寫的 golden comments + 六個工具沒命中的候選)拔掉來源標籤、打散順序,丟給一個獨立的驗證 agent。

今天看結果。

一樣先給位置:資料與腳本都在 benchmarks/code-review-bench,今天每一個數字都可以照著重算。

第二個數字

1,206 條 real、140 條 not_real、49 條 unclear。86.5% 是真的!😮‍💨

還有一個我沒預期到的:

人類寫的 137 條 golden comments,有 23 條沒通過盲測驗證。16.8%。🫨

這個數字跟哪個工具好無關,它戳的是地基:只要評分繼續把這 23 條當正解,沒命中的工具就白白被記一筆 false negative。

當然也可能是驗證員誤殺,這裡從頭到尾只有一個驗證員。86.5% 是它對整個 claim 池的判真率,保證不了它在 golden 這個子集合上一樣鬆。所以 16.8% 請讀成「未通過驗證率」,不是 golden 的錯誤率;誤差有多大,附錄有帳。

用驗證結果重算,我從最後一名變第一名:

工具 precision recall(對通過驗證的 golden) F1
nathan-code-review 89.2% 85.1% 87.1%
augment 87.6% 68.4% 76.8%
cubic-v2 77.7% 69.3% 73.3%
greptile-v4-1 87.5% 57.0% 69.0%
coderabbit 71.1% 63.2% 66.9%
claude-code 82.1% 47.4% 60.1%

從 13.0% 到 89.2%。同一批輸出,驗證結果被納入計分後,ground truth 與算法都變了。

這張表的 precision 會把驗證為真的額外發現納入命中數;recall 則只對通過驗證的 114 條 golden 計算,F1 是兩者的調和平均。因此它是一套探索性的校正指標,不是原始 benchmark 的同口徑重算。

看到這張表的當下我是真的高興。但它是我自己設計的算法,採信之前,得先問它偏不偏。

四組數字

我請了一個沒有本次任何 context 的 Opus subagent 稽核整套實驗設計,指令的重點一句話:假設作者誠實但有動機,找出機制上偏袒受測工具的地方。它用另外兩種同樣說得通的算法重算了一次。四組擺在一起:

# 算法 誰訂的 偏向 我們 F1 名次
1 上游原規則 benchmark 專案 未對上 golden 的候選會被計為 false positive,發言量越大越容易累積扣分 22.0% 6/6
2 盲測校正 話多不再扣分,但 recall 仍獎勵話多 87.1% 1/6
3 只對驗證過的 golden 稽核 agent 獨有發現一律記零分 21.9% 6/6
4 共用考卷(golden+至少兩工具都提的發現) 稽核 agent 獨有發現一律記零分 49.3% 6/6

每一組都有理由,每一組也都有偏向。第 2 組的問題最值得講:校正把 precision 的分子從「命中 golden」擴成「命中 golden + 驗證員確認為真的發現」,而驗證員對 86.5% 的 claim 都判真。我有 671 條未命中的候選可以被平反,同業是 70~219 條。原始規則裡 precision 是唯一處罰話多的項目,我的校正把處罰拿掉了,recall 卻還在獎勵話多。這是稽核 agent 抓到的,我自己沒發現。

還有一個值得停一下的細節:第 3 組(21.9%)幾乎等於第 1 組(22.0%)。也就是說,把那 23 條失效的 golden 拿掉,原始名次根本沒動。我從最後一名跳到第一名,100% 來自 discovery credit,0% 來自移除那 23 條未通過驗證的 golden。也就是說,我最得意的「標準答案也需要被稽核」這個發現,對名次反轉的貢獻是零;但整個盲測仍是 discovery credit 的來源。

recall 跟發言量的關係也量得出來:把我的候選按嚴重度由高到低截斷,150 條(每個 PR 留 3 條)時 recall 42%、200 條(每個 PR 留 4 條)49%、408 條(留下全部 Critical 與 Suggestion,77 加 331)72%、775 條(全量,含 124 條不掛嚴重度的 open questions)85%。同業用 175 條就打到 69%。(截斷是事後做的;工具當初不知道有額度限制,因此這些數字只能描述既有輸出的截斷結果,不代表事前設定相同額度時一定會得到相同表現。)

不管挑哪組算法都成立的事實只有三條:

  1. 137 條標準答案中,有 16.8% 未通過這次單一驗證員的盲測。
  2. 在通過驗證的 114 條 golden 上,我們命中 97 條,recall 85.1%,是六個工具中唯一超過 80% 的工具。
  3. 每講一條話命中標準答案的效率,我們最低:775 條命中 97 條 golden,cubic-v2 用 175 條命中 79 條。

找得最多,講得最多,單位效率最低。 至於名次,取決於你挑哪組算法,所以名次不是這次實驗的產出。

補記:這兩篇寫完之後,還抓到一次重複計分

上面那張校正後的表,數字跟這兩篇最早寫的時候不一樣。差別要交代清楚,因為它跟上一節講的是同一件事。

問題出在 cluster。驗證員是盲測的,而 cluster 跨來源,所以一個候選如果跟一條仍然站得住的 golden 落在同一個 cluster,那代表配對 judge 沒把它配對成功,它是既有的題目,不是新發現。舊算法把這種東西同時計入分母、又發了一次 discovery credit。修正的做法是兩邊都不給,往保守那一側倒。影響 15 個 PR、17 個 cluster,擴充後的 ground truth 從 883 條降到 866 條。

重跑之後,我們的 corrected precision 從 90.2% 降到 89.2%,主表 F1 從 87.6% 降到 87.1%。raw 計分一個 bit 都沒變,所以前一篇的數字不受影響;四組算法的名次也逐位不動。

真正該講的是那 17 個 cluster、18 次多發的 credit 分給了誰(有一個 cluster 同時記給了兩家)。六個工具分別拿到 8、4、3、1、1、1,拿最多的是我自己這一支。那個 bug 一直在替我加分,修掉它是我自己扣自己的分。

所以上一節的結論又成立了一次,只是這次的證據不是我主動去找的:我設計的算法會往我這邊偏,而我不會在數字好看的時候自己察覺。上一次是請一個沒有 context 的稽核員來抓,這一次是文章都寫完了,重跑的時候才撞到。

要自己重算的話:cluster 的歸併記在 scores/ground_truth_audit.jsonmerged_into_golden,配上 data/calibration/ 底下各 PR 的 *.map.json(工具歸屬)與 *.verdicts.json(cluster 歸屬),就能把 8/4/3/1/1/1 這組數字算回來。分數在 scores/summary.jsoncorrected:主表這一組看 precisiongolden_recallgolden_f1

冷靜下來想,這個結果很合理

一個 SKILL.md 加上幾個確定性工具,怎麼可能突然打贏所有商業工具?人家是整間公司在調一個產品,我是一個人在編碼自己的審查習慣。

但這個系列從 Day 3「萃取一個我」 開始,講的本來就不是「做一個最強的 reviewer」。是把我的流程、我的嚴重度標準、我的 GitLab 慣例、我的回覆語言做成 skill,讓它順應我的工作方式。這才是 skill 的價值:不是通用工具的替代品,是你自己形狀的延伸。

這次 benchmark 反而印證了這件事:它能量到的只有「給一份 diff、吐出 findings」這一小塊。intent check、發佈成 MR discussion、pushback 處理、re-review、繁體中文報告,這些 skill 真正花力氣的地方,benchmark 一項都看不到。我拿去比的,是我的 skill 裡最商品化的那一塊。

所以這兩篇的定位是分享,不是宣傳:方法、數字、翻車的地方全部攤開,整套做法都寫在這裡,想量自己 skill 的人可以照著建。

照著建的話,這兩個問題比分數本身重要,而且順序不能換:

  1. 這份標準答案站得住嗎? 先驗題目,再驗答題者。
  2. 這個算法偏誰? 自己訂的那一組,一定要找一個沒有 context 的人來抓。

那 Code Review 到底有沒有 KPI

這次量測真正讓我想通的是這件事。

F1 不是中立的。 就算 ground truth 完美,F1 仍是以對稱的調和平均組合 precision 與 recall;這不等於一次 false positive 和一次 false negative 的成本相同。一條 false positive 的成本可能是作者幾分鐘的注意力,一條 false negative 的成本可能是一次線上事故,也可能是零。兩者該如何取捨是產品與風險決策,不是 F1 能替團隊回答的問題;若其中一方更重要,可以改用 F-beta 或依實際成本設計評分方式。

而每個人的匯率不一樣。 Reviewer 百百種,切入點各不相同。我在醫療場域,我的立場就是寧願現在想多了,也不要等事情發生的當下才亂了步調。所以我是「寧願多說一點」的人,我的 skill 就長成那個樣子:recall 85%、話量 4.4 倍。這不全是 bug,有一部分是我在 precision 和 recall 的取捨上選的位置。skill 是我的形狀,廣義來說這也是康威定律的一種體現:系統會長成打造它的人的溝通結構。

指標一變成目標就會失效。 Goodhart 定律。哪天有工具開始對著這個 benchmark 優化,它學到的不會是「審得更好」,是「說 golden 會說的話」。

benchmark 量的是「說了什麼」,實務上更該追的是「最後改變了什麼」。 Code Review 從來不是 100% 抓得到、抓得準;就算抓到了,也可能因為不想改、改不動、風險被接受等理由遭到略過。一條被作者理解並修掉的 Suggestion,至少證明它形成了實際改變;十條未被採納的 Critical,則還要分辨是誤報、修正成本太高、風險被接受,還是尚未處理。採納不能取代正確性與嚴重度,但它是離線 benchmark 量不到的重要面向。

所以沒有一個 KPI 可以定調一支 Code Review 工具,就像沒有一個 KPI 可以定調一個 reviewer。但這不等於什麼都量不了。benchmark 能告訴你的是你站在取捨曲線的哪個點;它不能告訴你的是該站哪個點。該站哪,由你的場域決定。而 skill 的意義,是讓你有意識地選自己的點,而不是接受某個通用工具替你選好的點。

那下一步呢?

選了「寧願多說」,不代表噪音是免費的。775 件事裡只有 77 條是 Critical,其餘是 331 條 Suggestion、243 條 Nit,和 124 條 open questions。同事打開 MR 的注意力是真實預算。降噪還是要做,方向不變:

  1. 設定單次審查的 finding 數量上限
  2. 同類 Nit 聚合成一條
  3. 嚴重度門檻(低於門檻的不進正文)

目標不是講得更少,是讓值得讀的那幾條不要被淹掉。

之後想做的兩件事:

一、回頭看採納的形狀。 我的 skill 會把報告發佈成 GitLab MR discussion,作者的回覆、resolve、pushback 都留在討論串裡,而每份報告都帶 skill_version。累積夠多案例之後,就能回答離線 benchmark 答不了的問題:哪種嚴重度、哪個面向的 finding 實際被採納?被無視的長什麼形狀?降噪規則應該從那裡長出來,而不是從這 50 個 PR。

二、把這套 harness 留下來當回歸基線。 50 個 PR、驗證過的 golden、計分腳本都在。它已經被我看過答案,不能拿來當優化目標;對著它調參,就是對著一套已知有偏差的 KPI overfitting。不過,若固定資料集、模型、judge、prompt 與計分版本,它仍可作為相對回歸訊號。之後每次改 skill,定版時跑一次,同時比較 precision、recall、F1 與個別 PR 的變化,並把附錄裡估出的 4~5 分視為雜訊範圍,而不是把每一次升降都當成真的進步或退步。

這次跑完花了多少

413.81 美金、5.55 億 tokens。 算到 50 個案例跑完為止。

我是訂閱制,這筆錢沒有真的扣走,是把用掉的 token 乘上 API 牌告價得到的數值。

Day 1 那個單次 5.75 美金的中位數也是同一把尺量的;它怎麼取樣、後來怎麼變,留到最後一天更新數字時一起講。

雲端 Claude Code 的 /usage 計價結果明顯錯誤,所以以下資訊是由 ccusage 以及請 Claude Code 自己依照 jsonl 檔案紀錄回推、統計,並且相互驗證的。

項目 tokens USD 金額佔比
cache read 503,449,513 251.72 62.7%
cache write 17,177,557 116.85 29.1%
output 1,313,375 32.83 8.2%
input 48,289 0.24 0.1%

(上表是主力的 Opus 5,另外跑雜項的 Sonnet 5 花掉 12.17 美金。)

平均每個 PR 8.28 美金、1,110 萬 tokens。

Day 12 的單一 MR 是 12 美金,benchmark 的 PR 小一些,量級合理。

快取命中率 96.5%。沒有快取的話,同一批工作要 2703 美金,會是 6.5 倍。

$2703 是把所有 cache token 當一般 input 重算下所得出的金額。

時間拆開來看:從開工到收工橫跨 8 小時,真正在運算的只有 3.5 小時,其餘都在等我下指令。不過平均同時有 7 條在跑,所以把每條各自的運算時間加起來是 25 小時,其中 22 小時發生在 subagent 裡。

附錄:這次量測的限制

上面的每個數字都受這幾件事影響,照實列出:

  • 驗證員偏軟、會前後不一致,而且跟受測工具是同一個模型家族。 對 1,395 條 claim 判了 86.5% 為真;Claude 驗 Claude,匿名擋得住「認出自己」,擋不住「偏好同一種風格」。五個 PR 意外各跑了兩次,自我一致率 81~97%;其中 keycloak-38446 的七次改判全部朝同一邊、全部對我有利,被計分的是第二次。所以 16.8% 的 golden 失效率和校正後的所有數字,精度請當作「幾個百分點」。
  • 原始配對 judge 的盲測是部分的。 這是負責比對 candidate 與 golden 是否為同一問題的 agent,不是前面匿名驗證 1,395 條 claim 的那個驗證員。它沒有對話 context,也不讀 diff;但派工資料直接帶著工具名,而五個市售產品名裡混入一個個人 skill 名,要猜出哪個是受測者並不難。有沒有實際影響,這次沒有量測。
  • 配對 judge 的校準沒涵蓋關鍵那格。 拿五個同業對上游三個 judge 公布的結果校準,最大偏差 3.1 個百分點,小於上游 judge 彼此的最大歧異 4.5 個百分點,雜訊底線因此定在 4~5 分。但受測工具沒有上游分數可對,那一格沒有外部錨點。
  • 模型世代與訓練汙染分不開。 同業是 2026 年初的模型跑的,我用 Opus 5;這些又是公開的舊 PR,我的模型可能看過。最接近的對照組是同家族的 claude-code(raw F1 35.9%、校正後 60.1%),但 skill 和模型的貢獻在這個實驗裡分不開。
  • skill 沒有完整跑。 掃描器只有 ruff 能動,Java / Go / Ruby / TypeScript 沒有靜態分析;ncr-fresh-eyes 只跑成 10/50,ncr-quality-check 0/50(雲端環境跟我說 subagent 裡不能再派 subagent)。因此這次分數代表的是不完整的執行路徑;若補齊這些階段,precision、recall 與 F1 會如何變化,仍需重新實驗才能知道。

本日小結

原始分數最後一名,校正後第一名,兩個都是真的,也都不是重點:名次取決於你挑哪組算法,而四組算法每一組都有偏向。站得住的只有三條:golden 有 16.8% 沒通過驗證;通過驗證的那 114 條我們找到最多;每講一條話的效率我們最低。

另外賺到一課,而且賺了兩次:算法是我訂的,它就會往我這邊偏,偏的時候我自己看不出來。第一次是別人抓到的,第二次是自己撞上的。

評測到這裡收尾。下一個問題跟分數無關:這套 skill 裡的規則,一條一條都是踩坑加出來的,那它們要留到什麼時候?明天講:規則什麼時候該拿掉。


上一篇
Day 14|Skill 的評測(上):竟在 Benchmark 實測中拿了最後一名 😱
系列文
AI 的駕馭之道:一個 AI Code Reviewer 的養成、評測與邊界實錄15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言