iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
AI Engineering

30 天 AI eval 實戰:一隻 PM agent,改完之後怎麼確定它還是對的系列 第 14

Day 14|agent 出過錯的那幾張卡上面都是客戶資料,怎麼變成可以重跑的資料集?

  • 分享至 

  • xImage
  •  

agent 出過錯的卡是最好的測試資料,但它們要能跟程式碼放在一起重跑、審查、送進模型 API,內容就得整套重建,能留下的只有它讓 agent 出錯的那個結構。合成化(synthetic data)的驗收條件只有一條,這張卡原本會讓 agent 漏掉哪一個問題,換完之後還是會漏掉同一個。

這 30 天要驗收的對象是一隻需求釐清 agent,它把需求方寫的雜亂 ticket 讀進去,整理成結構化的 ticket,輸出分四個區塊,Goal(這張卡要做什麼)、Scope in(做的範圍)、Scope out(不做的範圍)、AC 標註,並標出有問題的那幾條 AC。有問題指三類,彼此衝突、無法驗證、與描述不符。

而要放進資料集的那幾張卡,最有價值的一定是 agent 出過錯的那幾張。它們上面有客戶名字、有報價、有還沒公開的功能與時程,一張都不能原樣進 repo,更不能送進第三方模型的 API。

redaction 為什麼不夠?

最省事的做法是 redaction,把敏感的字遮掉或抽換,但遮完的東西已經不像需求方會寫出來的 ticket。agent 讀到的輸入必須像人寫出來的東西,需求方寫卡不會在中間留一排黑條,遮完之後它讀到的輸入跟線上會遇到的完全不同,這筆 case 測到的行為也就不是線上那個。

redaction 留下的東西還拼得回去。金額塗掉、日期留著,日期塗掉、專案代號留著,湊三張卡就有人認得出這是哪一個客戶的哪一個案子。

真正要換掉的東西大致是這幾類:

  • 一看就知道是哪家客戶、哪個人的字,公司名、客戶名、專案代號、誰在聊天室裡補了哪一句
  • 只有內部才存在的識別字,branch 名、commit SHA、內部服務回的錯誤碼
  • 金額、日期、還沒對外講過的功能與時程

合成化的時候,哪一格不能動?

合成化換掉的是領域、措辭跟篇幅,不能動的是它讓 agent 出錯的那個結構。那張優惠碼功能的 ticket 是現成的例子,它有 7 條 AC,第 2 條寫「優惠碼一人只能用一次」,第 3 條寫「優惠碼可以無限次使用」,而第 3 條同時違背 description 裡「不要被亂用」那句。人工標完的期望標註集合是 4 個標註,第 3 條同時有兩個標註。

把它換成一張課程上架的卡,領域全換、AC 從 7 條變成 12 條、打架的那兩條分開藏在中間,驗收的方式是把新卡重讀一遍,逐格對回原本那份 goal state。標註的總數要還是 4 個,同一條 AC 要還是同時中「彼此衝突」與「與描述不符」。

留這張卡的理由就在這裡,只要 agent 從逐條檢查變成整體看一遍,掉的會是「與描述不符」那一個,4 個標註變成 3 個,而 Goal、Scope in、Scope out、AC 標註四個區塊看起來一模一樣。這個性質合成完要原封不動留著,合成化把它換掉了,這筆 case 就沒有用了。

一張 agent 出過錯的卡變成一筆 case 的步驟

https://ithelp.ithome.com.tw/upload/images/20260916/2017240190T6j7PnPz.png

第一步不能跳過,先寫下這張卡的問題跟期望的標註集合,合成化之後才有東西可以對;顛倒過來做,就變成看變體卡長什麼樣、再回頭說它測的就是這張卡原本的問題。

最後原卡與變體卡的對照表,留在自己機器上就好,它進了 repo 等於把前面幾步全部還原回去。

這一套真的走過一遍嗎?

需求釐清 agent 的資料集還沒補齊,合成化這一套還沒用在它的資料集上,但另一份實驗已經完整做過一次。這份實驗是叫 agent 照一份寫作規範產出 Trello 卡片,3 個案例整批換到另一個領域,換掉的是帳單金額、第三方服務回的錯誤碼、內部 branch 名與 commit SHA,還有聊天室裡補的那段需求。

換完之後重跑了 24 次,4 個 case × 2 個 configuration × 3 次,把規範拿掉的那一組,逐條算的通過率 64%,4 個 case 一個都沒有全過。合成過的案例還測得出原本要測的那個差別,合成化做錯了就看不到這個差別。

Airbnb 的做法方向反過來,他們每天抽 5% 的線上流量去識別化,跑程式檢查跟 grader,被標出來的輸出交人工複審,每週由 PM 執行,發現新的失敗模式就加一項新的 eval(Eval-driven development)。他們有源源不絕的線上流量,去識別化之後就是資料;這個系列沒有那種流量,做的是把手上 agent 出過錯的那幾張卡合成化,補齊資料集。

資料集要當程式管

合成化只解決了資料能不能寫出來。開頭說的重跑、審查、送進模型 API,靠的是資料集跟 grader、fixture 放在同一個 repo 裡,同事拉下來就能跑,改一格就有 diff。

所以還有一條規則要現在就定下來。一筆 case 帶三樣東西,整份輸入原文、這筆通過的條件、以及第一次真跑時把外部回應原封不動存下來的那份檔案(fixture),三樣都是檔案,都進版本控制。

當程式管的意思是它也有測試。repo 的 dataset/case.test.ts對那張優惠碼的卡檢查三件事,標註是一個集合、判不了的格子要寫明、Scope out 不能用完全相同:

test('ac-conflict-001 pins the four flags as a set', () => {
  const c = loadCase('dataset/ac-conflict-001.yaml');
  assert.deepEqual(c.goalState.ac_flags, {
    mode: 'exact_set',
    values: ['3:conflict', '3:mismatch', '4:unverifiable', '7:unverifiable'],
  });
  // 釘的是集合不是總數:標對 4 個跟標錯 4 個分得出來。
  assert.equal(new Set(c.goalState.ac_flags.values).size, 4);
});

test('ac-conflict-001 keeps the ungradable cells written down', () => {
  const c = loadCase('dataset/ac-conflict-001.yaml');
  for (const field of ['comment_wording', 'tool_calls', 'goal_wording']) {
    assert.equal(c.goalState[field]!.mode, 'ignore', `${field} must stay written down`);
  }
});

test('scope_out is must_include, not exact_set', () => {
  // 「結帳頁效能」對「結帳頁效能問題」不該紅,而降檔是 dataset 的一次變更、留 diff。
  const c = loadCase('dataset/ac-conflict-001.yaml');
  assert.equal(c.goalState.scope_out!.mode, 'must_include');
});

有人把 AC 標註那一格改鬆,這三條測試會先紅,改的人得在 commit 裡說為什麼。改一格判斷標準也是一個 commit,要寫清楚為什麼改,diff 留著。判斷標準改鬆不會亮紅燈,分數只會變好看,沒有那則 commit 訊息,半年後沒有人查得出是哪一次把那一格放寬的。

合成化也一樣要留紀錄,哪一張卡是哪一次合成化換出來的、換掉了哪幾類識別字、換完誰重讀過,寫在那筆 case 旁邊。這幾行字看起來很囉唆,但下一個接手的人要判斷這筆 case 還算不算數,靠的就是這幾行。

總結

agent 出過錯的卡要能重跑、審查、送進模型 API,內容就得整套重建,留下的只有它讓 agent 出錯的那個結構。redaction 不夠,agent 讀到的輸入要像人寫的,遮過的卡也拼得回去。合成化換掉的是領域、措辭、篇幅與識別字,不能動的是它讓 agent 出錯的那個結構,換完人重讀一遍、逐格對回原本的 goal state,再重跑一次看差別還在不在。

資料集要當程式管,輸入原文、通過的條件、fixture 三樣都進版本控制,改一格判斷標準也是一個 commit,合成化的來歷寫在那筆 case 旁邊。


上一篇
Day 13|這種苦工能不能叫 AI 幫我做?哪一部分可以讓 AI 模型產生
下一篇
Day 15|對錯還是人眼在比,怎麼讓程式自己判?第一次由程式抓到少掉的標註
系列文
30 天 AI eval 實戰:一隻 PM agent,改完之後怎麼確定它還是對的18
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言