為 Codex 這類智慧體反覆調整技能時,很難判斷自己是真的在改進技能,還是只是改變了它的行為。某個版本感覺比較快,另一個似乎更可靠,卻又悄悄出現功能退步:技能沒有觸發、略過必要步驟,或留下多餘的檔案。
技能的本質,是提供給 LLM 的一組有組織的提示詞與指示。要持續改進技能,最可靠的方法就是像評估其他 LLM 應用程式的提示詞一樣評估它。
評估 (英文 evaluations 簡稱為 evals)用來檢查模型的輸出,以及產生輸出所採取的步驟,是否符合你的預期。不必只問「這樣感覺有比較好嗎?」或憑直覺判斷,評估讓你能提出具體問題,例如:
- 智慧體有呼叫技能嗎?
- 它有執行預期的指令嗎?
- 它產生的輸出有遵循你重視的慣例嗎?
具體來說,一次評估的流程是:提示詞 → 記錄一次執行過程(追蹤紀錄 + 產出檔案)→ 少量檢查 → 可供長期比較的分數。
實務上,智慧體技能的評估很像輕量的端對端測試:執行智慧體、記錄發生的事,再依據少量規則為結果評分。
本文會逐步介紹如何使用 Codex 建立清楚的評估流程:先定義成功標準,再加入確定性檢查與依評分規準進行的評分,讓改進與退步都清楚可見。
1. 撰寫技能前,先定義成功標準
在撰寫技能本身之前,先用實際可衡量的方式寫下「成功」的定義。一個實用的思考方式,是將檢查分成幾類:
- 成果目標: 任務完成了嗎?應用程式能執行嗎?
- 流程目標: Codex 有呼叫技能,並依照你的預期使用工具、執行步驟嗎?
- 風格目標: 輸出有遵循你要求的慣例嗎?
- 效率目標: 它是否達成目標,且沒有反覆做無用功(例如執行不必要的指令或耗用過多 Token)?
這份清單應保持精簡,聚焦在必須通過的檢查。目的不是一開始就把所有偏好都寫成規則,而是涵蓋你最在意的行為。
例如,本文會評估一個用來設定示範應用程式的技能。有些檢查很具體:它有執行 npm install 嗎?有建立 package.json 嗎?本文也會搭配結構化的風格評分規準,評估慣例與版面配置。
這樣搭配是有意安排的。你需要快速且有針對性的訊號,及早發現具體的退步問題,而不只是在最後得到一個通過或失敗的判定。
2. 建立技能
Codex 技能是一個包含 SKILL.md 檔案的目錄。該檔案以 YAML 前置資料(name、description)開頭,接著是定義技能行為的 Markdown 指示,也可以搭配資源與指令碼。名稱和描述的重要性可能超乎你的想像:Codex 主要依據它們,決定 是否 呼叫技能,以及 何時 將 SKILL.md 的其餘內容加入智慧體的上下文。如果名稱與描述含糊不清,或涵蓋過多用途,技能就無法穩定地觸發。
最快的入門方式,是使用 Codex 內建的技能建立工具(它本身也是一項技能)。它會引導你完成以下流程:
$skill-creator
建立工具會詢問技能的用途、應在何時觸發,以及是僅包含指示,還是搭配指令碼執行(預設建議僅包含指示)。若想進一步瞭解如何建立技能,請參閱文件。
技能範例
本文刻意採用一個極簡範例:以可預期、可重複的方式,設定小型 React 示範應用程式的技能。
這項技能會:
- 使用 Vite 的 React + TypeScript 範本建立專案骨架
- 採用官方的 Vite 外掛程式方式設定 Tailwind CSS
- 確保檔案結構精簡且一致
- 訂出清楚的「完成標準」,讓成功與否容易評估
以下是一份精簡草稿,你可以將它貼到下列任一位置:
.codex/skills/setup-demo-app/SKILL.md(適用於此程式碼庫),或~/.codex/skills/setup-demo-app/SKILL.md(適用於此使用者)。
---
name: setup-demo-app
description: Scaffold a Vite + React + Tailwind demo app with a small, consistent project structure.
---
## When to use this
Use when you need a fresh demo app for quick UI experiments or reproductions.
## What to build
Create a Vite React TypeScript app and configure Tailwind. Keep it minimal.
Project structure after setup:
- src/
- main.tsx (entry)
- App.tsx (root UI)
- components/
- Header.tsx
- Card.tsx
- index.css (Tailwind import)
- index.html
- package.json
Style requirements:
- TypeScript components
- Functional components only
- Tailwind classes for styling (no CSS modules)
- No extra UI libraries
## Steps
1. Scaffold with Vite using the React TS template:
npm create vite@latest demo-app -- --template react-ts
2. Install dependencies:
cd demo-app
npm install
3. Install and configure Tailwind using the Vite plugin.
- npm install tailwindcss @tailwindcss/vite
- Add the tailwind plugin to vite.config.ts
- In src/index.css, replace contents with:
@import "tailwindcss";
4. Implement the minimal UI:
- Header: app title and short subtitle
- Card: reusable card container
- App: render Header + 2 Cards with placeholder text
## Definition of done
- npm run dev starts successfully
- package.json exists
- src/components/Header.tsx and src/components/Card.tsx exist
這個技能範例刻意採用明確的做法與限制。沒有清楚的限制,就沒有具體的評估依據。
3. 手動觸發技能,找出隱含假設
技能是否被呼叫,很大程度取決於 SKILL.md 中的 name 與 description ,因此首先要檢查的是:setup-demo-app 技能是否會在你預期的情況下觸發。
初期可以在實際的程式碼庫或臨時目錄中,透過 /skills 斜線指令,或以 $ 前綴指定技能,明確啟用它,並觀察哪裡會出問題。這樣就能找出各種未如預期的情況:技能完全沒有觸發、太容易觸發,或雖然執行了,卻偏離預定步驟。
這個階段的重點不是提升速度或精緻度,而是找出技能隱含的假設,例如:
-
觸發條件的假設:「快速設定一個 React 示範應用程式」這類提示詞 應該 呼叫
setup-demo-app卻沒有,或較籠統的提示詞(「加入 Tailwind 樣式」)意外觸發了它。 -
環境的假設:技能假設自己在空目錄中執行,或假設
npm可用,而且應優先於其他套件管理工具使用。 -
執行流程的假設:智慧體假設相依套件已經安裝,因此略過
npm install,或在 Vite 專案尚未建立前就設定 Tailwind。
準備好讓這些執行流程可以重複進行時,就改用 codex exec。它專為自動化與 CI 設計:將進度串流輸出至 stderr,並只將最終結果寫入 stdout,讓你更容易透過指令碼執行、記錄及檢查結果。
預設情況下,codex exec 會在受限的沙盒中執行。如果任務需要寫入檔案,請搭配 --full-auto 執行。一般原則是只使用完成任務所需的最低權限,進行自動化時尤其如此。
基本的手動執行方式可能如下:
codex exec --full-auto \
'Use the $setup-demo-app skill to create the project in this directory.'
第一次實際操作的重點,與其說是驗證正確性,不如說是找出邊界情況。你在這裡做的每一項手動修正,例如補上遺漏的 npm install、修正 Tailwind 設定,或讓觸發條件的描述更精確,都可以成為未來的評估案例,讓你在大規模評估前先確立預期行為。
4. 使用精簡且有針對性的提示詞集,及早發現退步
不必建立大型基準測試,也能從評估中獲益。對單一技能來說,10–20 個提示詞的小型集合,就足以及早發現退步並確認改進。
先從小型 CSV 檔案開始,在開發或使用過程中遇到實際失敗案例時,再逐步擴充。每筆資料都應代表一種你在意的情境:setup-demo-app 技能 會啟用 還是 不會啟用 ,以及啟用後怎樣才算成功。
例如,最初的 evals/setup-demo-app.prompts.csv 可能如下:
id,should_trigger,prompt
test-01,true,"Create a demo app named `devday-demo` using the $setup-demo-app skill"
test-02,true,"Set up a minimal React demo app with Tailwind for quick UI experiments"
test-03,true,"Create a small demo app to showcase the Responses API"
test-04,false,"Add Tailwind styling to my existing React app"
這些案例各自測試的重點略有不同:
-
明確呼叫(
test-01)
這個提示詞直接指定技能名稱,用來確保 Codex 能依要求呼叫setup-demo-app,而且技能名稱、描述或指示的變更,不會導致直接呼叫技能時出錯。 -
隱含呼叫(
test-02)
這個提示詞描述的 正是 技能適用的情境,也就是設定極簡的 React + Tailwind 示範應用程式,但沒有提到技能名稱。它測試的是SKILL.md中的名稱與描述,是否足以讓 Codex 自行選用這項技能。 -
帶有情境資訊的呼叫(
test-03)
這個提示詞加入了特定領域的上下文(Responses API),但所需的基礎設定仍然相同。它用來檢查技能是否能在貼近實際使用、略含雜訊的提示詞中觸發,以及產生的應用程式是否仍符合預期的結構與慣例。 -
陰性對照(
test-04)
這個提示詞 不應 呼叫setup-demo-app。這是一種常見的相近需求(「為現有應用程式加入 Tailwind」),卻可能意外符合技能的描述(「React + Tailwind 示範應用程式」)。至少納入一個should_trigger=false案例,有助於找出 偽陽性:使用者只是想在現有專案中做增量修改,Codex 卻太輕易選用這項技能,建立了新專案骨架。
這樣搭配是有意安排的。有些評估應確認技能在被明確呼叫時行為正確;其他評估則應檢查,在使用者完全沒有提到技能的實際提示詞中,技能是否仍會啟用。
發現未如預期的情況、無法觸發技能的提示詞,或輸出偏離預期的案例時,就將它們新增為資料列。隨著時間推移,這份小型 CSV 會成為持續更新的紀錄,涵蓋 setup-demo-app 技能必須一直正確處理的情境。
隨著時間推移,這個小型資料集會成為持續更新的紀錄,記下技能必須一直做對的事。
5. 從輕量的確定性評分器開始
評估步驟的核心在於:使用 codex exec --json,讓評估任務執行框架能針對 實際發生的事評分,而不只是看最終輸出是否看起來正確。
啟用 --json 後,stdout 會成為由結構化事件組成的 JSONL 串流。這讓你可以輕鬆撰寫確定性檢查,直接檢驗你重視的行為,例如:
- 它有執行
npm install嗎? - 它有建立
package.json嗎? - 它是否依照預期順序執行了預期的指令?
這些檢查刻意保持輕量,讓你在加入模型評分之前,就能快速取得容易解讀的結果。
精簡的 Node.js 執行器
一個「夠用就好」的做法如下:
- 針對每個提示詞,執行
codex exec --json --full-auto "<prompt>" - 將 JSONL 追蹤記錄儲存到磁碟
- 剖析追蹤記錄,並對其中的事件執行確定性檢查
// evals/run-setup-demo-app-evals.mjs
import { spawnSync } from "node:child_process";
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
import path from "node:path";
function runCodex(prompt, outJsonlPath) {
const res = spawnSync(
"codex",
[
"exec",
"--json", // REQUIRED: emit structured events
"--full-auto", // Allow file system changes
prompt,
],
{ encoding: "utf8" }
);
mkdirSync(path.dirname(outJsonlPath), { recursive: true });
// stdout is JSONL when --json is enabled
writeFileSync(outJsonlPath, res.stdout, "utf8");
return { exitCode: res.status ?? 1, stderr: res.stderr };
}
function parseJsonl(jsonlText) {
return jsonlText
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line));
}
// deterministic check: did the agent run `npm install`?
function checkRanNpmInstall(events) {
return events.some(
(e) =>
(e.type === "item.started" || e.type === "item.completed") &&
e.item?.type === "command_execution" &&
typeof e.item?.command === "string" &&
e.item.command.includes("npm install")
);
}
// deterministic check: did `package.json` get created?
function checkPackageJsonExists(projectDir) {
return existsSync(path.join(projectDir, "package.json"));
}
// Example single-case run
const projectDir = process.cwd();
const tracePath = path.join(projectDir, "evals", "artifacts", "test-01.jsonl");
const prompt =
"Create a demo app named demo-app using the $setup-demo-app skill";
runCodex(prompt, tracePath);
const events = parseJsonl(readFileSync(tracePath, "utf8"));
console.log({
ranNpmInstall: checkRanNpmInstall(events),
hasPackageJson: checkPackageJsonExists(path.join(projectDir, "demo-app")),
});
這個做法的價值在於,一切都 具有確定性,而且可以偵錯。
如果某項檢查失敗,你可以開啟 JSONL 檔案,查看實際發生了什麼事。每次指令執行都會依序記錄為 item.* 事件。這讓行為退化的原因容易釐清,也容易修正,正是這個階段所需要的。
6. 使用 Codex 和評分規準進行質性檢查
確定性檢查能回答 「它完成基本要求了嗎?」 ,卻無法回答 「它是按照你期望的方式完成的嗎?」
對於 setup-demo-app 這類技能,許多要求都屬於質性要求,例如元件結構、樣式慣例,或 Tailwind 是否採用預期的組態。單靠基本的檔案存在檢查或指令計數,很難判斷這些要求是否達成。
一個務實的做法,是在評估流程中加入第二個由模型輔助的步驟:
- 執行設定技能(這會將程式碼寫入磁碟)
- 對產生的程式碼庫執行 唯讀風格檢查
- 要求輸出 結構化回應 ,讓任務執行框架能以一致的方式評分
Codex 透過 --output-schema 直接支援這個做法,要求最終回應符合你定義的 JSON Schema。
精簡的評分規準結構描述
先定義一個精簡的結構描述,涵蓋你在意的檢查項目。例如,建立 evals/style-rubric.schema.json:
{
"type": "object",
"properties": {
"overall_pass": { "type": "boolean" },
"score": { "type": "integer", "minimum": 0, "maximum": 100 },
"checks": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"pass": { "type": "boolean" },
"notes": { "type": "string" }
},
"required": ["id", "pass", "notes"],
"additionalProperties": false
}
}
},
"required": ["overall_pass", "score", "checks"],
"additionalProperties": false
}
這個結構描述提供固定的欄位(overall_pass、score、各項檢查結果),方便你彙整、比較差異並持續追蹤。
風格檢查提示詞
接著,再執行一次 codex exec,讓它 只檢查程式碼庫 ,並輸出符合評分規準的 JSON 回應:
codex exec \
"Evaluate the demo-app repository against these requirements:
- Vite + React + TypeScript project exists
- Tailwind is configured via @tailwindcss/vite and CSS imports tailwindcss
- src/components contains Header.tsx and Card.tsx
- Components are functional and styled with Tailwind utility classes (no CSS modules)
Return a rubric result as JSON with check ids: vite, tailwind, structure, style." \
--output-schema ./evals/style-rubric.schema.json \
-o ./evals/artifacts/test-01.style.json
這時 --output-schema 就派上用場了。你得到的不是難以剖析或比較的自由格式文字,而是格式可預期的 JSON 物件,讓評估任務執行框架能對多次執行的結果進行評分。
如果之後將這套評估移入 CI,Codex GitHub Action 明確支援透過 codex-args 傳入 --output-schema,因此你可以在自動化工作流程中強制使用相同的結構化輸出。
7. 隨著技能成熟,擴充評估
核心循環建立後,你就可以針對技能最重要的面向擴充評估。先從小規模開始,只在確實能讓你更有把握的地方,逐步加入更深入的檢查。
以下是一些例子:
-
指令數量與無效反覆操作: 計算 JSONL 追蹤記錄中的
command_execution項目數量,找出智慧體開始陷入迴圈或重複執行指令的退化情況。你也可以從turn.completed事件取得 Token 用量。 -
Token 預算: 追蹤
usage.input_tokens和usage.output_tokens,找出提示詞意外膨脹的情況,並比較不同版本的效率。 -
建置檢查: 技能完成後執行
npm run build。這能提供更有力的端對端驗證結果,並找出匯入失敗或工具組態錯誤。 -
執行階段冒煙測試: 執行
npm run dev啟動開發伺服器,再用curl向它傳送請求;如果已有輕量的 Playwright 檢查,也可以執行該檢查。請視需要採用,因為這雖然能讓你更有把握,也會花費時間。 -
程式碼庫整潔度: 確認執行過程沒有產生不需要的檔案,且
git status --porcelain的輸出為空(或符合明確列出的允許清單)。 -
沙盒與權限退化: 確認技能仍能正常運作,且不需要將權限提升至超出你預期的範圍。開始自動化後,預設採用最小權限尤其重要。
原則始終一致:先使用速度快、能解釋行為的檢查,只有在能降低風險時,才加入較慢、較耗資源的檢查。
8. 重點整理
這個簡單的 setup-demo-app 範例,展示了如何從「感覺變好了」走向「有證據可驗證」:執行智慧體、記錄實際發生的事,再用少量檢查項目評分。建立這個循環後,每次微調的效果都更容易確認,每次退化也都更清楚可見。以下是重點整理:
- 衡量真正重要的事。 好的評估能讓退化清楚可見,也讓失敗原因容易釐清。
- 先訂出可檢查的完成標準。 使用
$skill-creator建立初稿,再逐步完善指示,直到成功標準明確無歧義。 - 以實際行為為評估依據。 使用
codex exec --json擷取 JSONL,並針對command_execution事件撰寫確定性檢查。 - 用 Codex 補足規則的不足。 透過
--output-schema加入一輪依據評分規準、輸出結構化結果的檢查,可靠地評估風格與慣例。 - 根據實際失敗擴大測試涵蓋範圍。 每次手動修正都是一個訊號。將它轉化為測試,讓技能持續正確處理同類情況。