Applied AI School
v0 · 規劃中
Anthropic

Code-based vs model-based grading

兩種打分方式的取捨——能用 regex / parser 驗的就用 code,開放性產出才用 LLM。

TL;DR

  • Code grading:能用 regex / JSON parse / 算數驗證的就用——快、便宜、確定
  • Model grading:開放性產出(meal plan、摘要、回信)只能用 LLM 評
  • 混搭:一個 case 同時跑 code(必要條件)+ model(品質);grader 永遠用比 generator 強的模型

一個情境:「這個輸出 8 分」是什麼意思?

你的 grader 跟你說「這個 meal plan 8 分」。你問為什麼,它說「整體還不錯」。

這個分數沒有用。因為你不知道下次怎麼讓它變 9 分。

好的 grader 做兩件事:

  1. 把「好不好」拆成可單獨檢查的條件
  2. 對每個條件給可驗證的判斷(pass/fail 或 1–10)

「整體還不錯」不是 grader,是讀者感想。Grader 必須能精準回答「這次哪一條沒做到」。

三種 grader

類型怎麼打分適合什麼缺點
Code寫程式驗(regex、parse、算數、長度)格式、syntax、必要條件只能驗「規則化」的東西
Model叫另一個 LLM 評品質、相關性、是否照做慢、貴、會 bias
Human真人讀最終品質基準太慢,只用於採樣

實務上是 code + model 混搭:能用 code 驗的條件先過 code(確定性高、便宜),剩下開放性的判斷交給 model。Human 留給離線抽樣校正用。

Code grader:能驗就驗

任何能寫成函式的判斷都是 code grader。常見:

import ast, json, re

def validate_python(text: str) -> int:
    try:
        ast.parse(text.strip())
        return 10
    except SyntaxError:
        return 0

def validate_json(text: str) -> int:
    try:
        json.loads(text.strip())
        return 10
    except json.JSONDecodeError:
        return 0

def validate_regex(text: str) -> int:
    try:
        re.compile(text.strip())
        return 10
    except re.error:
        return 0

其他常見的 code check:

  • 長度:output 不能超過 280 字(推文 generator)
  • 必含關鍵字:summary 必須出現原文的某個專有名詞
  • 不能含某些字:客服回信不能罵人、不能承諾退款
  • 數值範圍:價格估算必須是正數 < 10000

Code grader 的好處:0 token 成本、ms 級延遲、結果 deterministic

Model grader:給開放性產出打分

「這份 meal plan 有沒有照 vegan 限制」「這封回信語氣是不是夠專業」這種 code 寫不出來,要叫 LLM 評。

最關鍵的設計:不要只問「打幾分」。直接問「打幾分」,model 會永遠給 6 分(中間值偏好)。要連 reasoning 一起要:

GRADER_PROMPT = """
你是嚴格的飲食教練。評估以下 AI 產生的 meal plan:

User 輸入:{user_info}
限制:{constraints}
AI 輸出:{output}

評估標準:
1. 是否嚴格遵守限制(vegan / 過敏 / 熱量目標)
2. 是否提供完整 7 天 × 3 餐
3. 每餐是否標明份量

回 JSON:
{{
  "strengths": ["最多 3 點"],
  "weaknesses": ["最多 3 點"],
  "reasoning": "為什麼給這個分數",
  "score": 1-10 整數
}}
"""

實作(沿用 prefill + stop_sequence 拿乾淨 JSON):

def grade_by_model(test_case, output):
    prompt = GRADER_PROMPT.format(
        user_info=test_case["user_info"],
        constraints=test_case["constraints"],
        output=output,
    )
    messages = [
        {"role": "user", "content": prompt},
        {"role": "assistant", "content": "```json"},
    ]
    response = client.messages.create(
        model="claude-opus-4-7",  # grader 用比 generator 強的
        max_tokens=1024,
        messages=messages,
        stop_sequences=["```"],
    )
    return json.loads(response.content[0].text)

Rubric 的寫法決定 grader 品質。模糊的 rubric(「品質好不好」)會拿到模糊的分數;具體可驗的條目(「7 天 × 3 餐」「每餐標份量」)才會拿到能行動的反饋。

混搭:一個 case 跑兩種 grader

Code 驗硬規則(必要條件),model 驗品質(充分條件):

def grade(test_case, output):
    # Code 端:硬規則
    syntax_score = validate_format(output, test_case["format"])

    # Model 端:開放性品質
    model_grade = grade_by_model(test_case, output)
    model_score = model_grade["score"]

    # 簡單平均,或 syntax 不過直接 0
    if syntax_score == 0:
        return {"score": 0, "reasoning": "Format invalid", **model_grade}
    return {
        "score": (syntax_score + model_score) / 2,
        **model_grade,
    }

一個寫法的取捨:

合併方式行為用在哪
平均兩邊都算大多數情境
加權平均code 70% / model 30%格式比品質重要時
Gating(code 0 → 整體 0)format 不過直接 failoutput 必須是合法 JSON / Python 等

Code vs model 的決策表

你要驗的東西用什麼 grader
是不是合法 JSON / Python / regexCode
長度有沒有超過Code
有沒有出現禁字 / 必含字Code
數值是不是在合理範圍Code
回信語氣專不專業Model
摘要有沒有抓到重點Model
有沒有照 user 的 constraintsModel(除非可規則化)
翻譯是否流暢Model(或人工抽樣)

判斷原則:寫得出 if/else 的就用 code,寫不出來的才上 model

接下來

到這裡你有完整的 eval pipeline——dataset、prompt runner、grader。但「改 prompt」這件事本身還沒講方法論:要怎麼改?一次改幾個地方?怎麼確認改的有效?

下一節進入 Prompt Engineering:把 eval 當實驗台,每次只改一個變數,跑分、比較、保留贏家。沒 eval 沒 PE——這兩件事是同一回事。