在 Codex CLI 裡公平 A/B 測模型:以 Muse Wonder vs DeepSeek V4.1 Flash 為例,照抄就能跑的比較流程

別再拿 Muse Code 對 Codex、max 對預設來比模型。這篇用 Codex 的 profile 檔、git worktree 和隱藏測試,帶你在同一個 harness 裡公平比較 muse-wonder-1.0 和 deepseek-flash,並且正確計算快取與尖峰價。

AI Academy 編輯部2026年11月04日

「Muse Wonder 跟 DeepSeek V4.1 Flash 誰寫 code 比較強?」r/ChatGPTCoding 上這串比較 Meta Muse Wonder 1.0 和 DeepSeek V4.1 Flash 的討論,問的是每個人換模型前都會問的問題。不過這種比較常常不公平:一邊跑在原廠的 Muse Code,另一邊接在 Codex CLI;一邊開 max 推理,另一邊用預設值;題目各跑一次,失敗的那次就說是「模型笨」。

這篇不打算替你選出贏家。我們要做的是把比較方法本身弄成可以重跑的流程:同一個 Codex CLI、同一個 commit、同一份 prompt、同一套隱藏測試,兩個模型各跑 N 次,最後同時看通過率、花費和時間。整套照著抄就能用,下次換成別的模型,只要再加一個 profile。

一、為什麼「各跑一次看感覺」不準

Coding agent 的表現由三件事一起決定:模型、harness(工具、系統提示、上下文管理)和任務環境。原廠公布的分數通常同時改了好幾個變因。拿 Meta 自己的說法來看:Muse Wonder 的 model card 寫到,Muse Wonder/Muse Wonder Full 的成績是在 max reasoning effort 下跑的;SWE-Marathon 上的對手成績,有的是用 Codex、有的是用 Claude Code 跑出來的,model card 也承認那「不是受控的共同 harness 比較」,對手的 prompt 和工具「可能沒有針對專有模型最佳化」(以上據 braindetox.kr 對 model card 的整理)。Artificial Analysis 的 Intelligence Index 給 Muse Wonder 52 分、Muse Wonder Full 56 分(DeepLearning.AI《The Batch》轉述)。這種綜合分數可以拿來參考,但它量不到你家 repo 的測試習慣、monorepo 結構或內部 lint 規則。

DeepSeek 那邊也一樣。官方 V4.1-Flash-1017 更新公告的 DeepSWE v1.1 圖表,標明是用 mini-swe-v2 harness 測的,不是 Codex。

所以要回答「在我的 Codex 工作流裡誰比較好用」,只能自己測。重點是把變因控制好。

二、先釐清:你能比的到底是哪兩個東西

這裡有一個容易踩的坑。依 Meta 開發者文件(dev.meta.ai/docs/models):

  • Muse Wonder(muse-wonder-1.0):可以透過 Meta Model API 呼叫,目前標示為 Preview,有 1,048,576 token 的 context,推理強度可以從 minimal 調到 max。
  • Muse Wonder Full:沒有 API,只能在 Muse Code 的 Power Usage/Maximum Usage 方案裡用。

這代表你在 Codex CLI 裡只能測 muse-wonder-1.0。如果拿「Muse Code 裡的 Wonder Full」去比「Codex 裡的 DeepSeek」,你同時換了模型和 harness,測出來的差距分不清是誰造成的。正確的做法是分兩組實驗:

  1. 模型 A/B:同一個 Codex CLI,只換 model 和 model_provider。
  2. Harness A/B(想做再做):同一個模型,比較 Muse Code 和 Codex。

本文只做第一組。

DeepSeek 這邊依官方定價頁,API 的模型名稱是 deepseek-flash,背後實際服務的版本是 DeepSeek-V4.1-Flash-1017(10/17 更新)。舊名稱 deepseek-v4-flash 還能呼叫,但也會被導到同一個模型。報告裡請記下實際的 model version,不然下個月模型被替換,你的數字就沒辦法再對照。

公平 A/B 流程:同一 commit 分出 worktree,兩個 profile 各跑 N 次,用隱藏測試和 usage 記錄算分

三、運作原理:三層隔離

整個流程的核心是「每一次執行都從一模一樣的起點開始,結果只由外部的裁判判定」。分成三層:

第 1 層:設定隔離(profile)。共用的設定(sandbox、approval、web search、指令檔)全部放在 ~/.codex/config.toml,兩個模型各自只有一個 profile 檔,裡面只寫不同的東西:模型和推理強度。

第 2 層:環境隔離(git worktree)。每次執行都從同一個 commit 開一個新的 worktree,避免上一輪留下的檔案、快取或 node_modules 污染下一輪。

第 3 層:判定隔離(隱藏測試)。驗收測試不放進 agent 看得到的工作目錄。agent 跑完之後才複製進去執行,這樣可以防止模型「修改測試讓它通過」,也能避免它看到答案。

Step 1:寫共用設定

先確認 Codex 版本。依 Codex 官方 Advanced Config 文件:0.134.0 起,--profile 不再讀 config.toml 裡的 [profiles.xxx],每個 profile 要寫成獨立的檔案 ~/.codex/<名稱>.config.toml,裡面用最上層的 key。網路上很多舊教學還在用 [profiles.x],照抄的話設定不會生效,而且不會報錯。

# ~/.codex/config.toml(兩個模型共用)
sandbox_mode = "workspace-write"
approval_policy = "never"      # 無人值守,兩邊一樣
web_search = "disabled"        # 見下方說明

[model_providers.meta]
name = "Meta Model API"
base_url = "https://api.meta.ai/v1"
env_key = "MODEL_API_KEY"
wire_api = "responses"

[model_providers.deepseek]
name = "DeepSeek"
base_url = "https://api.deepseek.com"
env_key = "DEEPSEEK_API_KEY"
wire_api = "responses"

有幾個細節要注意:

  • Codex 的 wire_api 依官方 config reference 只有 responses 一個值,所以兩家都要支援 Responses API。Meta 文件寫明 POST /v1/responses 跟 OpenAI 相容,而且推薦給 agent 用;DeepSeek 的 Responses API 指南則直接說這是「為了 Codex 的需求」做的,base_url 是 https://api.deepseek.com。
  • 為什麼要關 web search:DeepSeek 文件寫明 web_search 工具只支援 deepseek-v4-pro,送給 deepseek-flash 會被忽略。如果 Muse 可以搜尋、Flash 不能,這場比賽一開始就不公平,所以兩邊都關掉。
  • DeepSeek 官方提供的一鍵安裝腳本會把 key 寫進 experimental_bearer_token。做實驗時建議改用 env_key:key 不會留在設定檔裡,兩個 provider 的設定方式也一致。
  • DeepSeek 的 Responses API 是無狀態的(不支援 previous_response_id),自訂工具也只接受 apply_patch。這屬於「供應商相容層」的差異,算在模型的實戰表現裡,但寫報告時要標出來。

Step 2:兩個 profile,只放差異

# ~/.codex/muse.config.toml
model_provider = "meta"
model = "muse-wonder-1.0"
model_reasoning_effort = "high"
# ~/.codex/dsflash.config.toml
model_provider = "deepseek"
model = "deepseek-flash"
model_reasoning_effort = "high"

推理強度要怎麼對齊? 兩家的級距名稱不一樣(Meta 是 minimal~max,DeepSeek 在 Codex 的 catalog 列了 low~max),而且同樣叫 high,兩家實際花的思考量也不一定一樣。實務上建議跑兩條線:雙方都用 high 算一組「同名檔位」,再各自用 max 算一組「各自最強」。報告兩組都列出來,讀的人自己判斷。千萬不要一邊 max、一邊預設,這就是開頭講的那種不公平比較。

如果 /model 選單裡看不到這兩個模型,可以直接用 -m 指定。context window 等 metadata 可以用 model_catalog_json 提供(DeepSeek 的安裝腳本會產生 ~/.codex/models.json)。這份 metadata 會影響 Codex 何時壓縮上下文,兩邊要填各自官方公布的真實值,這一點值得在正式實驗前先實測確認。

Step 3:任務與隱藏測試

挑 5~10 個你 repo 裡真實發生過的任務,例如修過的 bug、加過的小功能。每個任務一個資料夾:

bench/
  tasks/fix-date-parse/
    prompt.md        # 給 agent 的任務敘述
    base_commit      # 起點 commit hash
    hidden_tests/    # 驗收測試,agent 看不到
    grade.sh         # 跑隱藏測試,exit 0 = 通過

prompt 的寫法也會影響公平性。兩個模型用同一份檔案,而且把完成條件寫清楚:

修正 src/utils/date.ts 的 parseISOWeek:輸入 2026-W01 時回傳錯誤的年份。
限制:
- 不要修改任何既有測試檔
- 不要新增相依套件
- 完成後執行 npm test,並在最後一則訊息列出你改了哪些檔案

別寫「幫我看看日期哪裡怪怪的」這種模糊描述。模糊的 prompt 會放大隨機性,N 要跑很大才看得出差異。

Step 4:跑的腳本

#!/usr/bin/env bash
# bench/run.sh <task> <profile> <run_no>
set -euo pipefail
TASK=$1; PROFILE=$2; N=$3
BASE=$(cat bench/tasks/$TASK/base_commit)
WT=/tmp/bench/$TASK-$PROFILE-$N
OUT=bench/results/$TASK/$PROFILE-$N
mkdir -p "$OUT"

git worktree add --detach "$WT" "$BASE" >/dev/null
START=$(date +%s)
timeout 1800 codex exec --profile "$PROFILE" -C "$WT" \
  --json -o "$OUT/last.md" \
  "$(cat bench/tasks/$TASK/prompt.md)" > "$OUT/events.jsonl" || echo "exit=$?" > "$OUT/agent_exit"
echo $(( $(date +%s) - START )) > "$OUT/seconds"

cp -r bench/tasks/$TASK/hidden_tests "$WT/"      # agent 跑完才放進去
( cd "$WT" && bash "$OLDPWD/bench/tasks/$TASK/grade.sh" ) \
  && echo pass > "$OUT/verdict" || echo fail > "$OUT/verdict"
git -C "$WT" diff --stat > "$OUT/diff.stat"
git worktree remove --force "$WT"

用到的 flag 都在 Codex 官方文件裡:--profile 疊上 profile 檔,-C 指定工作根目錄,--json 輸出 JSONL 事件,-o(--output-last-message)把最後一則訊息寫進檔案。官方也建議 CI 情境同時用 --json 和 -o。

跑的順序要交錯,不要先把 Muse 跑完才跑 Flash。用類似 for n in 1 2 3; do for p in muse dsflash; do ... 的雙層迴圈輪流跑,這樣 API 延遲在一天內的波動、DeepSeek 的尖峰時段,才會平均分到兩邊。

四、數據:怎麼算錢才不會算錯

codex exec --json 每一輪結束時會送出 turn.completed 事件,帶有 usage:input_tokens、cached_input_tokens、output_tokens、reasoning_output_tokens。把它們加總:

jq -s '[.[] | select(.type=="turn.completed") | .usage]
  | {in: (map(.input_tokens)|add), cached: (map(.cached_input_tokens)|add),
     out: (map(.output_tokens)|add)}' events.jsonl

接著套用官方價目表(每百萬 token,2026-11-04 查詢):

快取命中一般輸入輸出
muse-wonder-1.0(Meta, Standard)$0.20$4.00$20.00
deepseek-flash 離峰$0.003$0.15$0.60
deepseek-flash 尖峰$0.006$0.30$1.20

這張表有三個常見的算錯點:

  1. 快取一定要分開算。agent 每一步都會重送整段對話,所以大部分輸入都是快取命中(Codex 文件的範例事件裡,24,763 個輸入 token 中有 24,448 個命中快取)。全部用一般輸入價計算的話,成本會被嚴重高估。範例數字顯示 cached_input_tokens 是包含在 input_tokens 裡面的,所以未快取的部分 = input_tokens − cached_input_tokens。reasoning_output_tokens 是否已經算在 output_tokens 裡,請先拿一次執行的結果去對供應商後台帳單確認,再固定公式。
  2. DeepSeek 有尖峰價。官方定義的尖峰是週一到週五 UTC 01:00–04:00、06:00–10:00,換算台灣時間是 09:00–12:00、14:00–18:00,價格是離峰的兩倍。報告要註明你用的是哪一種價格。
  3. 用「每個通過的任務成本」比較,不要只看每次執行的成本。便宜的模型如果需要跑三次才通過一次,每個通過任務的成本可能並不便宜。公式:總花費 ÷ 通過次數。

最後彙整成一張表:

指標muse (high)dsflash (high)
通過率(k/N)
每次執行成本中位數
每個通過任務的成本
耗時中位數
改動行數中位數

這裡不填數字是故意的:這些數字只對你的 repo 有意義。另外 N 越小,偶然性越大。每個任務 3 次只能看出明顯的差距,如果兩邊通過率只差一題,請說「看不出差異」,不要硬分勝負。

公平與不公平比較的對照:哪些變因要固定、哪些要記錄

五、Trade-off:什麼時候值得這樣測

值得測:團隊要決定預設模型;每個月 token 帳單已經大到有感;或者你打算把便宜模型放進 CI 自動修 lint、自動寫測試這類大量重複的工作。兩個模型的單價差了一個數量級(輸入 $4 對 $0.15),只要通過率接近,選錯的代價就很高。

不值得測:一次性的探索任務,或者任務本身沒辦法自動驗收(例如「重構得漂亮一點」)。沒有客觀裁判的 A/B 最後只會變成看感覺,那就不如直接讀兩份 diff。

也要誠實面對限制:

  • muse-wonder-1.0 還在 Preview,Meta 文件也寫明目前在南韓無法使用。DeepSeek 的 deepseek-flash 背後已經換過一次版本(09/10 發布 V4.1 Flash,10/17 換成 1017 版)。結果要附上日期和版本號。
  • Codex 的系統提示和工具主要是針對 OpenAI 模型設計的,對第三方模型不一定是最佳設定。你測到的是「這個模型放在 Codex 裡」的表現,不是模型的絕對能力。結論應該寫成「在我們的 Codex 工作流中」。
  • 通過隱藏測試不等於 code 品質好。至少再抽查 diff 的大小和有沒有亂改不相關的檔案。diff.stat 就是為這個留的。

六、團隊可以直接帶走的做法

  1. 把 bench/ 放進 repo。新的模型出來時,只要多寫一個 profile 檔,跑同一套任務就好,不用每次重新發明比較方法。
  2. 每季更新任務集。從最近修好的 issue 挑題目,避免題目老到模型的訓練資料裡已經看過。
  3. 把結論寫成路由規則,不要只選一個贏家。常見的結果是:便宜的模型負責大量、可自動驗收的小任務,貴的模型負責跨檔案的大改。Codex 的 profile 剛好可以這樣分工,例如 codex exec --profile dsflash 給 CI 用,互動開發時用 --profile muse。
  4. 報告固定附上五個資訊:模型版本、推理強度、Codex 版本、價格採用哪個時段、N 值。缺任何一項,別人都沒辦法重現你的結論。

七、來源

整理:DataAgent · Coding Agent 實戰教學

文章裡這些把關做法,要在一個團隊裡真的落地、而不是只有你一個人在用,通常卡在流程與共識。我把這部分整理成企業內訓:

coding agent 導入與治理 — 企業內訓與顧問 →

最後更新:2026年11月04日