昨天 Day 09 讓 Gemini 在 BigQuery 裡判讀四筆廣告異常,仔細看每一題會發現真正不一樣的只有中間幾行數字,前面的角色說明、後面的六個候選原因和四條規則每題都一模一樣,Gemini 每收到一題就把整段重新讀一次,也重新算一次錢。
Day 09 的題目很短,每題約 270 到 310 個 Token,重複的部分付了也沒多少錢,但實際要讓 AI 判斷得準,通常得附上背景資料,例如商品清單、檔期、每個廣告群組過去幾個月的成效,背景一長,每題重複的部分就變成好幾千個 Token,題目越多、重跑越多次,這段一樣的內容就被重複付費越多次。
Gemini 有一個快取機制專門處理這種狀況,簡單說就是先把每次都一樣的內容存在 Gemini 那邊,下次直接拿來用,讀取快取裡的內容只收一折,今天就用 Day 09 的四筆異常加上真正的背景資料來實測,看看能省多少、要問幾次才划得來。
今日核心目標:
| 步驟 | 做什麼 | 產出 |
|---|---|---|
| 組固定內容 | 從倉儲整理商品、檔期、素材、廣告群組每週成效,加上角色、選項、規則 | cache_context,一列 6,234 個 Token |
| 拆出會變的部分 | 每題只留那一筆異常的數字 | cache_prompt 檢視表,每題 139 到 179 個 Token |
| 建快取 | 用 Cloud Shell 把固定內容送進 Gemini 的快取,設定存活 30 分鐘 | 一個快取名稱 |
| 三種做法各跑三輪 | 同樣 4 題,分別用三種方式問 | cache_runs,每題記下輸入、命中、輸出的 Token 數 |
| 用完就刪 | 跑完立刻刪掉快取 | 不再收儲存費 |
💡 核心工程理念:
Gemini 的快取有兩種,差別在要不要自己動手,今天的金額都以 1 美元約 32 元換算成新台幣:
| 隱含式快取 | 明確快取 | |
|---|---|---|
| 怎麼開 | 預設就開著,不用設定 | 自己先建一個快取,每次呼叫帶上快取名稱 |
| 什麼時候命中 | Gemini 發現這次的開頭和最近某次一樣時,由 Google 決定 | 每次都命中 |
| 命中的部分 | 打一折 | 打一折 |
| 額外費用 | 沒有 | 建立那次照原價算,另外每百萬 Token 每小時收約新台幣 32 元儲存費 |
| 最短長度 | 4,096 個 Token | 4,096 個 Token |
最短長度是官方文件寫明的門檻,所以第一件事是檢查題目的順序,第二件事是背景夠不夠長,Day 09 每題約 300 個 Token,離門檻還差很遠。
今天的背景資料從倉儲整理出四塊,全部是 Day 07 建好的表:
| 區塊 | 內容 |
|---|---|
| 商品清單 | 5 項商品的定價與第一次出現的日期 |
| 專案檔期 | 秋日棉織專案 9/1 到 9/16 |
| 素材清單 | 30 支素材的通路、廣告群組、受眾、格式、上下檔日、主打商品 |
| 各廣告群組每週成效 | 15 個廣告群組從 6/22 起每週的點擊、點擊率、每次點擊花費 |
加上 Day 09 的角色、六個候選原因與四條規則,共 9,386 字,AI.COUNT_TOKENS 數出 6,234 個 Token(建快取時 Gemini 回報 6,231 個,兩種算法差 3 個),每題會變的數字只有 139 到 179 個 Token,也就是說一題裡超過九成五的內容都是重複的。
Day 09 的題目是先講這一筆的數字,最後才列出候選原因和規則,這個順序對人來說很自然,但每題很快就出現不一樣的字,隱含式快取派不上用場,今天把順序改成固定內容在前、數字在後,下面括號裡那一行只是說明,不會送出:
你是電商公司的廣告分析師,接下來會陸續收到系統自動找出的成效異常……
候選原因只能從這六個選一個:競價變貴、追蹤碼失效、素材疲乏、需求或季節變化、其他、資料不足
規則:……
【背景一】商品清單……
【背景四】各廣告群組每週成效(廣告群組|週一日期|有資料天數|點擊|點擊率%|每次點擊花費元)
meta-trn-prospecting|2026-08-10|7|1727|2.39|13.4
……
(以上每題都一樣,以下每題不同)
檢查批次:第 1 批
【這次要判斷的異常】
對象:廣告群組 meta-trn-prospecting(通路 meta)……
背景四用直線分隔的精簡格式,每一列只放名稱和數字,是為了讓背景資料不要膨脹得太長。
明確快取則是把「以上每題都一樣」那一段先送進 Gemini 存起來,之後每題只送出底下那幾行,再附上快取名稱,建快取要呼叫 Vertex AI 的服務,我寫成 caching/cache.sh,一行指令就能建立、列出或刪除,SQL 這邊改用 AI.GENERATE 並在 model_params 裡帶上快取名稱:
AI.GENERATE(
question,
endpoint => 'https://aiplatform.googleapis.com/v1/projects/專案ID/locations/global/publishers/google/models/gemini-3.5-flash-lite',
connection_id => 'us.vertex_ai_conn',
model_params => JSON '''{"cachedContent": "快取名稱", "generation_config": {...}}''')
endpoint 是請求要送去的地址,global 代表不指定機房地區的入口,這裡一定要寫完整網址,原因放在第 6 章,完整的腳本與參數說明在 caching/README.md。
同樣 4 題、同一個模型 gemini-3.5-flash-lite、同一個端點,三種做法各跑三輪共 36 次,每一輪在會變的那一段開頭加上「檢查批次:第 N 批」,模擬每次都是新的一批數字,數字在前的做法會變的部分本來就在最前面,所以批次編號也就在整題的最前面:
| 做法 | 12 題命中幾題 | 輸入 Token | 其中從快取讀取 | 輸出 Token | 費用 |
|---|---|---|---|---|---|
| 數字在前(和 Day 09 一樣把數字放在固定內容前面) | 0 | 78,657 | 0 | 1,357 | 約新台幣 0.86 元 |
| 固定內容在前(隱含式) | 1 | 78,657 | 6,016 | 1,519 | 約新台幣 0.82 元 |
| 明確快取(含建立與儲存費) | 12 | 78,645 | 74,772 | 1,434 | 約新台幣 0.30 元 |
明確快取 12 題全部命中,每題都從快取讀了 6,231 個 Token,實際照原價付費的只剩題目本身和回答格式設定約 320 個 Token,連同建立快取的新台幣 0.06 元、存活約 6 分鐘的儲存費新台幣 0.02 元,總共約新台幣 0.30 元,只看輸入的部分從新台幣 0.76 元降到 0.19 元(含建立與儲存費),省了七成五。
固定內容在前的隱含式快取就沒那麼理想,12 題只有第一輪的一題命中,稍早我用 Day 09 的遠端模型測過同樣的內容,第二輪 4 題有 3 題命中,發文前用 run.sh 重跑一次則是 12 題都沒命中,幾次的呼叫方式和間隔都不一樣,實測分不出原因,隱含式快取有命中就賺到,但不能拿來編預算。
判斷結果方面,三個植入的狀況 S1、S2、S3 三種做法共 27 題全部答對,第四筆 meta-evg-prospecting 是 S3 那支素材所在的廣告群組,答案表裡沒有它的標準答案,Day 09 沒有背景資料時 flash-lite 選了「資料不足」,今天附上 13 週的成效基準後 9 題都選素材疲乏,和 Day 09 我的判讀一致,但這一筆只有 3 天資料,呼叫方式也和 Day 09 不同,要多看幾個案例才能說背景資料讓判斷更準。
明確快取每一題會幫忙省下固定內容九成的輸入費,代價是建立時要照原價付一次,加上活著的每一個小時都要付儲存費,用今天的數字走一遍,單價都是每百萬 Token:
仔細看這三行,每一行都乘上同一個 6,231,換成更長或更短的背景,三行會一起放大或縮小,除完之後長度剛好互相抵銷,所以要問幾次才划得來只跟快取活多久有關,公式是(9.6 + 32 × 存活小時)÷ 8.64:
| 快取存活 | 至少要問幾次才划得來 |
|---|---|
| 10 分鐘 | 2 次 |
| 30 分鐘 | 3 次 |
| 1 小時 | 5 次 |
| 6 小時 | 24 次 |
| 24 小時 | 90 次 |
今天 12 題、存活 6 分鐘,遠遠超過兩平點,但如果建了快取卻讓它放一整天,一天要問滿 90 題才回本,重建一次的成本等於保留約 18 分鐘的儲存費,所以兩批之間相隔超過約 18 分鐘,就該跑完立刻刪、下一批再重建。
背景長度雖然不影響要問幾次,但決定了能不能用,低於 4,096 個 Token 建不起來,而且背景越長,每題省下的絕對金額越大。
cost.sql 在呼叫前用 AI.COUNT_TOKENS 數出固定內容與題目的長度,估出三種做法的最壞情況合計約新台幣 3.25 元,實際約新台幣 2 元,run.sh 要輸入 yes 才會建快取,而且不管中途有沒有出錯,腳本結束時都會去刪快取,刪不掉會印出警告martech_dw 裡有 diag_prompt,如果照 Day 09 第 5.5 節刪掉了,重跑 diagnosis/summary.sql 與 diagnosis/prompt.sql 即可,不會呼叫 Geminius.vertex_ai_conn 存在,而且連線的服務帳號有 Vertex AI User 角色gcloud auth list,帳號前面要有星號cd ~/ai-driven-martech-pipeline && git pull && bash caching/run.sh
畫面印出預估費用後輸入 yes,看到「16 項通過、0 項不通過」、三段對照表與明確快取的額外成本就完成了。
cd ~/ai-driven-martech-pipeline
git pull
ls caching
會看到 context.sql、cost.sql、cache.sh、experiment.sql、check.sql、report.sql、run.sh 與說明文件 README.md。
bq query --nouse_legacy_sql < caching/context.sql
bq query --nouse_legacy_sql --format=pretty < caching/cost.sql
context_tokens 至少要 4,096 才建得了快取,worst_twd 是最壞情況的新台幣費用。
bash caching/cache.sh create
會印出快取名稱、Token 數與到期時間,名稱同時存進 caching/.cache_name,從這一刻起開始收儲存費,快取 30 分鐘後會自動過期,請在時間內接著做步驟 4。
PROJECT=$(gcloud config get-value project)
CACHE=$(cat caching/.cache_name)
sed -e "s#PROJECT_ID#${PROJECT}#g" -e "s#CACHE_NAME#${CACHE}#g" caching/experiment.sql \
| bq query --nouse_legacy_sql
endpoint 在 SQL 裡只能寫常數,所以用 sed 把專案 ID 與快取名稱代進去,36 題約半分鐘到一分鐘跑完。
bash caching/cache.sh delete
bash caching/cache.sh list
bq query --nouse_legacy_sql --format=pretty --max_rows=100 < caching/check.sql
sed -n '/-- ①/,/;/p' caching/report.sql | bq query --nouse_legacy_sql --format=pretty
sed -n '/-- ②/,/;/p' caching/report.sql | bq query --nouse_legacy_sql --format=pretty
sed -n '/-- ③/,/;/p' caching/report.sql | bq query --nouse_legacy_sql --format=pretty
list 最後一行要顯示「還活著的快取:0」,check.sql 會確認固定內容夠長、三種做法都有 12 列、沒有錯誤、原因都在選項內、三個植入的狀況都答對、數字在前的做法沒有命中、明確快取 12 題全部命中,後面三行分別印出每一輪的費用、三種做法合計,以及不同存活時間要問幾次才划得來。
check.sql 16 項全部通過,cache.sh list 顯示沒有活著的快取bash caching/cache.sh list
bq rm -f -t martech_dw.cache_prompt
bq rm -f -t martech_dw.cache_context
bq rm -f -t martech_dw.cache_runs
先確認沒有活著的快取,三張表都很小放著不會產生費用,真正會一直收錢的只有還沒過期的快取。
cache.sh 預設 30 分鐘就會自動過期,run.sh 結束時也會主動刪除gemini-3.5-flash-lite 的快取會回 404,也就是找不到這個模型,要改建在 globalAI.GENERATE 只寫模型名稱時,看起來 BigQuery 會把請求送到別的區域,找不到 global 的快取,會出現「Not found: cached content metadata」,另外 endpoint 不能用變數只能寫常數,model_params 我也一併寫成常數thinking_level 會被 BigQuery 擋下,今天確認是大小寫的問題,寫成大寫 "LOW" 就能通過,但 flash-lite 在 LOW 仍然會先思考約 60 個 Token,像今天這種選擇題還是用 thinking_budget 設 0 最省今天把 Day 09 每題都重送的角色、選項、規則,加上從倉儲整理出來的商品、檔期、素材與每週成效,組成一段 6,234 個 Token 的固定內容,同樣 4 題用三種做法各跑三輪,數字在前完全不命中,固定內容在前靠隱含式快取只命中一題,明確快取則 12 題全部命中,連同建立與儲存費在內約新台幣 0.30 元,是數字在前的三分之一左右。
回到篇名,同一份資料問 AI 十次,只要先把資料存進快取,除了建立時付一次原價和一點儲存費,十次裡重複的部分都只收一折,而且算下來要問幾次才划得來只跟快取活多久有關,活 30 分鐘問 3 次就回本,所以最實用的習慣是批次開跑前建、跑完就刪。
另外附上背景資料之後,Day 09 判「資料不足」的那一筆今天改選素材疲乏,快取讓我們付得起更長的背景資料,也讓 AI 有了可以比較的基準,至於判斷有沒有因此變準,還要等之後有更多案例再下結論。
明日預告:Day 11《讓資料自己分組,才發現你的顧客其實只有這幾種人》,我們將回到倉儲裡的顧客與訂單,用 BigQuery ML 的 K-means 讓資料自己把顧客分成幾群,看看每一群的購買頻率、客單價和偏好的商品有什麼不同!