早就決定要寫窄的 unverifiable、mismatch 兩行定義,照事先 commit 的三條成功條件跑完四批,答案是不能放行,precision 確實升到 45.65% 與 42.67%,卻沒到事先訂的 50%,recall 掉了仍守住七成,run-all 四批都輸出「擋下」,窄定義也不併進 prompt。
需求釐清 agent 整理 ticket 時,除了寫 Goal、Scope in、Scope out,還會在 AC 標註區塊替有問題的驗收條件 AC 標上類別,類別有三種,彼此衝突 conflict、無法驗證 unverifiable、與描述不符 mismatch。整份 dataset 跑下來,它標出的標註大約每三個才有一個人工也標了,115 個多標裡有 104 個落在後兩類,看卡的人碰到這種比例,已經在放棄整個區塊的邊緣。
於是決定往 precision 讓,把後兩類的定義寫窄,界線是 recall 不掉破七成。
改動只有兩行,放在 prompts/narrow-definitions.txt,其餘 23 行與 src/agent/prompt.ts 一字不差(原版第 18–19 行、窄定義第 13–14 行):
原版
- unverifiable:這條 AC 沒有寫清楚怎樣算做完
- mismatch:這條 AC 跟 description 說的不一樣
窄定義
- unverifiable:這條 AC 只用形容詞描述,寫不出做完時可以觀察的行為或數字;寫了具體行為的不算
- mismatch:這條 AC 跟 description 裡某一句講的相反或對不上;description 沒提到這條 AC 的事不算
跑任何一批之前,這份對照紀錄先寫下三條成功條件,k 是同一筆 case 重跑的次數:
run-all 判定單趟通過的意思是,最終卡片每個受評欄位都符合 goal state,也就是人工事先寫好的通過條件;AC 標註要和人工標的集合完全相同,多一個、少一個都不算。
成立之後怎麼處理也先寫好了:
1、2 都成立,叫做「診斷變好」,可以把窄定義併進
prompt.ts;3 成立才叫「放行」。
這段條件在 commit dd91f23 進版控,時間是 2026-10-01 10:37:13,判定擋或放的程式 ae1e54a 在 10:37:44 進版控,計入結果的四批 10:44 開跑。之後這份文件只多了結果那一段,條件一個字都沒動。
原版與窄定義各跑兩批,每批 16 筆、k=5,模型 gpt-5.4-mini,開著 --fill-missing。A5 與 C5 是一組,A6 與 C6 是另一組,同組兩批相隔 2 秒開跑。
下表的計數單位是條號加類別。agent 標出、人工也事先標進 goal state 的叫對上,只有 agent 標的叫多標,只有人工標的叫漏標,precision 是對上占 agent 所有標註的比例,recall 是對上占人工標註的比例:
| 批次 | prompt | 完成/預定 | 通過趟數 | 對上/多標/漏標 | precision | recall |
|---|---|---|---|---|---|---|
| A5 | 原版 | 80/80 | 4 | 116/222/19 | 34.32% | 85.93% |
| C5 | 窄定義 | 80/80 | 10 | 105/125/30 | 45.65% | 77.78% |
| A6 | 原版 | 80/80 | 6 | 115/199/20 | 36.62% | 85.19% |
| C6 | 窄定義 | 80/80 | 6 | 99/133/36 | 42.67% | 73.33% |
比對的第一步是核對每批完成幾趟,四批都是 80/80,其中 5 趟靠 --fill-missing 補上錄製時那句固定回答,agent 讀到的內容沒有變。
另有一個不是抖動的問題,第一次開跑時兩批在同一毫秒啟動,寫進同一個目錄互相覆蓋,那兩批作廢,接著要開的兩批也停掉;run-all.ts 第 33–35 行改成目錄已存在就直接停下。
precision 同一個 prompt 兩批之間差 2.30(原版)與 2.98(窄定義)個百分點,配對差距是 C5 高 11.33、C6 高 6.05,方向相同、都大於 2.98,照事先約定的判法算改動造成。recall 同 prompt 兩批差 0.74 與 4.44,配對差距是 -8.15 與 -11.85,同樣方向一致、都超過 4.44,照同一個判法算是掉了。
差值都由原始計數算出,例如 recall 的 4.44 是 (105−99)/135。這個判法是事先約定的工程規則,不是統計檢定。
多標按類別看,減少的主要是 unverifiable 類,從 117 到 123 個降到 36 到 44 個;mismatch 類沒有一致減少。兩行是在同一個變體裡一起改的,分不出每一行各自的效果。
條件 1 不成立,precision 升了,兩批卻停在 45.65% 與 42.67%,都低於 50%。條件 2 成立,窄定義兩批的 recall 是 77.78% 與 73.33%,都還在七成以上,但這只是這個時段量到的,同一個原版前一晚跑的 recall 就比這次低約 7 點,73.33% 那批離七成只剩 3 點多。
條件 3 交給 run-all 判定,report-all.ts 的 releaseDecision 逐筆 case 分類:
for (const r of rows) {
if (r.passes < r.completed) failed.push(r.caseId);
else if (r.completed < r.k) incomplete.push(r.caseId);
else passed.push(r.caseId);
}
return {
release: failed.length === 0 && incomplete.length === 0,
完成的趟裡有一趟沒過,這筆 case 記為判定失敗;完成的都過、趟數卻不足 k,記評估未完成;兩邊都是 0 筆才放行。四批總表的放行判定(A5、C5、A6、C6 第 30–34 行)都是同樣兩行:
擋下 門檻 pass^5 16/16,這批 pass^5 0/16
判定失敗 16 筆 評估未完成 0 筆
照事先寫下的規則,條件 1 沒過,窄定義不併進 prompt.ts;條件 3 沒過,這一版不放行。假如 precision 到了 50%,窄定義可以併進去,但 pass^5 0/16 照樣擋下,併不併和放不放行分開判。
改完定義之後,第一個要看的是沒過的趟裡有多少只差在多標。每趟的 score.json 逐個欄位記下過或沒過,ac_flags 另記漏了哪些、多了哪些;沒過的欄位只有 ac_flags、人工標的又一個都沒漏,這一趟就是只差在多標(對照紀錄):
| 批次 | 沒過的趟 | 只差在多標 | 只有 ac_flags 沒過、但有漏標 |
|---|---|---|---|
| A5 原版 | 76 | 55 | 11 |
| C5 窄定義 | 70 | 38 | 16 |
| A6 原版 | 74 | 54 | 9 |
| C6 窄定義 | 74 | 38 | 21 |
原版沒過的趟約七成只差在多標,窄定義降到約一半。通過的趟數只從 4、6 變成 10、6,有漏標的失敗則從 11、9 變成 16、21。兩版跑的是不同的趟,這些是總數的變化,看不出同一趟從多標變成了什麼。
四批的 token 依 gpt-5.4-mini 單價、input 全按原價換算約 US$1.10,作廢與停掉的批次另計;每批從第一趟到最後一趟 4 分 14 秒到 5 分 47 秒。
unverifiable、mismatch 兩行定義寫窄之前先 commit 三條成功條件,再讓原版與窄定義成對開跑四批。precision 升到 45.65% 與 42.67%,沒到事先訂的 50%;recall 掉了,仍守住七成;run-all 四批都輸出「擋下」,判定失敗 16 筆、評估未完成 0 筆,所以窄定義不併進 src/agent/prompt.ts,這一版也不放行。
多標減少的主要是 unverifiable 類,mismatch 類沒有一致減少。原版沒過的趟約七成只差在多標,窄定義降到約一半,通過的趟數卻沒多出多少,四批 pass^5 仍是 0/16,離結果門檻沒有更近。