Day 15 讓 Gemini 看六張樣本圖,每張交出五個固定欄位,30 次呼叫只錯了 1 格,錯在沒給判斷標準的那一輪,加上判斷標準之後五個欄位全部答對,連那一輪答錯的文字多寡也改對了,但六張是抽查,只能說寫法可行,今天把同一套寫法放大到素材庫的全部 24 張,一次跑完、存成一張特徵表,Day 17 要拿這張表和點擊率放在一起比。
從六張放大到一整批,多出來的不只是呼叫次數,六張手動跑一次、看一眼結果就好,一整批就會碰到六張遇不到的事,失敗的呼叫要補,補的時候不能把成功過的圖再付一次錢,整批花了多少也要記下來,而且看圖的人不只 AI,規格本身也可能有問題,這批素材的底圖是 AI 生的,生出來的畫面不一定完全照規格,如果規格寫錯了,AI 答錯就不一定是 AI 的錯,今天的雲端費用一樣以 1 美元 32 元換算成新台幣。
今日核心目標:
mart_creative_features,一張圖一列| 步驟 | 做什麼 | 產出 |
|---|---|---|
| 判讀 | 看圖之前先找出規格和畫面看起來不一致的圖 | martech_gt.gt_creative_review |
| 看圖 | 24 張 × 預設、低解析度各一次,只呼叫還沒成功過的 | martech_dw.mm_features_log |
| 記用量 | 每一次呼叫的 Token 抄一份 | martech_dw.ops_llm_usage |
| 建表 | 每張圖挑一筆預設解析度的結果 | martech_dw.mart_creative_features,24 列 |
| 對答案 | 只在報表這一步把結果和答案表放在一起 | report.sql 第 3 到 5 段 |
💡 核心工程理念:
martech_gt,看圖和建表的 SQL 只讀 martech_dw,run.sh 會用 grep 確認看圖的寫法和 Day 15 的 B 輪完全一樣,AI.GENERATE 帶 output_schema 鎖型別,題目列出選項和判斷標準,模型用 gemini-3.5-flash-lite,差別在 Day 15 是先挑六張放進 mm_sample 再 JOIN,今天直接對物件表裡的每一張圖,每張看兩次,一次預設解析度寫進特徵表,一次低解析度拿來對照,放大到整批之後多做了三件事。
第一件是跑過的不重跑,AI.GENERATE 是一列呼叫一次,物件表有幾列就付幾次錢,如果 48 次裡有 3 次失敗,最直覺的做法是整批重跑,但這樣成功的 45 次會再付一次,所以每一次呼叫都寫進呼叫紀錄 mm_features_log,成功失敗都留著,要看圖之前先算出「還沒有成功紀錄的圖 × 解析度」,只對這些呼叫:
CREATE TEMP TABLE todo AS
SELECT a.uri, a.creative_id, a.resolution
FROM (
SELECT o.uri, REGEXP_EXTRACT(o.uri, r'/([^/]+)\.jpg$') AS creative_id, res AS resolution
FROM martech_dw.obj_creatives o
CROSS JOIN UNNEST(['default', 'low']) AS res
) a
LEFT JOIN done d USING (creative_id, resolution)
WHERE d.creative_id IS NULL;
done 是紀錄裡已經成功的組合,這裡的成功要定義清楚,status 是空字串只代表 API 有回應,欄位還是可能空著,所以要 status 空字串而且五個欄位都有值才算,建表、檢查和估價都用這一條定義,否則會出現檢查說 24 張都完成、重跑時卻又呼叫了一次的情況,這段 SQL 第一次執行時 48 組都在 todo 裡,第二次執行時 todo 是空的,後面的 AI.GENERATE 一次都不會呼叫,run.sh 會真的跑第二次來確認這件事。
第二件是超出選項只補那幾張,Day 15 提過 output_schema 只能鎖型別,dominant_color 是字串就能過關,填成「暖色」它也不會擋,Day 15 的六張沒有發生,但放大到整批就不能只靠運氣,所以預設解析度跑完之後,SQL 會找出有值不在選項裡的圖,只把這幾張改用 response_schema 的 enum(只能從清單裡選值的鎖法)再問一次,全部都在選項裡時這一段是 0 次呼叫,也就不用付錢。
第三件是花了多少要記下來,每一次呼叫的輸入與輸出 Token 都抄一份進共用的用量表 ops_llm_usage,下一節再說。
呼叫紀錄是流水帳,同一張圖可能有失敗的、有補問的、有低解析度的,Day 17 要的是乾淨的一張圖一列,所以另外建特徵表 mart_creative_features,從紀錄裡挑預設解析度的成功結果,同一張圖有好幾筆時先挑值都在選項裡的再挑最新的:
| 欄位 | 說明 |
|---|---|
creative_id |
素材 ID,和 dim_creative、fct_ad_daily JOIN |
has_person 到 headline |
Gemini 看圖抽出的五個欄位 |
in_option |
三個有選項的欄位是否都在選項內 |
model、method、resolution、extracted_at |
這一列從哪個模型、哪種鎖法、哪種解析度、什麼時候來的 |
特徵表每次整張重建,重建只是查詢,不會呼叫 Gemini,後面四個欄位是給之後追查用的,看這幾欄就知道每一列是哪個模型、用哪種解析度讀出來的,特徵表只放 AI 讀出來的東西,規格留在答案資料集,Day 17 拿特徵表和點擊率比的時候,就不會不小心 JOIN 到答案。
Day 09 到 Day 15 的 Token 用量都記在各自的結果表裡,Day 09 放在 mart_diagnosis 的一個 JSON 欄位裡,Day 14 在 mm_describe,Day 15 在 mm_structured,每張表的欄位名稱和一列代表什麼都不一樣,Day 25 要做成本監控時得一張一張翻,所以從今天起多一張共用表 ops_llm_usage,一次呼叫一列,記下第幾天、哪個程式、執行編號、模型、端點類型、解析度、素材 ID、輸入與輸出 Token 和狀態,依日期分區。
單價刻意不存在表裡,價格會變、端點不同價格也不同,Day 15 才剛更正過非 global 端點比 global 貴一成的事,表裡只記 Token 和端點類型,計費時再對照當時的單價,抄寫時也不只抄這一次執行,而是補上所有還沒抄過的執行,萬一某次執行在中途出錯、用量沒抄到,下一次執行會補回來。
這批素材是照規格合成的,每一張圖都有標準答案,但底圖是 AI 生的,規格寫「冷色調」,生圖模型交出來的背景可能只是一面淺灰牆,照題目裡的判斷標準,淺灰要填 neutral,這時 AI 答 neutral 反而是對的,答案表才是錯的,所以今天在呼叫 Gemini 之前,先請另一個 AI 助手(不是 Gemini)當判讀者,只給它 24 張圖和同一套判斷標準、不給規格,逐張判讀之後再和規格比對,有疑慮的格子存成答案資料集裡的 gt_creative_review,沒有疑慮的不入表:
| 判讀結果 | 格數 | 說明 |
|---|---|---|
| 和規格不同(disagree) | 1 | cr-meta-trn-r2 的主色,規格是 cool,畫面是淺灰牆和水泥地,只帶一點點藍 |
| 可能判得不一樣(borderline) | 6 | 全部是主色,背景混了兩種色系,或灰藍淡到接近淺灰 |
| 沒有疑慮 | 89 | 24 張 × 4 欄共 96 格,扣掉上面 7 格 |
這一步一定要在看成績之前做完,先看了 AI 的答案再回頭挑「規格有問題的題目」,很容易變成替 AI 找理由,今天的判讀結果是先寫進 review.sql 並 commit,之後才第一次呼叫 Gemini,run.sh 的順序也是先建判讀表再看圖。

兩種解析度共 48 次呼叫全部成功,第二次執行呼叫 0 次,13 項檢查全部通過,特徵表 24 列,三個有選項的欄位沒有任何一個值超出選項,所以 enum 補問的那一段一次都沒有用到,24 張 × 5 欄共 120 格只錯 1 格:
| 欄位 | 預設解析度答對 | 低解析度答對 |
|---|---|---|
| 有沒有人物 | 24/24 | 24/24 |
| 按鈕位置 | 24/24 | 24/24 |
| 主色 | 23/24 | 19/24 |
| 文字多寡 | 24/24 | 24/24 |
| 標題逐字 | 24/24 | 24/24 |
預設解析度唯一的錯就是判讀表標成 disagree 的那一格,cr-meta-trn-r2 規格寫 cool,Gemini 答 neutral,和判讀者一樣,扣掉這一格,預設解析度 119 格全對,6 格 borderline 也全部和規格一致,Day 15 六張抽查看到的結果在 24 張上撐住了,不過這批圖的條件本來就好,文字和按鈕是程式畫上去的,規格只有三種主色、三種按鈕位置,這個答對率不能直接套到真實的廣告圖上,Day 20 做正式評測時會排除 disagree 的那一格。
低解析度的題目、鎖法和模型都一樣,只在 generation_config 加一個 media_resolution,一次輸入從 1,485 個 Token 降到 657 個,圖片本身從 1,104 降到 276,和 Day 14 量到的一樣是四分之一,輸出都是 56 到 59 個 Token,沒有變。
代價在主色,其他四欄 24 張都和預設解析度答得一樣,主色照規格錯了 5 張,而且 5 張全部答成 neutral,其中 cr-meta-trn-r2 就是預設解析度也答 neutral 的那一格 disagree,所以改用低解析度實際多錯了 4 張,這 4 張有 3 張在判讀表裡標成 borderline,背景混色或色偏很淡,另一張 cr-line-trn-p2 判讀者沒有標成有疑慮,預設解析度也答對了,低解析度卻答成 neutral,在這 24 張文字和按鈕由程式畫上去的圖上,有沒有人物、按鈕在哪、標題寫什麼這種看形狀和文字的欄位不受影響,我推測是圖片縮小之後淡淡的色偏先被抹掉了,字小或細節多的真實廣告圖還是要先抽幾張比較。
換算成 1,000 張圖,預設解析度約新台幣 20.7 元,低解析度約 12.0 元,省四成,但這批只有 24 張,全部用預設解析度也才 0.5 元,特徵表用預設解析度,主色是 Day 17 要拿來和點擊率比的欄位之一,為了省兩毛錢多錯 4 張主色不划算,低解析度適合用不到顏色、只需要形狀和文字的場景,要注意的是主色不能拆出去另外問,問主色那一次一樣要付整張圖的 Token,拆成兩次反而比一次問完五欄貴。
run.sh 先查出這次真的要呼叫幾次再估價,第一次執行是 48 次,最壞情況以預設解析度每次輸入 1,600、低解析度 800、輸出上限 256 個 Token 計,約新台幣 1.69 元,看到數字輸入 yes 才會呼叫,萬一有值超出選項,用 enum 補問的每張約再加 0.04 元,不含在這個估價裡,實際合計新台幣 0.785 元,預設解析度 0.497 元、低解析度 0.287 元,第二次執行要呼叫的是 0 次,所以不會再問、也不花錢,萬一第一次有沒成功的,第二次執行前會再問一次 yes單價沿用 Day 15 的非 global 端點價格,3.5-flash-lite 每百萬 Token 輸入 0.33、輸出 2.75 美元,endpoint 只寫模型名稱時 BigQuery 會把請求送到非 global 的端點,ops_llm_usage 的 endpoint_type 欄記的就是這件事,Day 25 算成本時依它對照單價。
輸出每次只有 56 到 59 個 Token,48 次的輸入費約 0.54 元、輸出費約 0.24 元,低解析度省下的全部是輸入費,兩種解析度的輸出費一樣,max_output_tokens 維持 Day 15 的 256,check.sql 確認 48 次都沒有撞到上限。
~/ai-driven-martech-pipeline 執行 git pull,取得 Day 16 的 features/ 目錄gcloud config get-value project 要印出你的專案 IDmartech_dw 裡有物件表 obj_creatives,bucket 裡有 24 張圖gt_creative_design,沒做過的話執行 bash structured/run.sh,在估價那一步直接按 Enter 就好,搬家免費、不會呼叫 Geminigcloud auth list,帳號前面要有星號cd ~/ai-driven-martech-pipeline && git pull && bash features/run.sh
run.sh 先建判讀表,接著查出這次要呼叫幾次、印出最壞費用,輸入 yes 才會看圖,看完建特徵表,然後把看圖的 SQL 再跑一次,確認成功過的圖不會再呼叫,最後是 13 項檢查與八段報表,在估價那一步直接按 Enter 就會停下來,判讀表已經建好但不會產生 Token 費用,整支 run.sh 可以重複執行,第二次執行時要呼叫的次數是 0,也就不會再問 yes。
cd ~/ai-driven-martech-pipeline/features
bq query --nouse_legacy_sql --format=pretty < review.sql
建立 martech_gt.gt_creative_review,印出 disagree 1 格、borderline 6 格。
bq query --nouse_legacy_sql --format=pretty < extract.sql
bq query --nouse_legacy_sql --format=pretty < mart.sql
第一個是 48 次呼叫,最後印出這次執行兩種解析度各呼叫幾次、成功幾次,這一步會產生約新台幣 0.8 元的費用,第二個建特徵表並印出主色與有沒有人物的分組,想親眼看「跑過的不重跑」,再執行一次第一行,最後那張統計表不會有任何一列。
bq query --nouse_legacy_sql --format=pretty < check.sql
bq query --nouse_legacy_sql --format=pretty --max_rows=100 < report.sql
check.sql 是 12 項流程檢查,第 13 項由 run.sh 用 grep 補上,report.sql 八段分別是特徵分佈、每次執行呼叫幾次、逐欄答對、答錯清單、兩種算法的主色答對率、高低解析度一致度、費用與用量表,只有第 3 到 5 段會讀答案資料集。
mart_creative_features 24 列、一張圖一列、in_option 全部是 truemm_features_log 48 列,預設與低解析度各 24 張,status 全部是空字串ops_llm_usage 48 列,和呼叫紀錄一樣多run.sh 的 13 項檢查全部通過,其中一項確認成功過的圖沒有被再呼叫,一項確認看圖和建表的 SQL 沒有讀答案資料集今天建的四張表都留著,呼叫紀錄 mm_features_log 是特徵表的來源,刪掉之後特徵表就不能免費重建,check.sql 和報表也跑不了,再跑 run.sh 會重新呼叫 48 次,特徵表是 Day 17 要用的,判讀表是 Day 20 要用的,用量表是 Day 25 要用的,四張加起來一百多列,放著的儲存費在免費額度內。
status 是空字串只代表 API 有回應,欄位還是可能空著,要重跑時用什麼條件判斷「已經完成」,建表、檢查和估價都要用同一條,否則會出現檢查說全部完成、重跑卻又呼叫的情況DECLARE run_id 之後寫 WHERE run_id = run_id,比到的是欄位自己,永遠成立,今天的執行編號變數因此叫 this_run
run.sh 先查紀錄算出這次要呼叫幾次,0 次就不問今天把 Day 15 的寫法放大到全部 24 張,48 次呼叫全部成功,合計新台幣 0.785 元,同一段 SQL 再跑一次呼叫 0 次,每一次呼叫的 Token 都記進了共用的用量表,預設解析度 120 格只錯 1 格,錯的那一格正好是看圖之前就標出來、規格和畫面不一致的題目,低解析度輸入省了一半多,但主色多錯了 4 張,所以特徵表用預設解析度。
回到篇名,AI 一口氣看完所有廣告圖不難,一段 AI.GENERATE 對著物件表就能跑完,難的是放大之後的那幾件事,失敗的只補失敗的、跑過的不再付錢、花了多少記下來,還有先確認答案本身沒有問題,特徵表 mart_creative_features 現在一張圖一列,每一列都能 JOIN 回廣告成效。
明日預告:Day 17《什麼樣的廣告圖比較會賣?在 BigQuery 把視覺特徵和轉換率放在一起比就知道》,今天的特徵表是 AI 看圖讀出來的,明天把它和點擊率、轉換率放在一起,控管通路和受眾之後,看人物、按鈕位置、主色這幾個特徵和點擊率、轉換率有沒有關係。