昨天把查資料的工具交給 Gemini 之後,一問一答已經不會亂編數字了,可是實際上沒有人是這樣問問題的,行銷同事問完「上週哪支廣告花最多錢」,下一句一定是「那它的點擊率呢」,再下一句是「前一週也是它嗎」,每一句都省略了主詞,「它」是哪一支、「前一週」是從哪一週往前算,都要靠前面的對話才聽得懂。
Day 21 的程式每一題都是從零開始,第二句丟進去模型根本不知道「它」是誰,另外昨天的三個工具查得到功勞、異常和素材特徵,就是查不到廣告花了多少錢,還有一個更基本的問題,模型不知道今天是幾月幾號,「上週」對它來說沒有意義。
今天把這三個洞補起來,做出一個可以連續聊的行銷助理,不用寫 SQL,用聊天的方式一路問下去。
今日核心目標:
| 和 Day 21 比 | Day 21 | Day 22 |
|---|---|---|
| 對話紀錄 | 每一題用完就丟 | 整場對話共用一份,一輪一輪往後接 |
| 日期 | 題目裡自己寫明起訖日 | 系統指示給今天的日期,模型自己換算 |
| 工具 | 歸因、異常診斷、素材特徵 | 多一個查廣告花費,共四個 |
| 上限 | 每題最多呼叫模型 4 次、輸入最多 4,000 個 Token | 輸入放寬到 8,000 個 Token,再加上每場最多 8 輪 |
💡 核心工程理念:
generate_content 每一次呼叫都是獨立的,不會記得上一次說過什麼,所謂的連續對話是程式把前面每一輪的內容全部再送一次,模型每次都是把整場對話從頭讀完才回答Day 21 的程式裡已經有 contents 這份清單,只是每一題都重新建一份,今天改成整場對話共用同一份,一輪問答結束之後裡面會多出這些東西:
| 順序 | 誰說的 | 內容 |
|---|---|---|
| 1 | 使用者 | 這一輪的問題 |
| 2 | 模型 | 要呼叫哪些工具、參數是什麼(不需要查的時候沒有這一項) |
| 3 | 程式 | 每個工具查到的結果(同上) |
| 4 | 模型 | 這一輪的回答 |
下一輪把新的問題接在後面,整份再送一次,程式的主體只有這幾行:
contents = [] # 整場對話共用
for turn, question in enumerate(questions, 1):
row, calls, tool_rows = one_turn(client, bq, contents, config, question, session_id, turn)
one_turn 裡面和 Day 21 幾乎一樣,多做的事只有兩件,成功的時候把模型的回答也接進 contents,失敗的時候把這一輪接上去的東西全部拿掉,還原到這一輪開始之前的樣子,如果不還原,紀錄裡會留下一段有問題、有工具結果卻沒有回答的半套來回,下一輪接著問的時候模型讀到的對話就是亂的。
模型的知識停在訓練資料的那個時間點,它不知道現在是哪一天,問它「上週」它沒有依據可以換算,解法是在系統指示裡直接講清楚:
今天是 2026-09-17(星期四),資料最新到 2026-09-16。
使用者說的今天、上週、上個月都以這個日期換算,
一週從星期一算到星期日,上週指的是今天所在那一週的前一週。
這一段有三個細節,第一是合成資料只到 9 月 16 日,所以把隔天當成今天,真的上線時這裡要換成程式執行當下的日期,第二是連星期幾都一起給,模型自己推算星期幾不一定可靠,直接給它比較保險,第三是把「一週」和「上週」的定義寫死,有人的一週從星期日開始,有人說的上週是過去七天,不寫清楚模型每次選的可能不一樣。
有了這一段,「上週」被換算成 9 月 7 日到 9 月 13 日,「再前一週」是 8 月 31 日到 9 月 6 日,實測兩次都算對了。
查花費的工具讀的是 Day 07 的廣告成效表 fct_ad_daily,同一筆花費可以依單支廣告、廣告群組、活動或通路來看,我沒有寫成四個工具,而是用一個 group_by 參數切換:
| 參數 | 可以填什麼 |
|---|---|
start_date、end_date |
起訖日,YYYY-MM-DD |
group_by |
creative(單支廣告)、ad_group、campaign、channel |
channel |
只看某個通路時填 meta、line 或 google_cpc |
回傳的每一列有花費、曝光、點擊、點擊率和每次點擊成本,依花費由高到低排,寫這個工具的時候有兩件事要先處理,都是在呼叫模型之前的審查階段發現的。
第一件是結果被截斷,上週有 30 支廣告有花費,工具一次最多回 20 列,如果只回 20 列什麼都不說,模型沒有辦法知道後面還有 10 支,所以回傳裡多了 total_groups(全部有幾個)和 truncated(有沒有被截掉)兩個欄位,工具說明也寫明最多回傳花費前 20 名。
第二件是兩個工具的通路名稱不一樣,花費表叫 meta,歸因表叫 meta / paid_social,歸因表裡還同時有 google / cpc 和 google / organic,要把花費和功勞放在一起看,模型得知道誰對到誰,所以在花費工具的說明裡加了一句對照,meta、line、google_cpc 分別對應歸因工具裡的 meta / paid_social、line / display、google / cpc。
固定用五句話問一輪,後面四句都要靠前面的對話才聽得懂:
| 輪 | 我問的 | 模型查了什麼 | 回答重點 |
|---|---|---|---|
| 1 | 上週哪支廣告花最多錢? | 花費,依單支廣告,9/7 到 9/13 | cr-meta-trn-p1,12,938 元,點擊 877 次 |
| 2 | 它的點擊率跟其他幾支比起來怎麼樣? | 沒有查 | 2.46%,比同通路另外兩支高,但比再行銷素材的 3.14% 和最高的 3.22% 低 |
| 3 | 那再前一週呢?一樣是它最貴嗎? | 花費,依單支廣告,8/31 到 9/6 | 一樣是它,14,328 元,比上週還高 |
| 4 | 上週花最多錢的通路是哪個?它在同一週用時間衰減算的訂單功勞排第幾? | 花費依通路,加上歸因的時間衰減,同一輪一起查 | Meta 花了 59,902 元,功勞 79.9 筆排第 3,付費通路裡排第 1 |
| 5 | 幫我把剛剛聊到的整理成三點,我要貼給主管 | 沒有查 | 把前四輪的數字整理成三點 |
五輪回答裡的數字我都另外寫 SQL 直接查表對過,全部正確,有幾個地方值得看:
meta 和歸因表的 meta / paid_social 被當成同一個通路,功勞排在電子報和直接流量後面,它還補了一句只看付費通路的話是第 1花最多錢的那支廣告點擊率並不是最高的,花最多錢的通路功勞排第 3,這兩件事如果只問第一句是看不到的,都是接著問才問出來。
數字都對不代表每一句話都對,第 2 輪它說點擊率比「Google/LINE 的高點擊素材」低,可是它手上那 20 支裡的 LINE 素材點擊率全部低於 2.46%,高的只有 Google 那幾支,LINE 是它順手多帶的,這種沒有數字的形容最容易被放過。
這一輪只問了一次,溫度設 0,換一種問法結果可能不一樣,第 4 輪「排第幾」我也沒有說是在哪些通路裡排,模型兩種都給了,換一次不一定會這麼周到。
chat.sh 一樣先印最壞金額,輸入 yes 才開始五輪對話一共呼叫模型 8 次,每一次的輸入 Token 是這樣長大的:
| 輪 | 第幾次呼叫 | 輸入 Token | 這時候紀錄裡多了什麼 |
|---|---|---|---|
| 1 | 1 | 1,056 | 只有系統指示、四個工具定義和第一句話 |
| 1 | 2 | 2,433 | 上週 20 支廣告的花費 |
| 2 | 1 | 2,627 | 第 1 輪的回答 |
| 3 | 1 | 3,261 | 第 2 輪的問答 |
| 3 | 2 | 4,584 | 前一週 20 支廣告的花費 |
| 4 | 1 | 4,927 | 第 3 輪的回答 |
| 4 | 2 | 5,678 | 通路花費與歸因兩份結果 |
| 5 | 1 | 6,195 | 第 4 輪的回答 |
最後一次呼叫的輸入是第一次的 5.9 倍,第 5 輪那句「整理成三點」本身只有二十個字左右,卻要付 6,195 個 Token 的輸入,因為前面四輪的東西全部重送了一次,五輪合計輸入 30,761 個 Token、輸出加思考 2,920 個,費用約新台幣 1.09 元,其中輸入佔了 68%,事前印出的最壞金額是 8.76 元。
最佔空間的是工具結果,一份 20 支廣告的花費大約 1,300 個 Token,查一次之後每一輪都要跟著重送,所以回傳的列數和欄位要節制,另外如果接下來每一輪都再查一份 20 列的結果,再聊兩三輪就會碰到 8,000 的上限,上限到了的處理方式今天是最簡單的請使用者重開,把舊的對話壓縮成摘要再繼續是另一種做法,這個系列沒有做,費用依 gemini-3.6-flash 在 global 端點的單價(每百萬 Token 輸入 0.75、輸出 3.75 美元)與實際 Token 數算出,匯率以 1 美元約 32 元計,實際以帳單為準。
~/ai-driven-martech-pipeline 執行 git pull,取得 Day 22 新增的 agent/chat.py、agent/chat.sh、兩支檢查與報表的 SQL,以及加了查花費工具的 agent/tools.py
gcloud config get-value project 要印出你的專案 IDops_llm_usage 是 Day 16 建的,也要先有google-genai 與 google-cloud-bigquery 兩個 Python 套件,缺少時 chat.sh 會提示安裝指令cd ~/ai-driven-martech-pipeline && git pull && bash agent/chat.sh
chat.sh 先確認前幾天的表都在,接著做不花錢的預檢,確認上週查得到廣告花費、四份工具結果放得進輸入上限,印出最壞金額之後停下來,輸入 yes 才開始,五輪的問題、查了哪些工具、回答和每一輪用掉的 Token 都會印在畫面上,全部跑完大約兩分鐘,約新台幣 1 元,完整跑過一次之後再執行不會重複收費。
cd ~/ai-driven-martech-pipeline/agent
python3 -c "
import json, tools
from google.cloud import bigquery
bq = bigquery.Client(location='US')
r = tools.run_tool(bq, 'get_ad_spend', {'start_date': '2026-09-07', 'end_date': '2026-09-13', 'group_by': 'channel'})[0]
print(json.dumps(r, ensure_ascii=False, indent=1))
"
會印出上週三個通路的花費,把 group_by 換成 creative 可以看到 total_groups 是 30、truncated 是 true,這一步不花錢。
cd ~/ai-driven-martech-pipeline && bash agent/chat.sh --talk
一樣會先印估價,輸入 yes 之後就可以自己打字問,每一輪結束會印出這一輪用掉的 Token 和累計金額,輸入空白行結束,一場最多 8 輪,八輪的最壞金額是新台幣 14.01 元。
cd ~/ai-driven-martech-pipeline
bq query --nouse_legacy_sql --format=pretty < agent/chat_check.sql
bq query --nouse_legacy_sql --format=pretty --max_rows=100 < agent/chat_report.sql
chat_check.sql 的 9 項都是 OK,包含五輪都有回答、沒有工具錯誤、每次輸入都在 8,000 以內、最後一輪的輸入比第一次多chat_turns、chat_calls_log、chat_tool_log 三張表都只有幾列,留著不會產生費用,明天要拿這個助理來測惡意指令,Day 25 的成本儀表板也會用到 ops_llm_usage 裡今天寫進去的用量(只跑固定腳本是 8 列),都不要刪。
今天把 Day 21 的工具接成可以連續對話的行銷助理,對話紀錄整場共用、系統指示給了今天的日期、多了一個查廣告花費的工具,五句話一路問下去,上週花最多錢的是 cr-meta-trn-p1 的 12,938 元,前一週也是它,花最多錢的通路 Meta 在時間衰減下的功勞排第 3,五輪的數字全部和資料表對得上,其中兩輪沒有重查、直接用紀錄裡的資料回答,合計呼叫模型 8 次約新台幣 1.09 元。
回到篇名,不用寫 SQL 這件事做到了,但它不是免費的,模型的記憶其實是我們每一輪重送的那份紀錄,最後一次呼叫的輸入是第一次的 5.9 倍,聊得越久每一句越貴,所以這種助理一定要有輪數和輸入的上限。
明日預告:Day 23《別讓你的 AI 助理被惡意指令套出個資或亂講話》,助理能聊天也能查資料之後,接下來要面對的是有人故意叫它做不該做的事,明天拿今天的助理來測 Prompt Injection、個資遮蔽和安全設定。