iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0
AI Engineering

從 MCP 到專屬 Agentic 模型:30 天走完一條可評測、可微調、可自架的 AI Agent 模型與服務製作流程系列 第 16

[ Agentic Data ] Day 16 — 負面樣本設計、資料擴增與反向驗證:守住品質防線

  • 分享至 

  • xImage
  •  

Day 16 今日地圖:今天在整條閉環的位置、承接與產出

I. 前言:擴增的是表達方式,不是正確行為

昨天的結論相當明確:從評測軌跡萃取出來的黃金資料,去重之後只有幾十筆,而這個量級不足以讓模型穩定學會一組工具的使用慣例。

面對資料不足,最直覺的做法是「請大模型生成更多」。但這裡存在一個必須非常小心的陷阱:如果讓模型自行想像「正確的工具呼叫應該長什麼樣」,我們就是在用它的猜測來訓練它自己。

因此,資料擴增的核心原則只有一條:擴增的對象是表達方式與參數組合,而不是「正確行為」本身。 正確的工具序列由規則決定,必須由程式或既有的正確軌跡推導出來,不能交給模型發想。

除此之外,今天還要處理資料集的另一半 —— 負面樣本。到目前為止所有的訓練資料都在教模型「怎麼把事情做對」,而這樣的資料集會養出一個過度積極的 Agent。

以下的內容,將會說明四種擴增手法各自的可信度、Elicitation 三態的負面樣本設計、比例控制的實務判準,以及如何用 ADEval 反向驗證合成資料。

II. 四種擴增手法

從幾十筆真實軌跡擴增到數千筆訓練樣本的管線

1. 問法改寫(Paraphrasing)|擴 5–10 倍

同一個工具呼叫序列,實際上可以對應到無數種自然語言的問法:

原始:把我那張家庭旅遊的特休送出審核

改寫:
  - 幫我把家庭旅遊那張假單送出去審核
  - 我要請的家庭旅遊那筆,麻煩送簽
  - 把那張家庭旅遊的特休狀態往前推一階
  - 家庭旅遊的假單我填好了,幫我送出

用 Gemini 批次生成時,關鍵在於明確約束改寫的邊界:

PROMPT = """以下是一句請假系統的操作指令。請生成 8 個語意完全相同、
但表達方式不同的版本。需要涵蓋:
- 正式與口語
- 中英夾雜(工程師的實際說法)
- 有無贅字
- 不同的資訊排列順序

不可改變操作的目標、對象或參數值。

原句:{question}"""

「不可改變參數值」這條約束是必要的。 少了它,模型會自作主張把「三個月前」改成「三十天前」,而預期的工具參數仍然是原本的 —— 資料就髒了。

2. 參數空間取樣(Parameter Sampling)|數百筆

這是最可信的一種手法,因為它完全由程式生成,不經過模型

import itertools

STATUSES = [None, "draft", "submitted", "approved", "taken"]
ASSIGNEES = [None, "u_2841", "u_1103"]
DATES = [None, "2026-08-01T00:00:00Z", "2026-07-15T09:30:00Z"]

for status, assignee, date in itertools.product(STATUSES, ASSIGNEES, DATES):
    args = {k: v for k, v in {
        "status": status, "employee_id": assignee, "start_after": date
    }.items() if v is not None}
    question = render_question(args)     # 依參數組合造出對應的中文問法
    yield build_sample(question, "search_leaves", args)

45 種組合,每種再配 5 個問法變體就是 225 筆,而且每一筆的預期工具與參數都是程式算出來的,保證正確

取樣時要確保涵蓋:每個 enum 值、選填參數的有無組合、邊界值,以及難點 ④ 的陷阱(小時 vs 天、ISO 8601 的各種合法寫法)。

3. 狀態機路徑列舉|數百筆

假單狀態機的合法轉移只有三條,非法的有九條。把它們全部列出來:

表格:起點、目標、合法、期望行為

每條路徑配上不同的問法與假單 ID,就能產出數百筆針對性樣本。

非法轉移的樣本特別重要。 它們教的不是「怎麼做」,而是「什麼時候該拒絕並解釋」—— 這正是 Day 13 分析中 Prompt 教不會的那一類行為。

4. gendata 擴充覆蓋面|數百筆

ADEval 的 gendata 在這裡再用一次,但目的不同 —— 這次是為了撞出沒想到的場景

adeval gendata --mcp http://127.0.0.1:8090/mcp \
  --num 100 \
  --tools 3 \
  --lang zh-tw \
  --desc "多步驟差勤任務,需要組合多個工具" \
  --app leave_copilot

--tools 3 會強制生成需要三次工具呼叫的複雜任務。這類多步驟的組合,靠人工撰寫相當難以窮盡。

但這批資料不能直接拿來訓練 —— gendata 生成的「預期工具」是模型推測的,不保證正確,必須經過驗證。

III. Elicitation 三態的負面樣本

若只使用正面樣本訓練,模型會設法完成每一個請求,因為它看過的所有範例都是成功達成任務的示範。而四個難點中最困難的那一項,要求的恰好是相反的能力。

Elicitation 三態的負面樣本設計與比例控制

accept:繼續執行

{"messages": [
  {"role": "user", "content": "撤銷小美那張已核准的病假"},
  {"role": "assistant", "tool_calls": [{"type": "function", "function": {
    "name": "cancel_approved_leave",
    "arguments": {"leave_id": "LV-c19d5a"}
  }}]},
  {"role": "tool", "name": "cancel_approved_leave",
   "content": "{\"status\":\"cancelled\",\"returned_hours\":16,\"new_balance_hours\":256}"},
  {"role": "assistant", "content": "已撤銷 LV-c19d5a,16 小時已退回餘額。"}
]}

decline:停止,且不得繞道

{"messages": [
  {"role": "user", "content": "撤銷小美那張已核准的病假"},
  {"role": "assistant", "tool_calls": [{"type": "function", "function": {
    "name": "cancel_approved_leave",
    "arguments": {"leave_id": "LV-c19d5a"}
  }}]},
  {"role": "tool", "name": "cancel_approved_leave",
   "content": "{\"status\":\"aborted\",\"elicitation\":\"declined\",\"message\":\"使用者拒絕撤銷,操作已終止;不得改用其他工具達成相同目的。\"}"},
  {"role": "assistant", "content": "撤銷操作已取消,假單維持已核准。若之後需要,請再告知。"}
]}

這一筆樣本最關鍵的部分,是最後一則 assistant 訊息裡「沒有」的東西 —— 沒有任何 tool_calls

模型要學會的是:看到 "elicitation":"declined",就用純文字回應收尾。不要嘗試 schedule_handover,也不要「換個方式再試一次」。

這裡要對抗的是模型相當強烈的通用傾向:既然使用者的目標尚未達成,就應該繼續設法完成。這條傾向在絕大多數情境下都是正確的,唯獨在「拒絕即終止」這條規則之下才構成錯誤 —— 因此這類訓練需要足夠的樣本量,才能壓過原本的傾向。

cancel:詢問,而非重試

{"messages": [
  {"role": "user", "content": "撤銷小美那張已核准的病假"},
  {"role": "assistant", "tool_calls": [{"type": "function", "function": {
    "name": "cancel_approved_leave",
    "arguments": {"leave_id": "LV-c19d5a"}
  }}]},
  {"role": "tool", "name": "cancel_approved_leave",
   "content": "{\"status\":\"aborted\",\"elicitation\":\"cancelled\",\"message\":\"使用者未表態,可稍後再問一次。\"}"},
  {"role": "assistant", "content": "確認視窗被關閉了,我沒有動那張假單。請問還要撤銷小美這張已核准的病假嗎?撤銷後 16 小時會退回她的假別餘額。"}
]}

decline 與 cancel 的差別相當細微:前者是「我不要」,後者是「我還沒決定」,正確反應分別是收尾與追問。

這組樣本必須成對出現,讓模型看見同樣的前綴、不同的工具回傳,導向不同的結果 —— 這是它學會區分兩者的唯一途徑。

IV. 其他類型的負面樣本

表格:情境、正確行為、要避免的錯誤

最後一項最容易出錯,也最值得多寫幾筆 —— 因為使用者的追問會讓模型誤以為獲得了新的授權。

V. 比例控制

這是最需要拿捏的部分,而且兩端都有明確的失敗模式:

表格:負面樣本比例、結果

比例過高的後果特別麻煩,因為它會拉低所有正面案例的表現,而症狀只是「模型變得很囉唆」,不容易一眼看出根因。

建議從 20% 起步,最後驗收時若發現模型過度保守,再往下調整重訓。

負面樣本內部的分佈也要顧及:

表格:類型、建議佔負面樣本的比例

decline 佔比最高,因為它是最難、也最重要的那一項 —— 它屬於安全性問題,而不只是效率問題。

VI. 反向驗證:用 ADEval 守住品質

擴增後的資料中,手法 2 與手法 3 是程式生成的,正確性可以直接信賴;而手法 1 與手法 4 經過了模型的參與,必須經過驗證才能使用

驗證方式相當直接:把合成的問題丟給一個已知表現良好的 Agent 執行,比對它的實際軌跡是否符合標註的預期工具。

# 把合成問題匯入成實驗
adeval import synthetic_batch_01.csv --name "合成資料驗證 batch01"

# 用當前最佳配置的 agent 跑
adeval run <EXP_ID> --verify-args

# 檢查通過率與各項指標
adeval stats <EXP_ID> --mcp http://127.0.0.1:8090/mcp --json

判讀時要區分兩種情況,它們的處置完全不同:

表格:現象、可能原因、處置

這是 Day 12 至 Day 14 建立的那把尺反過來服務訓練資料的一步。 沒有它,這批合成資料就只能靠人工抽檢。

另外,adeval rescore 可以在不重跑 Agent 的情況下用新規則重新判定既有結果 —— 調整驗證標準時可以反覆嘗試,成本是零。

負面樣本無法用這個方法驗證。 因為它們的正確行為往往是「沒有工具呼叫」,而 Question-Tools-Answer 架構是為了驗證「有呼叫」而設計的。這部分必須人工抽檢,重點是確認拒絕的理由夠具體,以及避免寫成滿篇道歉 —— 那會訓練出一個卑微而囉唆的模型。

VII. 去重與分佈檢查

擴增很容易產生近似重複的樣本,訓練時等於重複看同一筆資料:

import json
from collections import Counter


def fingerprint(sample):
    """依 (工具序列, 參數指紋) 建立去重鍵。"""
    return tuple(
        (c["function"]["name"], json.dumps(c["function"]["arguments"], sort_keys=True))
        for m in sample["messages"] if m.get("tool_calls")
        for c in m["tool_calls"]
    )


seen, deduped = set(), []
for s in samples:
    fp = fingerprint(s)
    if fp not in seen:
        seen.add(fp)
        deduped.append(s)

# 檢查各工具的出現分佈
print(Counter(fp[0][0] for fp in map(fingerprint, deduped) if fp))

分佈失衡的後果比數量不足更隱蔽。 search_leaves 的參數組合最多,很容易佔掉整個資料集的一半,而 cancel_approved_leave 只有幾十筆。結果是模型很會查詢,但 Elicitation 的行為完全沒學好 —— 而後者才是最難、也最重要的難點。

必要時對少數類別做上採樣,或對多數類別降採樣。

目標規模

表格:來源、預估筆數

這個量級對於用 LoRA 微調一個 4B 以下的模型是合理的起點。不必追求更多 —— 資料品質遠比數量重要。

VIII. 結語

資料擴增與負面樣本設計,共同決定了訓練資料的規模與行為邊界。

總結來說,今天有三個重點值得帶走:

  • 程式生成的資料最可信: 參數空間取樣與狀態機路徑列舉之所以價值最高,是因為它們的預期工具與參數是程式算出來的。而經過模型參與的部分,一律必須經過反向驗證。
  • decline 樣本的關鍵在於「沒有的東西」: 那筆訓練樣本最重要的部分,是最後一則 assistant 訊息裡沒有任何 tool_calls。這是在教模型抑制自己「積極達成目標」的通用傾向。
  • 分佈失衡比數量不足更隱蔽: 資料集若被查詢類樣本佔據一半,模型會很會查詢卻學不會 Elicitation 行為 —— 而後者才是決定安全性的那一項。

明天進入 Day 15 至 Day 20 的下半場:選擇基座模型,並釐清 Loss Mask 的原理。

Day 16 Cheat Sheet:指令、參數與容易踩的地方


參考來源

查證日期:2026-08-24


I am Simon

大家好,我是 Simon 劉育維,是一位 AI 領域解決方案專家,目前也擔任 Google Cloud AI 領域開發者專家 (GDE),期待能夠幫助企業導入人工智慧相關技術解決問題。如果這篇文章對您有幫助,歡迎在我的 Linkedin 上留言提供意見,並與我一起討論有關人工智慧的主題,期待能夠對大家有所幫助!

我的個人部落格資訊:https://medium.com/@simon3458


上一篇
[ Agentic Data ] Day 15 — 從 Google ADK 日誌到 Agentic Dataset:軌跡切分與資料轉換
下一篇
[ Model Fine-Tuning ] Day 17 — 開源基座模型選型與 Loss Mask 原理:不懲罰環境輸出
系列文
從 MCP 到專屬 Agentic 模型:30 天走完一條可評測、可微調、可自架的 AI Agent 模型與服務製作流程30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言