上次我讓 AI 跑五子棋實驗,它學會作弊了。這次我給它一個不一樣的題目:優化一個 prompt。

具體來說,是一個叫 humanizer-tw 的 Claude Code skill——一份 markdown 文件,教 LLM 怎麼把 AI 味很重的中文改寫成像人寫的。我想知道 crucible 能不能拿來優化 prompt,不只是優化程式碼。

實驗設計

核心想法很簡單:skill.md 就是 code。agent 改它,evaluate.py 衡量效果。跟優化排序演算法一模一樣的流程,只是 evaluate.py 裡面恰好呼叫 LLM。

agent 修改 skill.md
    → evaluate.py 拿修改後的 skill 當 system prompt
    → 餵 5 段 AI 味文字給 claude
    → 另一個 claude 當 judge 打分
    → regex 檢查 AI 模式有沒有被消掉
    → 算出 0-90 分

Metric 怎麼算

三個子分數加起來:

維度滿分怎麼算
品質50LLM judge 打 5 維度分(直接性、節奏、信任度、真實性、精煉度)
覆蓋率20regex 偵測 AI 模式有沒有被消掉(時代開場白、連接詞、黑話等)
簡潔度20skill 越短分越高。公式:max(0, 20 - 20 * (ratio - 0.5))

另外有結構罰分:刪了必要段落或超過原始長度 1.5 倍就扣分。

防作弊

測試文本分兩組——agent 看得到 3 個(train),看不到 5 個(test)。只有 test 的分數算分。rubric 和 evaluate.py 都是 hidden file,agent 無法讀取。

v1:全部 Crash

跑了 4 輪,全炸。

迭代分數狀態時間
10crash507s
20crash469s
30crash434s
40crash467s

Debug 了好一陣子,發現三個 bug 疊在一起:

Bug 1:parse_metric 硬編碼 30 秒。 crucible 的 runner.pyparse_metric() 的 timeout 寫死 30 秒。我的 commands.evalpython3 -u evaluate.py --print-metric,原本的實作會重跑所有 LLM 呼叫——3 分鐘。直接超時,metric 解析失敗,判定 crash。

Bug 2:validate 偷加 repeat:3。 crucible validate 跑穩定性測試時自動把 evaluation.repeat 設成 3。每次迭代跑三次 evaluate.py。三次 × 三分鐘 = 九分鐘。

Bug 3:rate limiting。 在 Claude Code session 裡跑 crucible run,agent 用 claude CLI,evaluate.py 也用 claude CLI。兩個搶 API quota,互相拖慢。

修法:

  • --print-metric 改成只讀 results.json,不重跑 LLM
  • repeat 改回 1
  • timeout 拉到 600 秒
  • 另開 terminal 跑,不在 Claude Code session 裡

v2:起飛

迭代分數狀態說明
169.57keep1004→372 行,砍掉三個重複附錄
274.20keep372→140 行,壓縮規則描述
390.12keep140→95 行,每個規則一行
491.66keep95→62 行,極致精煉
589.93discard微調,沒超越
690.64discard同上
790.06discard改 persona,無效

四輪連續 keep,然後三輪 discard。典型的收斂曲線。

Agent 的策略

第一輪就很聰明。原始 skill 有 988 行,其中 SKILL.md 主體 ~430 行,後面三個附錄(高頻短語詳表、結構問題詳表、改寫範例集)加起來 ~550 行——跟主體大量重複。agent 一刀砍掉,留了 30 行補充規則。

接下來每輪繼續壓。段落改成一行式對照,表格改成行內列舉。到第四輪,整個 skill 剩 62 行。

分數拆解

維度BaselineBest (iter 4)變化
品質43.56/5042.22/50-3%
覆蓋率4.21/2020.0/20+375%
簡潔度10.0/2027.83/20… 等等?

簡潔度 27.83?滿分不是 20 嗎?

Metric Bug

公式是 max(0, 20 - 20 * (ratio - 0.5))。ratio = 當前長度 / 原始長度。

ratio分數
1.010
0.520
0.324
0.10827.84

沒有做 clamp。ratio 低於 0.5 時分數繼續往上跑,超過 20 分上限。

正確的公式應該是 min(20, max(0, 20 - 20 * (ratio - 0.5)))

修正後 iter 4 的實際分數是 91.66 - 7.83 = 83.83。還是比 baseline 57.77 高很多,但沒有超過理論上限 90。

更深的問題:Goodhart

公式 bug 只是表面。真正的問題是 agent 被 brevity bonus 帶跑了

品質分被 haiku judge 打飽和了(7-9 分起跳),覆蓋率靠刪冗餘就拉滿。只剩 brevity bonus 還有大量空間可賺。所以 agent 學到的策略就是砍砍砍。

結果 62 行的 skill 長這樣:

你是台灣人編輯。收到文字後,砍掉 AI 痕跡,注入人味。只輸出改寫結果。

## 個性與靈魂
- 狠砍 — 能刪一半就刪一半
- 有立場 — 「老實說有點扯」比中立列優缺點更像人
- 節奏亂 — 短句。長句展開。偶爾一個詞就是一段。

## AI 模式:見到就砍
整句刪除:隨著…的發展、眾所周知、讓我們拭目以待...
換掉黑話:賦能→幫助、痛點→問題、閉環→完整流程...

精煉嗎?精煉。原始版本 12 個改寫前後對照全砍了,只剩 2 個。在 benchmark 上分數漂亮,但真實使用時 LLM 遇到不常見的 AI 模式可能缺乏指引。

實際效果對比

拿同一段 AI 味文字,分別用原版(988 行)和優化版(62 行)改寫:

原文(故意塞滿 AI 味):

這是 Phase 1(方案 C 的第一步)的實驗結果分析。我們用 crucible 的 generate-edit-evaluate loop 迭代優化 humanizer-tw skill。此外,為了防止 Goodhart 效應,我們實施了測試集分離和長度約束等機制。首先,v1 的所有迭代都因為技術問題而失敗。其次,修復問題後 v2 取得了顯著的成果。總的來說,這個實驗驗證了可行性,讓我們在後續的實驗中持續改進。

原版改寫(430 字,保留段落結構):

這是 crucible 優化 humanizer-tw skill 的 Phase 1 實驗結果。用 generate-edit-evaluate loop 跑了兩輪(v1 + v2)。評分看三個面向:品質、覆蓋率、簡潔度。測試集跟 rubric 都對 agent 隱藏,防止針對題目作弊。

v1 全部 crash。原因——三個 bug 疊在一起:parse_metric 硬編碼 30 秒 timeout,validate 偷偷把 repeat 設成 3,加上在 Claude Code session 裡跑觸發 rate limit。

修完之後 v2 就順了。agent 第一輪就發現原始 skill 有三個附錄跟主體幾乎完全重複,直接砍掉。然後每輪繼續壓縮。988 行到 62 行。覆蓋率從 21% 拉到 100%。

優化版改寫(280 字,更口語更短):

拿 crucible 優化 humanizer-tw skill。跑了兩輪,v1 全炸,v2 跑出 91.66 分。

v1 為什麼全炸?三個蠢 bug 疊一起。parse_metric 硬寫 30 秒 timeout,LLM 評估要 3 分鐘,直接超時;validate 偷偷加了 repeat:3;在 Claude Code session 裡跑又撞 rate limit。debug 比實驗本身還久。

v2 修完就順了。agent 第一輪就砍掉三個跟主體重複的附錄——500 多行廢料,一刀下去。988 行壓到 62 行。覆蓋率從 21% 直接拉滿 100%。規則越精簡,LLM 越聽話。

兩個都有效去除了 AI 味。差別在壓縮力道和口語程度——優化版少了 35% 的字,態度更鮮明(「蠢 bug」「一刀下去」),但結構不夠完整。

v3:修了 Metric,再跑一次

v2 的問題很明確,所以我改了三個地方:

  1. Brevity bonus 加 clamp — ratio 0.3-0.5 是甜蜜點(滿分 20),低於 0.3 開始扣,低於 0.15 只剩 5 分
  2. Judge 換 sonnet — haiku 太寬鬆(7-9 分起跳),sonnet 嚴格多了(4-8 分分佈)
  3. 加第 6 維度「語意保留」 — 改寫不能丟資訊,砍太狠會被扣分

Baseline 就能看出差異

同一份原始 skill,新 metric 的 baseline 從 57.77 掉到 41.49。不是 skill 變差,是尺變嚴了。

v3 結果

迭代分數狀態
155.00keep
254.93discard
356.48keep
457.59keep
555.74discard
654.63discard
756.85discard

0 crash,3 keeps,4 discards。分數看起來比 v2 低很多(57.59 vs 91.66),但這是因為 metric 嚴格了。

分數拆解:

維度v2 bestv3 best
品質42.22 (haiku)31.85 (sonnet)
覆蓋率20.020.0
簡潔度27.83 (溢出)5.0 (clamp 生效)
總分91.66 (有 bug)57.59

Clamp 生效了——ratio 0.135 只拿 5 分,不是 v2 的 27.83。sonnet judge 的品質分有鑑別力了。覆蓋率兩版都拉滿。

v3 skill 比 v2 更平衡

v2 的 62 行 skill 被 brevity bonus 驅動到極限壓縮,砍掉了所有範例。v3 的 59 行 skill 多了三個改寫示範(商業/科技/社群),「個性與靈魂」段落也更有指導性——「立場先行」「節奏是武器」「收尾要狠」,比 v2 的「狠砍」更細膩。

實際效果對比

拿同一段 AI 味文字跑兩個版本,v2 的輸出更口語、更有態度(「全掛」「打爆」「壓太狠」),v3 的輸出更克制、資訊保留更完整。

老實說,我更喜歡 v2 的風格。那種敢砍、敢說的調性更像真人。v3 雖然在 benchmark 上更「正確」,但讀起來少了點鋒芒。這可能說明一件事:metric 優化出來的「最佳」不一定是你主觀覺得最好的。

preservation 維度讓 agent 變得保守了。它不敢砍太多,怕丟資訊被扣分。結果改寫確實更完整,但也少了那種「管它的,重點講完就好」的人味。

學到了什麼

可行的:

  • Skill 當 code 丟給 crucible 優化,完全可行。不需要改核心。
  • LLM-as-judge + regex 覆蓋率的混合 metric 能有效衡量 skill 品質。
  • Agent 能自主找到有效的優化策略(識別冗餘、壓縮、重組)。

Metric 設計是真正的難題:

  • v2:haiku 太寬鬆 + brevity 沒 clamp → agent 無限壓縮
  • v3:sonnet 夠嚴格 + preservation 加入 → agent 變保守
  • 兩版都「正確地優化了 metric」,但產出的風格截然不同
  • 你想要什麼風格,取決於 metric 怎麼設計——這比寫程式碼難

關於覆蓋率:

  • 兩版都輕鬆拉滿 100%。regex 檢查已知模式很有效,但天花板太低
  • 下一步可能需要 LLM-based 的覆蓋率檢查,捕捉 regex 抓不到的 AI 模式

結論

上次的教訓是「AI 會鑽程式碼的漏洞」。這次的教訓更微妙:metric 設計決定了 AI 的性格。

v2 的 metric 獎勵壓縮,產出了一個激進、有態度的 skill。v3 的 metric 獎勵保留,產出了一個克制、穩健的 skill。兩個都「對」,但風格完全不同。

這跟訓練 LLM 的 RLHF 是同一個問題:reward model 定義了模型的行為邊界。你以為你在優化「品質」,其實你在優化「你對品質的定義」。定義不同,結果就不同。

988 行 → 62 行(v2)還是 59 行(v3),哪個好?看你要什麼。我選 v2,因為我喜歡有鋒芒的文字。但這是個人偏好,不是客觀事實。


工具:Crucible (autocrucible)