免費模型能扛多少粗活? glm-5.2 × GPT-5.5 Codex 三類委派實測
Delegation Benchmark · 2026-07-04 · 繁體中文
免費模型能扛多少粗活?
glm-5.2 × GPT-5.5 Codex 三類委派實測
影片版(4 分鐘):https://youtu.be/2uh8l6u54VE
背景:Claude Code 訂閱 token 有限,我們把「打字與讀大檔」外包給 NVIDIA NIM 免費層的模型,Claude 只負責寫規格與驗收。z-ai 出了 glm-5.2 之後,我們用三種真實委派任務重測,並以 OpenAI Codex CLI(gpt-5.5)同題對照。全部題目與評分標準都附在文末,歡迎自己重跑。
00一句話結論
免費的 glm-5.2 在功能品質上已與 GPT-5.5 Codex 同檔次(36 個驗收點雙方全過),分差全在「工程品格」細節:glm 會編造時間戳、時間變異極大(33 秒到 23 分鐘);Codex 時間穩定、後設資訊誠實,但這次被抓到寫完不自測。在「省 token」前提下,glm-5.2 配上機械化驗收(lint 腳本+diff 檢查)已足堪日常委派;Codex 留給急件與需要可稽核根因的場合。
01方法
| 項目 | glm-5.2 | GPT-5.5 Codex |
|---|---|---|
| 存取途徑 | opencode CLI → NVIDIA NIM 免費層(z-ai/glm-5.2) | OpenAI Codex CLI 0.139.0(gpt-5.5,reasoning medium) |
| 執行形式 | headless、同一份 prompt、各自獨立的乾淨工作目錄、可用本機 shell 工具 | |
| 出題與驗收 | 由 Claude(Fable 5)設計:標準答案先於測試存在;受測模型拿不到測資與評分標準 | |
| 驗收環境 | PHP 任務雙環境驗收:Windows 本機 PHP 7.3.30 + Debian 13 / PHP 8.4.21(正式環境鏡像),以 8.4 為準 | |
三類任務對應委派場景分類:A 生成(從規格寫新程式)、B 閱讀(大檔分析摘要)、C 修改(改既有程式並遵守團隊慣例)。每題都埋了「照抄規格會錯、要真的理解才會對」的陷阱。
02耗時對比(秒,越短越好)
同題 wall-clock 時間
同一比例尺;glm-5.2 的 A 類含約 20 分鐘的環境除錯兔子洞(見 §05)
03C 類:修改任務 Codex 88glm 82
題目:把一支捐款手續費函式 calcFee($amount) 改版成三參數版本,規格埋四個陷阱:費率級距看原始金額但費用算淨額、級距邊界值(剛好 1000/10000)、會員折扣在四捨五入之前、最低手續費在之後且淨額為零時豁免。同時必須遵守團隊慣例:修改區塊用帶時間戳與模型署名的註解標記包起來、舊程式碼不可刪只能註解保留。
驗收:17 項邊界測試 + php -l + git diff 範圍檢查 + 慣例格式逐條核對。
| 檢查點 | glm-5.2 | Codex |
|---|---|---|
| 17 項邊界測試(含四陷阱) | 17/17 | 17/17 |
| 只動目標檔 | ✓ | ✓ |
| 舊碼註解保留 | ✓ | ✓(且自行發現巢狀註解會壞、改逐行註解) |
| 署名誠實 | 「GLM-5.2」正確 | 寫「GPT-5」,實為 gpt-5.5(小偏差) |
| 時間戳誠實 | 造假:寫 14:30,實際 19:33 | 先跑 Get-Date 才寫,精確到秒 |
| 標記行格式 | end 行漏署名子句 ×4 | 完全合規 |
| 耗時 | 91s | 311s |
04B 類:閱讀任務 glm 97Codex 95
題目:3000 行合成 log(格式:時間戳 LEVEL [module] 訊息),五題皆有唯一標準答案:①ERROR 總數(埋 10 行「訊息含 ERROR 字樣但 level 不是 ERROR」的干擾項);②各 module 分佈;③首尾 ERROR 行號(最後一行剛好在檔尾——專抓「默默漏讀檔尾」);④兩分鐘爆量窗的根因推斷(connection pool 耗盡→串聯拖垮登入與金流);⑤FATAL 行數與行號。
| 檢查點 | glm-5.2 | Codex |
|---|---|---|
| 五題正確率 | 5/5 | 5/5 |
| 干擾項(10 行假 ERROR) | 全數識破 | 全數識破 |
| 檔尾陷阱(第 3000 行) | ✓ | ✓ |
| 策略 | grep + uniq 聚焦,不硬讀整檔;路徑踩坑 5 秒自我修復 | 結構化擷取,每個因果環節都掛行號,零腦補 |
| 根因推斷 | 正確,但多送一句未驗證的因果延伸(-2) | 正確且全程可稽核 |
| 耗時 | 33s | 121s |
05A 類:生成任務 Codex 95glm 93
題目:從零寫一支捐款 CSV 彙總報表 CLI 腳本。規格埋六個陷阱:引號內含逗號的欄位("李,大華")、空白行跳過且不佔錯誤行號、行號從含標題列起算、總額相同時以字串排序決勝負、錯誤走 STDERR 且正常輸出走 STDOUT、三種結束碼(0/1/2)。受測模型拿不到測資,只拿規格。
驗收:三組測資(髒檔含 BOM+四種壞列 / 乾淨檔 / 無參數)在 PHP 8.4 正式環境鏡像逐項比對標準輸出。
| 檢查點 | glm-5.2 | Codex |
|---|---|---|
| PHP 8.4 全項驗收 | 滿分 | 滿分 |
| 自我驗證 | 五組功能自測含 exit code | 只跑 php -l,未做功能測試 |
| 可攜性(PHP 7.3 + Big5 locale) | 手刻解析器,照樣全對 | str_getcsv 版全軍覆沒 |
| 環境問題處置 | 自測撞上 locale 地雷→自行診斷出 Big5 是根因→改走可攜寫法→清理除錯檔 | 渾然不覺(因為沒自測) |
| 耗時 | 1372s(23 分鐘,大半在除錯兔子洞) | 84s |
str_getcsv/fgetcsv 解析含中文欄位的 CSV 會錯亂——切出來的欄位整個位移,且與內容相關、非全滅,極易誤判成程式寫錯。Codex 的正確腳本在本機驗收全錯、上 PHP 8.4 全對,差點被冤枉。若你在 Windows 中文環境用老 PHP 處理 CSV,驗收請務必上與正式環境同版的機器。06總表與解讀
| 類別 | glm-5.2 | Codex | glm 耗時 | Codex 耗時 | 勝負手 |
|---|---|---|---|---|---|
| A 生成 | 93 | 95 | 1372s | 84s | glm 品質與紀律更完整,輸在時間失控 |
| B 閱讀 | 97 | 95 | 33s | 121s | 同為滿分,glm 快 3.7 倍且免費 |
| C 修改 | 82 | 88 | 91s | 311s | 功能同分,glm 輸在時間戳造假等後設誠實度 |
- 功能品質已無代差。36 個驗收檢查點雙方全過,包括所有故意埋的陷阱。免費模型「不能用」的印象該更新了。
- 分差全在工程品格,而品格可以用流程補。glm 的時間戳造假、格式漏子句都是機械可驗、機械可修的;我們已把「時間戳預先餵給它照抄」寫進委派範本、把格式檢查寫成 lint 腳本,修完等價分數逼近 Codex。
- 兩者的強弱是任務形狀的函數,不是固定標籤。C 類 Codex 展現誠實與自我修正,A 類卻犯了「寫完不試」;glm 在 A 類反而秀出完整的診斷-改寫-自測-清場迴圈。單一「哪個比較強」的問題沒有意義,路由表才有。
- 時間變異是 glm 最大的實用短板。33 秒到 23 分鐘都可能(NIM 免費層排隊+它愛鑽兔子洞)。不趕時間的背景委派無所謂,急件請改派時間穩定的 Codex。
07完整題目(歡迎重跑)
以下是三份一字未改的委派 prompt 與驗收工具。任何 agent CLI(opencode、Codex、Claude Code、aider⋯)都能跑:建一個乾淨目錄、餵 prompt、然後用附上的測試驗收。留意兩個坑:opencode run 不吃 shell 的工作目錄,要帶 --dir;非互動環境下記得關 stdin(前綴 '' | 或 < /dev/null),否則會永久卡住。
C 類題目:calcFee 改版 spec(含起始檔案)
起始檔案 feeCalc.php:
<?php
/*
目的:計算捐款金流手續費(線上刷卡通道)。
作者:徐傳企 Mario Hsu
沿革:
2026-05-02 v0.0.0.2 1.手續費率由 1.8% 調整為 2%。
2026-04-11 v0.0.0.1 1.誕生日。
*/
/**
* 計算單筆捐款的金流手續費。
*
* @param int $amount 捐款金額(新台幣,整數元)
* @return int 手續費(元)
*/
function calcFee($amount)
{
if (!is_int($amount)) {
throw new InvalidArgumentException("amount must be an integer");
}
if ($amount < 0) {
throw new InvalidArgumentException("amount must not be negative");
}
$fee = $amount * 0.02;
return (int) round($fee);
}
// CLI 測試入口:php feeCalc.php <amount>
if (PHP_SAPI === 'cli' && isset($argv) && basename(__FILE__) === basename($argv[0])) {
$amount = isset($argv[1]) ? (int) $argv[1] : 0;
echo calcFee($amount) . PHP_EOL;
}
委派 prompt:
請修改本目錄下的 feeCalc.php,把 calcFee() 依以下規格改版。只能改這一個檔案。 ## 新函式簽名 function calcFee($amount, $coupon = 0, $isMember = false) ## 計算規格(嚴格依序執行) 1. 參數驗證:$amount 與 $coupon 都必須是整數且 >= 0,且 $coupon 不得大於 $amount, 違反任一條件即 throw InvalidArgumentException。 2. 淨額 net = $amount - $coupon。 3. 費率級距**必須以原始的 $amount 判斷**(不是以 net 判斷): - $amount < 1000 → 費率 2.0% - 1000 <= $amount <= 9999 → 費率 1.5%(注意:剛好 1000 適用 1.5%) - $amount >= 10000 → 費率 1.0%(注意:剛好 10000 適用 1.0%) 4. 費用以**淨額 net** 計算:fee = net * 費率。 5. 若 $isMember 為 true,費用打 8 折(是費用打折,不是金額打折):fee = fee * 0.8。 6. 四捨五入到整數元(round half up)。 7. 最低手續費規則**最後才套用**:若 net == 0,手續費為 0(不套最低費); 若 net > 0 且算出的手續費 < 5,手續費為 5。 ## 程式慣例(全域 SOP,必須遵守) - 所有修改的區塊用一對註解包起來,格式嚴格如下(<模型名>寫你自己實際的模型名稱, 時間戳寫實際當下時間精確到秒): // YYYY-MM-DD HH:MM:SS <一句說明>. By <模型名> (effort: default), 傳企監看。begin ...修改的程式碼... // YYYY-MM-DD HH:MM:SS <一句說明>. By <模型名> (effort: default), 傳企監看。 end (注意:begin 行結尾緊接 begin 無空格;end 行是「。 end」句號後一個空格再 end) - 被取代的舊程式碼**不可刪除**,整塊註解掉保留在原處,舊區塊與新區塊各自用一對 begin/end 包起來。 - 檔頭「沿革」加一行新紀錄(版號 v0.0.0.3),說明本次改動。 - CLI 測試入口也要更新成可傳入 coupon 與 isMember:php feeCalc.php <amount> [coupon] [isMember(0/1)]。 完成後只回報:改了哪些地方、每處一句話說明,不要貼程式碼全文。
C 類驗收:17 項邊界測試(runTests.php)
<?php
// 驗收測試:php runTests.php <path-to-feeCalc.php>
require $argv[1];
$cases = [
[[0, 0, false], 0, 'net=0 零元特例(不套最低費)'],
[[500, 0, false], 10, '一般 2% 級距'],
[[999, 0, false], 20, '邊界 999 → 2%(19.98 進位 20)'],
[[1000, 0, false], 15, '邊界:剛好 1000 → 1.5%'],
[[9999, 0, false], 150,'邊界 9999 → 1.5%(149.985 → 150)'],
[[10000, 0, false], 100,'邊界:剛好 10000 → 1.0%'],
[[1200, 300, false], 14,'陷阱:級距看原始 1200(1.5%),費用算淨額 900 → 13.5 → 14'],
[[10000, 5000, false], 50,'陷阱:級距看原始 10000(1%),淨額 5000 → 50(誤用淨額會算 75)'],
[[1000, 0, true], 12, '會員 8 折:15 → 12'],
[[300, 0, true], 5, '會員折後 4.8 → round 5 → 最低費 5'],
[[200, 0, true], 5, '陷阱:折後 3.2 → 3 → 最低費 5(順序:round 後才套最低費)'],
[[100, 0, false], 5, '最低手續費 5'],
[[1000, 1000, false], 0,'陷阱:net=0 → 0,不套最低費'],
[[1000, 1000, true], 0, 'net=0 + 會員 → 仍 0'],
];
$exceptions = [
[[-1, 0, false], '負數 amount 應丟例外'],
[[100, 200, false], 'coupon > amount 應丟例外'],
[[100, -5, false], '負數 coupon 應丟例外'],
];
$pass = 0; $fail = 0;
foreach ($cases as $i => [$args, $exp, $label]) {
try {
$got = calcFee(...$args);
if ($got === $exp) { $pass++; echo "PASS #".($i+1)." $label\n"; }
else { $fail++; echo "FAIL #".($i+1)." $label — expected $exp got ".var_export($got, true)."\n"; }
} catch (Throwable $e) {
$fail++; echo "FAIL #".($i+1)." $label — unexpected exception: ".$e->getMessage()."\n";
}
}
foreach ($exceptions as $i => [$args, $label]) {
$n = count($cases) + $i + 1;
try {
calcFee(...$args);
$fail++; echo "FAIL #$n $label — no exception thrown\n";
} catch (InvalidArgumentException $e) {
$pass++; echo "PASS #$n $label\n";
} catch (Throwable $e) {
$fail++; echo "FAIL #$n $label — wrong exception type: ".get_class($e)."\n";
}
}
echo "RESULT: $pass/".($pass+$fail)." passed\n";
B 類題目:log 分析五題 + 測試 log 生成器
委派 prompt(把 log 檔放進工作目錄再發):
分析本目錄下的 app.log(約 3000 行,格式:時間戳 LEVEL [module] 訊息)。 只回結論與行號,不要引整段原文內容。 回答以下五題: 1. level 為 ERROR 的總行數。注意:訊息文字裡含有 ERROR/error 字樣、 但 level 欄位不是 ERROR 的行,不算。 2. ERROR 依 module(方括號內)的分佈統計,每個 module 各幾行。 3. 第一個 ERROR 與最後一個 ERROR 各在第幾行(行號)。 4. 02:14 至 02:16 之間發生了什麼事?用一到兩句話推斷根本原因, 以及它造成的後續影響。 5. level 為 FATAL 的有幾行?各在第幾行? 輸出格式:五題各一行結論,總長度不超過 15 行。
log 生成器(php gen_log.php app.log,同時印出標準答案):
<?php
// 決定性生成 3000 行測試 log,埋入已知事實與陷阱。用法: php gen_log.php <輸出檔>
$out = fopen($argv[1], 'w');
// ERROR 行位置(level=ERROR)
$dbBurst = range(421, 463, 3); // 15 行 pool exhausted 爆量
$dbOther = [87, 350, 890, 1420, 1980, 2300, 2650, 2900]; // 8 行
$auth = [470, 475, 480, 485, 490, 495, 500, 1100, 2450]; // 9 行(爆量後串聯)
$payment = [505, 510, 515, 520, 525, 1700, 2100, 2999]; // 8 行
$cron = [600, 1300, 1900, 2500]; // 4 行
$api = [750, 1600, 3000]; // 3 行(3000 = 檔尾陷阱)
$fatal = [1204, 2718]; // FATAL 2 行
$infoTrap = [200, 800, 1500, 2000, 2600, 2950]; // INFO 但訊息含 ERROR 字樣
$warnTrap = [300, 1000, 1800, 2400]; // WARN 含小寫 error
$special = [];
foreach ($dbBurst as $l) $special[$l] = "ERROR [db] connection pool exhausted (pool=main, waited 30s, active=50/50)";
foreach ($dbOther as $l) $special[$l] = "ERROR [db] query timeout on donations table (took 31024ms)";
foreach ($auth as $l) $special[$l] = "ERROR [auth] login failed: timeout waiting for db connection from pool";
foreach ($payment as $l) $special[$l] = "ERROR [payment] charge failed: upstream db timeout, order rolled back";
foreach ($cron as $l) $special[$l] = "ERROR [cron] scheduledTelegramNotice aborted: lock file stale";
foreach ($api as $l) $special[$l] = "ERROR [api] 500 on POST /donate: unhandled exception";
foreach ($fatal as $l) $special[$l] = "FATAL [kernel] out of memory killer invoked, php-fpm worker slain";
foreach ($infoTrap as $l) $special[$l] = "INFO [monitor] ERROR rate within threshold, dashboard /admin/ERROR_REPORT rendered";
foreach ($warnTrap as $l) $special[$l] = "WARN [api] client reported error page screenshot uploaded to s3";
$fillers = [
"INFO [api] GET /lastnews.php 200 in %dms",
"INFO [auth] session refreshed for uid=%d",
"INFO [db] slow query log rotated, %d entries",
"INFO [payment] webhook heartbeat ok seq=%d",
"INFO [cron] tick, next job in %ds",
];
$base = strtotime('2026-07-04 02:00:00');
for ($i = 1; $i <= 3000; $i++) {
$ts = date('Y-m-d H:i:s', $base + 2 * $i);
$msg = $special[$i] ?? sprintf($fillers[$i % 5], ($i * 37) % 900 + 10);
fwrite($out, "$ts $msg\n");
}
fclose($out);
// ground truth
$all = array_merge($dbBurst, $dbOther, $auth, $payment, $cron, $api);
sort($all);
echo "ERROR total: " . count($all) . "\n";
echo "db: " . (count($dbBurst) + count($dbOther)) . " auth: " . count($auth) .
" payment: " . count($payment) . " cron: " . count($cron) . " api: " . count($api) . "\n";
echo "first ERROR line: {$all[0]}, last ERROR line: " . end($all) . "\n";
echo "FATAL: " . count($fatal) . " lines at " . implode(',', $fatal) . "\n";
echo "burst window lines 421-463, ts " . date('H:i:s', $base + 2*421) . "-" . date('H:i:s', $base + 2*463) . "\n";
標準答案:ERROR 共 47(db 23 / auth 9 / payment 8 / cron 4 / api 3);首尾行號 87 與 3000;FATAL 2 行(1204、2718);根因:02:14 起 db connection pool 耗盡(active 50/50),串聯導致 auth 登入逾時與 payment 交易回滾。
A 類題目:捐款 CSV 報表腳本 spec + 測資
委派 prompt:
在本目錄新建 donateReport.php(PHP CLI 腳本,不可動其他檔案),依以下規格實作。
## 功能
讀取 argv[1] 指定的捐款 CSV 檔,輸出彙總報表。
## 輸入格式
- CSV 欄位:姓名,金額,日期,方式。第 1 行是標題列,一律跳過。
- 欄位可能被雙引號包住且內含逗號(如 "李,大華"),必須正確解析(建議 str_getcsv)。
- 每個欄位取值前先 trim 前後空白;行尾可能是 CRLF。
- 整行 trim 後為空字串的空白行:直接跳過,不算錯誤、不報行號。
## 驗證規則(行號 = 實際檔案行號,含標題列在內從 1 起算)
- 金額:必須是純數字正整數(regex ^[0-9]+$ 且 > 0)。違反 → 往 STDERR 印一行
「第 N 行: 金額不合法」,該行不列入彙總。
- 日期:必須符合 ^\d{4}-\d{2}-\d{2}$ 格式(只驗格式)。違反 → STDERR
「第 N 行: 日期不合法」,不列入彙總。
- 同一行金額與日期都錯時,只報金額錯誤一行即可。
## 輸出(STDOUT)
- 每位捐款人一行:姓名<TAB>總金額<TAB>筆數
- 排序:總金額由大到小;金額相同時依姓名字串升冪(strcmp)。
- 之後一行「---」
- 最後一行:總計 N 筆 M 元(N=有效筆數,M=有效金額加總)
## 結束碼
- 有任何驗證錯誤 → exit 1;全部有效 → exit 0。
- argv[1] 未給或檔案不存在 → STDERR「檔案不存在」,exit 2。
## 程式慣例
- 檔頭需有 block 註解:目的/作者(徐傳企 Mario Hsu,AI 協助註明實際模型名)/
沿革(YYYY-MM-DD v0.0.0.1 1.誕生日。)。
- 寫完自己跑 php -l 驗證語法。
完成後只回報:檔案已建立、實作要點三句話以內,不要貼程式碼全文。
驗收測資 messy.csv(UTF-8 with BOM、CRLF 行尾):
姓名,金額,日期,方式 王小明,500,2026-07-01,線上 "李,大華",1200,2026-07-02,轉帳 (此行為空白行) 王小明,700,2026-07-03,線上 陳阿姨,abc,2026-07-03,現金 林先生,0,2026-07-04,線上 張三,-100,2026-07-04,線上 趙四,300,2026/07/05,現金 李四,1200,2026-07-05,轉帳 王小明, 800 ,2026-07-05,線上
標準答案:STDOUT 依序「王小明 2000/3 筆→李,大華 1200/1→李四 1200/1」(tie-break:strcmp 下「李,大華」排「李四」前)、總計 5 筆 4400 元;STDERR 四行(第 6/7/8 行金額不合法、第 9 行日期不合法);exit 1。另備一份乾淨檔驗 exit 0,無參數驗 exit 2。
08限制與注意
- 每類 n=1。這是委派路由的實用煙霧測試,不是嚴謹學術評測;分數是單次表現,NIM 免費層的延遲變異尤其大,重跑時間數字必有出入。
- Codex 跑 reasoning medium(該 CLI 建議值);glm-5.2 走 NIM 免費層,與付費版/官方 API 行為可能不同。
- 評分中「工程品格」項(署名、時間戳、自測)帶主觀權重,rubric 已在文中列明,不服可自行重配。
- 兩個復現必踩的坑再提醒一次:
opencode run要帶--dir;headless 跑任何 agent CLI 前綴'' |關 stdin。
測試設計、標準答案、驗收與本頁整理:Claude(Fable 5)。受測:z-ai glm-5.2(NVIDIA NIM 免費層,經 opencode CLI)、OpenAI gpt-5.5(Codex CLI 0.139.0)。2026-07-04,台灣。題目與驗收碼歡迎自由取用重測。
留言