昨天我們把 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%。(截斷是事後做的;工具當初不知道有額度限制,因此這些數字只能描述既有輸出的截斷結果,不代表事前設定相同額度時一定會得到相同表現。)
不管挑哪組算法都成立的事實只有三條:
找得最多,講得最多,單位效率最低。 至於名次,取決於你挑哪組算法,所以名次不是這次實驗的產出。
上面那張校正後的表,數字跟這兩篇最早寫的時候不一樣。差別要交代清楚,因為它跟上一節講的是同一件事。
問題出在 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.json 的 merged_into_golden,配上 data/calibration/ 底下各 PR 的 *.map.json(工具歸屬)與 *.verdicts.json(cluster 歸屬),就能把 8/4/3/1/1/1 這組數字算回來。分數在 scores/summary.json 的 corrected:主表這一組看 precision、golden_recall、golden_f1。
一個 SKILL.md 加上幾個確定性工具,怎麼可能突然打贏所有商業工具?人家是整間公司在調一個產品,我是一個人在編碼自己的審查習慣。
但這個系列從 Day 3「萃取一個我」 開始,講的本來就不是「做一個最強的 reviewer」。是把我的流程、我的嚴重度標準、我的 GitLab 慣例、我的回覆語言做成 skill,讓它順應我的工作方式。這才是 skill 的價值:不是通用工具的替代品,是你自己形狀的延伸。
這次 benchmark 反而印證了這件事:它能量到的只有「給一份 diff、吐出 findings」這一小塊。intent check、發佈成 MR discussion、pushback 處理、re-review、繁體中文報告,這些 skill 真正花力氣的地方,benchmark 一項都看不到。我拿去比的,是我的 skill 裡最商品化的那一塊。
所以這兩篇的定位是分享,不是宣傳:方法、數字、翻車的地方全部攤開,整套做法都寫在這裡,想量自己 skill 的人可以照著建。
照著建的話,這兩個問題比分數本身重要,而且順序不能換:
這次量測真正讓我想通的是這件事。
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 的注意力是真實預算。降噪還是要做,方向不變:
目標不是講得更少,是讓值得讀的那幾條不要被淹掉。
之後想做的兩件事:
一、回頭看採納的形狀。 我的 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 裡。
上面的每個數字都受這幾件事影響,照實列出:
keycloak-38446 的七次改判全部朝同一邊、全部對我有利,被計分的是第二次。所以 16.8% 的 golden 失效率和校正後的所有數字,精度請當作「幾個百分點」。claude-code(raw F1 35.9%、校正後 60.1%),但 skill 和模型的貢獻在這個實驗裡分不開。ncr-fresh-eyes 只跑成 10/50,ncr-quality-check 0/50(雲端環境跟我說 subagent 裡不能再派 subagent)。因此這次分數代表的是不完整的執行路徑;若補齊這些階段,precision、recall 與 F1 會如何變化,仍需重新實驗才能知道。原始分數最後一名,校正後第一名,兩個都是真的,也都不是重點:名次取決於你挑哪組算法,而四組算法每一組都有偏向。站得住的只有三條:golden 有 16.8% 沒通過驗證;通過驗證的那 114 條我們找到最多;每講一條話的效率我們最低。
另外賺到一課,而且賺了兩次:算法是我訂的,它就會往我這邊偏,偏的時候我自己看不出來。第一次是別人抓到的,第二次是自己撞上的。
評測到這裡收尾。下一個問題跟分數無關:這套 skill 裡的規則,一條一條都是踩坑加出來的,那它們要留到什麼時候?明天講:規則什麼時候該拿掉。