前三天我們把行銷助理做出來了,Day 21 教它查資料,Day 22 讓它記得上下文,Day 23 替它裝上護欄,每一天我都是把回答一題一題讀過才敢寫進文章,可是哪天主管問起這個助理講的數字能不能直接放進週報,我能回的只有「我試過幾題還不錯」,這句話沒有題目清單、沒有標準答案、沒有分數,下次換了模型或改了工具,也沒辦法知道它是變好還是變差。
有系統地替 AI 的回答打分數這件事叫評測,做評測要先有三樣東西,題目、標準答案、評分的方式,標準答案的來源我們在 Day 05 就準備好了,當時藏進合成資料的那幾個訊號就是現成的依據,今天麻煩的是第三樣,評分的方式本身也會出錯,用關鍵字比對會被換個說法騙過,請人來評又貴又慢,請另一個模型來評很便宜,但它評得準不準也要有人驗過。
所以今天要考的對象有兩個,先考助理,再考替助理打分數的方式。
今日核心目標:
整個流程分四段,每一段的結果都寫進 BigQuery,花錢的只有第一段和第四段。
| 段落 | 做什麼 | 會不會花錢 | 結果放哪裡 |
|---|---|---|---|
| 問問題 | 8 題在有工具、沒有工具各問一次,問答流程原樣沿用 Day 21 的 agent/ask.py |
會,呼叫 gemini-3.6-flash | eval_answers、eval_calls_log |
| 規則評分 | 用規則運算式看關鍵事實有沒有出現 | 不會 | eval_scores(grader 是 rule) |
| 人評分 | 16 則打亂順序匯出,AI 助手先打草稿,人逐則讀過定案並 commit | 不會 | eval_scores(grader 是 human) |
| 評分模型評分 | 呼叫 Gen AI evaluation service,兩個評分模型各評一次 | 會 | eval_scores(grader 是 judge:模型名稱) |
這個設計有三個刻意的安排:
第一,題目、標準答案、規則、評分標準全部寫死在 evaluation/eval_run.py,問模型之前要先 commit,run.sh 會檢查,這些內容還會算出一個指紋跟著每一個回答、每一筆分數寫進表裡,看完回答再回頭改題目或改標準答案,指紋就對不起來檢查會直接報錯。
第二,人的分數要在評分模型開始之前寫好,而且 human_labels.csv 要先 commit,run.sh judge 才肯往下跑,順序反過來的話,人看過評分模型給的分數和理由很難不被影響,要先說明的是,這一次人的分數是先請另一個 AI 助手打草稿、我再逐則審過定案的,所以這個基準並不是完全獨立於模型的判斷,3.2 和 3.5 會交代這件事對結論的影響。
第三,給人評分的 16 則是打亂順序的,檔案裡只有編號,沒有標出哪一則是有工具的回答,這只遮得住標籤,有工具的回答帶著精確的數字,讀內容多半還是猜得出來。
8 個問題分成三種。
| 題 | 訊號 | 題型 | 問題 | 標準答案的重點 |
|---|---|---|---|---|
| e1 | S1 | 查得到 | meta-trn-prospecting 八月出過什麼狀況、哪一天開始、點擊成本變成幾倍 | 8/10 那一週起每次點擊成本增加約 78%,原因是競價變貴 |
| e2 | S2 | 查得到 | 8 月底哪一天購買數掉到 0、那天真的沒有訂單嗎 | 8/27,追蹤碼失效,後台照常有 37 筆訂單 |
| e3 | S3 | 查得到 | 哪一支素材出現素材疲乏、點擊率下滑的情況 | cr-meta-evg-p1,點擊率比上線頭 14 天低約 31% |
| e4 | S4 | 查得到 | 放人物、按鈕放右下、用暖色系各讓點擊率變成幾倍 | 約 1.28、1.13、1.08 倍,人物幫助最大 |
| e5 | S6 | 查得到 | meta 和 google 搜尋廣告誰比較常是第一次接觸、誰比較常是最後一次 | 第一次接觸 meta 789 筆對 352 筆,最後一次接觸 google / cpc 746 筆對 380 筆 |
| e6 | S7 | 查不到 | 秋日棉織專案的主打商品銷售占比上升多少 | 三個工具都查不到商品銷售件數,要說查不到 |
| e7 | S5 | 查不到 | 買過一次就不再回購的沉睡客佔幾成 | 三個工具都查不到顧客類型,要說查不到 |
| e8 | 沒有藏 | 沒有藏 | LINE 這個通路有沒有出過成效異常 | 沒有專屬的異常,只有 8/27 那一筆全站追蹤問題 |
e6、e7 兩題是故意出的,訊號確實藏在資料裡,但助理手上的三個工具(通路歸因、成效異常診斷、素材特徵)查不到,這時候正確的回答是老實說查不到,最後一題則是答案表裡根本沒有放的東西,正確的回答是沒有。
寫標準答案的時候我踩了一個坑,第一版直接抄答案表,例如 e1 寫「8/12 起每次點擊成本變成 2 倍」,可是助理查得到的是 Day 09 的診斷結果,那裡寫的是「8/10 到 8/16 那一週增加 78%」,兩邊講的是同一件事,數字卻對不上,照第一版去評,答得越忠於工具的回答越容易被扣分,花錢之前我請另一個 AI 做獨立審查它就抓到這一點,之後標準答案全部改成工具查得到的版本,藏進資料的設定寫在後面當成容許範圍,像 e1 就註明日期回答 8/10 那一週或 8/12 都算對、倍數回答約 1.8 倍或 2 倍都算對。
分數只有 0、1、2 三種,三種評分方式看的是同一段文字。
2 分:重點和標準答案一致。對象、日期、方向都對,數字和標準答案相差在兩成以內算一致,日期落在同一週內算一致。多給了標準答案沒提到、但不矛盾的細節不扣分。標準答案說正確的回答是查不到,而回答也清楚說明查不到,同樣給 2 分。
1 分:部分正確。少了關鍵的一項,或是有一項和標準答案不符。標準答案有內容,回答卻只老實說自己查不到、沒有編造,也給 1 分。
0 分:和標準答案矛盾、答非所問,或是編出和標準答案矛盾的數字、日期或原因。標準答案說正確的回答是查不到,回答卻自己給了數字,一律 0 分。
規則是把每一題的關鍵事實寫成比對條件,例如 e2 要同時出現日期(8/27)、追蹤出問題、其實有訂單三項才給 2 分,寫規則時有一個原則,關鍵事實只能挑題目裡沒有出現過的字,第一版我把「疲乏」「點擊成本」這些題目本來就有的字也算進去,結果一個回答只要把題目照念一次再說不知道,就能拿到 1 分。
人的部分,16 則回答打亂順序之後,先由 AI 助手(Claude,不是今天那兩個評分模型)照評分標準評一遍當草稿,我再逐則讀過定案,最後改了一則的分數,這一則後面會細講,它是今天最有意思的地方,也因為 16 則裡有 15 則我同意草稿的分數,下面凡是寫「人的分數」,指的都是這種 AI 草稿加人審核的分數。
評分模型用的是 Gen AI evaluation service,下一節說明。
Gen AI evaluation service 是 Google Cloud 上專門做評測的服務,今天用的是 pointwise 指標,一次評一個回答,另外還有 pairwise 指標用來比較兩個回答哪個好(今天沒有用到),評分說明是自己寫的,內容就是 3.2 那份評分標準,加上問題、標準答案、回答三個欄位,我直接呼叫它的 REST 端點 evaluateInstances,一次送一筆。
body = {
"pointwiseMetricInput": {
"metricSpec": {"metricPromptTemplate": JUDGE_TEMPLATE}, # 評分說明,裡面有 {prompt}、{reference}、{response}
"instance": {"jsonInstance": json.dumps(
{"prompt": 問題, "reference": 標準答案, "response": 助理的回答}, ensure_ascii=False)},
},
"autoraterConfig": {
"samplingCount": 1,
"autoraterModel": f"projects/{project}/locations/global/publishers/google/models/{judge}",
"generationConfig": {"temperature": 0, "maxOutputTokens": 768},
},
}
resp = session.post(
f"https://aiplatform.googleapis.com/v1beta1/projects/{project}/locations/global:evaluateInstances", json=body)
result = resp.json()["pointwiseMetricResult"] # {"score": 2, "explanation": "..."}
幾個設定值得說明:
metricPromptTemplate 是自己寫的評分說明,大括號裡的變數由服務用 jsonInstance 的內容代入autoraterModel 可以指定用哪個模型來評,今天用了兩個,一個是和助理同一個模型的 gemini-3.6-flash(等於自己評自己,這也是限制之一),一個是比較便宜的 gemini-3.5-flash-litesamplingCount 是同一筆要問評分模型幾次,依 SDK 裡的欄位定義,沒有指定時是 4 次(預設的行為我沒有實測),今天設成 1 次省錢,代價是每筆分數只是一次抽樣generationConfig 把溫度設成 0、輸出上限設成 768 個 Token,這個服務不回報用掉多少 Token,不設上限的話連估價都沒辦法估先看人定案的分數(滿分 2 分)。
| 題型 | 有工具 | 沒有工具 |
|---|---|---|
| 查得到(5 題) | 2、2、2、2、2 | 1、0、0、0、2 |
| 查不到(2 題) | 2、2 | 0、0 |
| 沒有藏(1 題) | 2 | 0 |
| 平均 | 2.00 | 0.38 |
有工具的 8 個回答全部滿分,查得到的 5 題數字和日期都和工具結果一致,查不到的 2 題都老實說查不到,其中 e6 模型先要求了一個清單裡不存在的工具 get_project_impact,程式沒有執行並把錯誤交回去,模型接著回答目前的工具查不到,沒有硬掰。
沒有工具的那一邊值得一題一題看:
8 題裡有 6 題拿 0 分,其中 5 題編出了我們資料裡不存在的事(e2 的日期、e3 的素材與數字、e4 的倍數、e6 的占比、e8 的兩件異常),e7 沒有編公司的數字,但拿產業的一般值來回答,同樣是 0 分,這些回答我讀起來語氣和有工具時一樣肯定,光看回答的樣子分不出哪一個是查出來的,e5 則是另一種提醒,答對不代表有依據,這一題沒有工具也答中了,像這樣答案和常識方向一致的題目,測不出助理有沒有真的去查。
把人的分數當基準,另外三種方式的結果如下。
| 評分方式 | 和人一樣 | 比人高 | 比人低 | 不一樣的回答 |
|---|---|---|---|---|
| 規則 | 12 / 16 | 3 | 1 | e2 沒工具、e4 沒工具、e5 有工具、e8 沒工具 |
| gemini-3.6-flash | 15 / 16 | 1 | 0 | e2 沒工具 |
| gemini-3.5-flash-lite | 14 / 16 | 1 | 1 | e2 沒工具、e5 沒工具 |
規則的 4 個不同,原因各不相同,除了下面會講的 e2,另外三個是這樣:
兩個評分模型都給 e2 沒工具的回答 1 分,人給 0 分(規則也是 1 分,但那是因為它只認出追蹤碼這一項,日期和訂單都沒有認出來,只是剛好同分),評分模型的理由很清楚,gemini-3.6-flash 寫的是「助理正確說明了是追蹤碼問題且當天實際上仍有訂單產生,但解答的日期(8月31日)與標準答案(8月27日)不符」,三項對兩項、錯一項,照 1 分那一條的字面是 1 分,AI 助手打草稿的時候也是給 1 分。
我最後把它改成 0 分,理由是站在使用者的角度看,這個回答把一個錯的日期講得很肯定,同事拿去查 8 月 31 日的紀錄很可能什麼都查不到,甚至以為追蹤沒有問題,比起老實說不知道,講錯的傷害更大,e1 沒有工具時說查不到都有 1 分,e2 講錯日期不該和它同分。
回頭看評分標準,1 分那一條寫的是有一項和標準答案不符,0 分那一條寫的是編出和標準答案矛盾的數字、日期或原因,這個回答兩條都符合,標準本身有模糊的地方,評分模型選了寬的那一邊,人選了嚴的那一邊,這不是評分模型看錯,是「講錯比不知道更糟」這個判斷沒有被寫進標準裡,而這個判斷來自對使用情境的理解,這裡也要老實交代,e2 這一則正是我唯一推翻 AI 草稿的一則,其餘 15 則的基準本來就出自模型,評分模型和它一致,有一部分只是模型和模型一致,所以評分模型和人的一致率會偏高,不一致集中在這一則也和做法有關。
gemini-3.5-flash-lite 另外把 e5 沒工具的回答評成 1 分,理由是缺少標準答案裡的具體筆數,評分標準只寫了多給細節不扣分,沒有寫少給數字要不要扣,它把少了筆數當成少了關鍵的一項,這也說得通,這一次它在這一題比另一個評分模型嚴。
今天非人不可的地方有三件事:
再往專業的題目走,例如歸因模型該怎麼選、異常的原因判斷合不合理,我的看法是最後的審核更需要該領域的專家來做,評分模型適合拿來做第一輪的大量篩選,把分數不一致和分數偏低的挑出來給人看。
這些數字的限制也要講清楚,只有 16 個回答、每題只問一次、評分模型每筆只抽樣一次、其中一個評分模型和助理是同一個模型、做基準的分數是 AI 草稿加一個人審核而且 16 則裡只有 1 則被改過,15 / 16 和 14 / 16 只差一個回答,不能拿來說哪個評分模型比較準,12 / 16 和 15 / 16 也不能當成評分模型一定比規則準的證據,今天能說的是每一個不一致各自是什麼原因。
--one,每個模型先試一筆、印出原始回應,確認分數讀得到再跑完整的| 步驟 | 模型 | 呼叫次數 | 輸入 Token | 輸出 Token | 費用(新台幣) | 依據 |
|---|---|---|---|---|---|---|
| 問問題 | gemini-3.6-flash | 24 | 14,294 | 7,752(含思考) | 1.27 元 | 實際用量 |
| 評分 | gemini-3.6-flash | 16 | 7,181 | 最多 12,288 | 最多 1.70 元 | 估價上限 |
| 評分 | gemini-3.5-flash-lite | 16 | 7,181 | 最多 12,288 | 最多 1.07 元 | 估價上限 |
| 合計 | 56 | 最多 4.05 元 | 另有 1 次測試沒有記在表裡 |
問問題那一列是實際用量算出來的,16 個回答呼叫模型 24 次,事前印出的預期是 1.14 元、最壞是 13.67 元。
評分那兩列只能給上限,因為 evaluation service 的回應裡沒有 Token 用量,輸入是送出前自己數的再多估三成,輸出當成每一次都寫滿 768 個 Token,實際金額要等帳單,另外找出評分模型路徑寫法的時候多打了一次 gemini-3.5-flash-lite,沒有記在表裡,約 0.07 元以內。
單價用官方定價頁 global 端點的價格(gemini-3.6-flash 每百萬 Token 輸入 0.75、輸出 3.75 美元,gemini-3.5-flash-lite 輸入 0.30、輸出 2.50 美元),匯率以 1 美元約 32 元計。
~/ai-driven-martech-pipeline 執行 git pull,取得 Day 24 新增的 evaluation/ 目錄gcloud config get-value project 要印出你的專案 IDops_llm_usage 是 Day 16 建的google-genai、google-cloud-bigquery、requests 三個 Python 套件,缺少時 run.sh 會提示安裝指令evaluation/human_labels.csv 和 answers_to_label.md 是我這一次的紀錄,你的模型回答不會和我的一字不差,要自己重評,開始前先把這兩個檔案改名留存cd ~/ai-driven-martech-pipeline/evaluation
git mv human_labels.csv human_labels.jimmy.csv && git mv answers_to_label.md answers_to_label.jimmy.md
git commit -m "keep the original labels for reference"
cd ~/ai-driven-martech-pipeline
bash evaluation/run.sh answers
# 讀 evaluation/answers_to_label.md,把 0、1、2 填進 evaluation/human_labels.csv 的 score 欄
bash evaluation/run.sh human
git add evaluation/human_labels.csv evaluation/answers_to_label.md && git commit -m "my labels"
bash evaluation/run.sh judge --one
bash evaluation/run.sh judge
answers 會問 16 次、做規則評分、匯出給人評分的兩個檔案,human 把你填好的分數寫進表,judge --one 每個評分模型只試一筆,judge 跑完剩下的並印出檢查與報表,answers、judge --one、judge 三個步驟都會先印估價再停下來,輸入 yes 才開始,我這次的估價合計最多約新台幣 4.05 元,累計估到 5 元會自動停,問過的問題、評過的分數再執行都不會重複收費,兩次 git commit 需要 Cloud Shell 已經設定過 git 的 user.name 與 user.email,另外因為本機多了 commit,之後的 git pull 會變成合併。
路線 B 是把路線 A 拆開來看每一步在做什麼,實際執行的指令仍是 5.2 那幾個。
cd ~/ai-driven-martech-pipeline
python3 evaluation/eval_run.py selftest
python3 -c "
import sys; sys.path.insert(0, 'evaluation')
import eval_run as E
q = {x['id']: x for x in E.QUESTIONS}
print(E.rule_score(q['e2'], '8/27 是追蹤碼失效,後台其實有 37 筆訂單'))
print(E.rule_score(q['e5'], 'google 比較常是第一次接觸,meta 比較常是最後一次接觸'))
print(E.rule_score(q['e7'], '查不到,但一般電商約三到四成'))
"
這一步要先裝好 5.1 列的 Python 套件,第一行是 27 個例子的自我檢查,後面三行會印出分數和說明,分別是答對、把方向講反、嘴上說查不到卻還是給了數字,分數依序是 2、0、0。
bash evaluation/run.sh answers --dry
會先做預檢(實際查一次三個工具,確認查得到資料、最長的結果放得進輸入上限),再印出預期金額與最壞金額,沒有呼叫模型,judge --dry 要等有了 16 個回答、人的分數也 commit 之後才印得出估價。
answers_to_label.md 開頭就是評分標準,16 則的順序是打亂的,同一個問題會出現兩次,human_labels.csv 可以用試算表軟體開,只填 score 和 note 兩欄,n 和 fingerprint 不要動,fingerprint 是用來確認這份分數評的是這一批回答。
bash evaluation/run.sh report
check.sql 的 12 項都是 OK,第 6 項確認人的分數比第一次呼叫評分模型早寫進表,第 10 項確認回答和每一筆分數用的是同一版題目與評分標準eval_answers、eval_calls_log、eval_scores 三張表都很小,留著不會產生費用,三張表都是只加不刪,之後換模型或改工具再跑一次,就能和今天的分數比,Day 25 的成本儀表板會用到 ops_llm_usage 裡今天寫進去的 24 列,評分模型的用量因為服務不回報,沒有寫進那張表。
maxOutputTokens 的話每一筆可能花多少完全沒有底,samplingCount 沒有指定時依定義還會同一筆問 4 次今天用 Day 05 藏好的訊號出了 8 個問題,有工具和沒有工具各問一次,再用規則、人、評分模型三種方式替 16 個回答評分,有工具的助理 8 題全部滿分,查不到的也老實說查不到,沒有工具時平均只有 0.38 分,8 題裡有 6 題拿 0 分,其中 5 題編出了資料裡不存在的事,三種評分方式裡,規則有 4 個回答和人不同,兩個評分模型分別是 1 個和 2 個,記在表裡的模型呼叫共 56 次,費用最多約新台幣 4.05 元。
回到篇名,今天的答案分兩層,有工具的時候 8 題都答對,其中 3 題是正確地說查不到或沒有,沒有資料可查的時候它照樣答得很肯定,所以該信的不是它的語氣,而是它查了什麼,至於替它打分數這件事,在這 16 個回答上,評分模型和人不同的地方比規則少,而且每一筆都有說得通的理由,可以拿來做第一輪篩選,但樣本太小、基準又是從 AI 的草稿審出來的,不能當成它比較準的證據,今天規則和兩個評分模型都和人不同的那一則,正好是需要理解使用情境才能判斷的地方,講錯比不知道更糟,這種判斷要由人來下,再寫回評分標準裡,越專業的問題越需要專家做最後的審核。
明日預告:Day 25《每天用了多少 Token?打開儀表板一目了然》,從 Day 16 開始,拿得到用量的每一次模型呼叫都寫進了同一張表,明天把它變成看得到的儀表板。