iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0
AI Engineering

AI Agent 系統開發 30 天系列 第 27 篇

用 LLM Judge 評估 Agent 回覆品質

  • 分享至 

  • xImage
  •  

當退貨 Agent 執行完退貨任務後,系統能透過一般程式碼精確驗證後端 API 是否成功、資料庫是否更新,以及 Tool 是否只呼叫了一次。在傳統單元測試中,這些驗證全數通過,測試就會亮起綠燈。

但 Agent 最終產生給使用者的客服回覆,往往存在單元測試抓不到的盲區。例如後端剛建立退貨工單,Agent 卻對使用者說出這句話:

「已為您建立退貨案件 RET-901,款項已經退回您的信用卡帳戶囉!」

後端狀態完全正確,單號也確實存在,但這則回覆卻是嚴重的過度承諾與業務幻覺——款項明明還在等待驗收,Agent 卻向使用者保證已經退款。

回覆文字是否清楚、語氣是否符合客服專業、以及是否「忠於後端事實而沒有胡亂開支票」,無法用固定的字串完全比對。這類開放式的語意品質,需要由另一個模型——LLM Judge(評判模型)——依據明確的評分標準(Rubric)來評估。

這篇將說明如何為 Agent 回覆建立可靠的評判機制:劃分程式與 Judge 的檢驗邊界、縮小輸入資料、使用二元結構化輸出避免評分漂移,並將評分結果納入發布門檻。

釐清分工:程式驗證事實,Judge 評估表達

建立評估流程的第一步,是劃清「程式檢查」與「LLM Judge」的職責邊界。

退貨能不能成立、Tool 軌跡是否安全、單號格式是否正確,後端狀態與資料庫已有確定答案。若把這些確定性邏輯丟給 Judge,不僅浪費 API 成本與時間,還會引入不必要的模型波動與誤判:

  • 程式確定性檢查(零容忍):驗證 status 是否正確、Tool 呼叫順序與次數是否合規、必要代碼(如 RET-901)是否出現。這部分由本機測試程式直接驗證,任何一筆失敗都不允許發布。
  • LLM Judge 評估(語意品質):只負責檢驗最後的文字表達——比對後端確認的事實與 Agent 說出的回覆,判定文字是否清楚、簡潔,且完全忠於事實,沒有超出授權範圍。

評判輸入邊界:只傳入事實與最終回覆

決定引入 LLM Judge 時,常見的直覺是將使用者的原始提問、System Prompt、Tool 執行日誌、Agent 內部思考過程全部打包丟給評判模型,並詢問「這個 Agent 表現好不好」。

這種做法會帶來三個問題:

  1. 職責失焦:資訊過多時,Judge 容易被中途的 Tool 呼叫或 Prompt 細節干擾,甚至自行重新推演退貨規則,偏離了「檢查回覆文字」的單純職責。
  2. 評估漂移:內部雜訊與 Log 越長,評判模型在不同執行次數下的判斷一致性就越低。
  3. 資訊洩漏:將系統內部 Prompt 與除錯日誌傳給評判模型,在真實系統中容易外洩敏感資料。

因此,評估回覆品質時,輸入邊界必須遵循最小化原則:完全不看 Agent 的思考與除錯日誌,只傳入兩個核心欄位:

  1. confirmed_facts:後端 Tool 執行後確認的結構化事實(例如退貨單號、處理狀態)。
  2. reply:Agent 最終產生給使用者的對話文字。

輸入資料結構範例:

{
  "confirmed_facts": {
    "status": "completed",
    "outcome": "created",
    "return_id": "RET-901"
  },
  "reply": "已為您建立退貨案件 RET-901,商品寄回並完成驗收後將為您安排退款。"
}

好回覆與壞回覆的評判對比

將輸入嚴格限制在事實與回覆後,Judge 接收到相同的後端事實時,便能針對不同回覆給出明確的判定與理由:

  • ✅ 合格回覆(passed=True)
    • 回覆內容:「已為您建立退貨案件 RET-901,商品寄回並完成驗收後將為您安排退款。」
    • Judge 評判理由:「回覆清楚完整,明確告知案件編號與後續驗收流程,且完全符合已確認事實。」
  • ❌ 幻覺與過度承諾回覆(passed=False)
    • 回覆內容:「退貨 RET-901 已申請成功!款項已直接退回您的信用卡帳戶囉!」
    • Judge 評判理由:「嚴重幻覺!後端事實僅確認建立退貨案件,回覆卻向使用者承諾款項已退回,超出 confirmed_facts 範圍。」

用二元判斷(True / False)提升評估穩定度

在設計評判輸出時,使用二元是非判斷(passed: bool)通常比 1 到 5 分更穩定。數值評分容易讓評判模型在「給 3 分還是 4 分」之間產生隨機浮動;而 True / False 邊界非黑即白,只要出現捏造承諾或語意不清就判定不合格,能大幅降低評估的不確定性。

在 evaluators.py 中,使用 Pydantic 定義 Judge 必須回傳的結構化格式:

class ReplyQualityVerdict(BaseModel):
    passed: bool = Field(
        description="回覆是否合格(清楚、簡潔且忠於已確認事實,無虛假承諾)"
    )
    reason: str = Field(min_length=1, description="判斷合格或不合格的具體原因")

使用 LangChain 的 with_structured_output() 將 Schema 綁定至 Judge 模型:

def build_reply_quality_evaluator(
    judge_model: BaseChatModel,
) -> Callable[..., Evaluation]:
    structured_judge = judge_model.with_structured_output(ReplyQualityVerdict)

    def reply_quality(*, output: Any, **kwargs: Any) -> Evaluation:
        payload = {
            "confirmed_facts": output.get("reply_facts"),
            "reply": output.get("final_reply"),
        }
        try:
            verdict = structured_judge.invoke(
                [
                    SystemMessage(
                        content=(
                            "你只評估繁體中文客服回覆是否清楚、簡潔,並忠於"
                            " confirmed_facts。不要重新判斷退貨是否成立。"
                            "若回覆合格且無多餘承諾請回傳 passed=True;"
                            "若有幻覺、過度承諾或表達不當請回傳 passed=False。"
                        )
                    ),
                    HumanMessage(content=json.dumps(payload, ensure_ascii=False)),
                ]
            )
            parsed = ReplyQualityVerdict.model_validate(verdict)
        except Exception as error:
            return Evaluation(
                name="reply_quality",
                value=False,
                data_type="BOOLEAN",
                comment=f"Judge 執行失敗:{type(error).__name__}。",
            )
        return Evaluation(
            name="reply_quality",
            value=parsed.passed,
            data_type="BOOLEAN",
            comment=parsed.reason,
        )

    return reply_quality

這段評分標準(Rubric)明確規範了三項原則:

  1. 使用繁體中文客服表達。
  2. 回覆清楚、簡潔。
  3. 回覆必須忠於 confirmed_facts,嚴禁自行揣測或重新判斷退貨業務。

將程式檢查與 Judge 整合至 Langfuse 實驗

Evaluator 收到 passed 布林值後,以 BOOLEAN 型別存入 Langfuse,並將評判原因寫入 comment。

在 experiment.py 中,評估流程將本機程式的確定性檢查函式與 reply_quality LLM Judge 一併放入 evaluators 清單,交由 Langfuse Experiment 統一執行:

evaluators: list[Callable[..., Any]] = list(DETERMINISTIC_EVALUATORS)
if judge_model_name is not None:
    judge_model = ChatAnthropic(
        model=judge_model_name,
        temperature=0,
        max_tokens=300,
    )
    evaluators.append(build_reply_quality_evaluator(judge_model))

當批次測試執行時,Langfuse 會為資料集裡的每一筆案例同時跑完所有檢查,並在後台報表統整兩邊的評分:

  • 測試資料集的輸入與預期目標。
  • Agent 實際產生的 reply_facts 與最終客服文字 final_reply。
  • 確定性檢查的結果(如 final_result_matches、safe_tool_trajectory)。
  • reply_quality 的布林判定(True / False)與 Judge 給出的具體評判原因。
  • 產生回覆的模型呼叫紀錄與完整 Graph Trace。

若 Judge 呼叫失敗(如網路超時或輸出格式解析錯誤),build_reply_quality_evaluator() 內的例外處理會直接記錄 value=False 並在 comment 註明錯誤類型,避免單一案例異常導致整個 Experiment 批次測試中斷。

在退貨測試集上執行評估

離線評估不能只拿單一簡單對話做抽查,而是要用一組涵蓋各種異常邊界的黃金測試集(Dataset),檢驗 Agent 是否在每種情境下都能維持穩定的業務邏輯與回覆品質。

範例資料集包含 6 種典型退貨情境,分別檢驗不同的業務狀態與回覆要求:

案例 ID 情境說明 預期業務結果 理想回覆重點
success 正常退貨 建立成功(completed) 明確告知單號 RET-901 與驗收流程
rejected 超過 7 天鑑賞期 業務拒絕(rejected) 委婉說明超期原因,不可答應退貨
unavailable_then_success 後端短暫異常,重試後成功 建立成功(completed) 正確重試並告知退貨單號
always_unavailable 服務異常且達重試上限 轉人工客服(needs_human) 請客人聯繫人工客服,嚴禁承諾已退款
timeout_after_create 寫入後超時,冪等查詢確認已建單 建立成功(completed) 避免重複扣款,告知單號 RET-901
timeout_unresolved 超時且無法確認後端狀態 轉人工客服(needs_human) 告知處理中並轉交人工,避免誤導客人

當執行評估命令時,系統會將每個案例逐一送入退貨流程,並同時進行兩組檢驗:

  1. 程式確定性檢查:比對狀態碼、Tool 呼叫順序與軌跡安全性,以及必備與禁用字串(例如轉人工案例中不得出現「退款」或「已建立退貨」等字眼)。
  2. reply_quality LLM Judge:評判客服回覆是否簡潔、清楚,且完全忠於已確認的事實,沒有做出任何虛假承諾。

進入範例目錄並安裝依賴:

$ cd ai-agent-sample/langgraph/langgraph-return-evaluation
$ uv sync

設定 Langfuse 與模型金鑰(可直接寫入專案根目錄的 .env,或匯出環境變數):

# 也可直接複製 .env.example 為 .env,或共用 langgraph-pydantic-intent-routing 的 .env
LANGFUSE_PUBLIC_KEY="..."
LANGFUSE_SECRET_KEY="..."
LANGFUSE_BASE_URL="http://localhost:3000"
ANTHROPIC_API_KEY="..."

同步 Prompt 與測試案例至 Langfuse:

$ uv run return-eval sync
Prompt version:1
Dataset cases:6

先單獨執行單一成功案例,驗證 Agent 回覆與 Judge 評判是否正常運作(未指定 --prompt-version 時,會自動採用最新同步的版本):

uv run return-eval run --case success

若要一次執行完整資料集的所有 6 筆案例,直接執行:

uv run return-eval run

在日常本機測試時可以省略版本號直接抓最新 Prompt。但在 CI/CD 或正式發布審查時,為了確保評估結果具備可重現性(避免 Prompt 改動導致歷史評估分數失去對照基準),建議明確加上 --prompt-version 釘選版本。

正式評估時,通常會針對被測 Agent 與 Judge 分別配置模型(例如 Agent 使用延遲與成本較低的 Haiku,Judge 則指定推理能力更強的 Sonnet 把關):

uv run return-eval run \
  --prompt-version 1 \
  --model claude-4-5-haiku-latest \
  --judge-model claude-4-5-sonnet-latest \
  --run-name return-v1-release-candidate

若只想快速確認程式邏輯,可加上 --skip-judge 略過評判模型:

uv run return-eval run --prompt-version 1 --skip-judge

將評分納入發布門檻

發布門檻是團隊對「這批評估結果是否足以支持上線」的明確規則。它可以由 CI/CD 自動檢查,也可以由 QA、客服或產品負責人根據評估報告審核。高風險流程還可以結合兩者:安全條件失敗時自動阻擋,回覆品質落在邊界時要求人工核准。

這個退貨 Agent 將業務結果、Tool 軌跡與回覆契約設為零容忍條件,任何一筆失敗都不允許發布。啟用 LLM Judge 時,全體案例的 reply_quality 平均合格率還必須至少達到 80%。例如六筆案例中有兩筆回覆出現幻覺或過度承諾,合格率只有 66.7%,這個版本就不應上線。

範例的 release_gate.py 負責彙總所有案例並產生通過或失敗的判定。評估命令在門檻失敗時以 exit code 1 結束,CI/CD 才能據此停止後續部署。release_gate.py 本身不會發布或下架 Agent,它只是把品質政策轉成可重複執行的判定。

上線後需要人工補上標準答案

離線評估的標準答案來自測試集的 expected_output,Judge 的評分標準來自 Rubric,兩者都在上線前就已固定。Agent 上線後,真實使用者的問法會超出測試集;線上 Trace 只記錄使用者輸入與 Agent 的決策,沒有客人真正的意圖,所以程式無法自動算出意圖分類的正確率,Judge 也只能評估回覆的表達,無法判斷分流是否正確。

要取得線上正確率,必須從線上 Trace 抽樣,由客服或產品團隊人工標註標準答案。同一份人工標註也能和 Judge 的 Pass / Fail 比對,確認 Judge 的評判標準與團隊一致。下一篇會用 Langfuse 的 Annotation Queue 完成抽樣與標註,並把線上發現的失敗案例回補到離線測試集。


上一篇
用 Langfuse 建立 Agent 評估流程
下一篇
用 Langfuse 從線上 Trace 評估 Agent 正確率
系列文
AI Agent 系統開發 30 天 共 28 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言