上一個系列裡,我和 Codex 從零做出了一個會看地圖的 LINE Bot。使用者分享位置後,Bot 會透過 Vertex AI Google Maps Grounding 找附近咖啡廳,再送回繁中摘要與可以直接打開的 Maps 來源卡片。
做到這裡,Bot 已經會「找店」了,但互動還是一次性的。推薦看完之後,如果使用者不喜歡這一批,或想找更適合帶筆電工作的地方,只能重新傳位置、重新開始。
所以這次,我想讓 Bot 不只是回答一次就結束,而是能接著上一輪搜尋繼續互動。
最先想到的是兩個很直覺的功能:
一開始我以為,這應該只是多放兩顆按鈕。真的和 Codex 討論後才發現,畫面上的按鈕反而是最簡單的部分。
比較麻煩的是:使用者按下「換一批」時,Bot 要怎麼知道上一次在哪裡搜尋,又推薦過哪些店?
這篇先從這個問題開始,聊聊為什麼我選了 LINE Postback,以及 Bot 怎麼替一次搜尋保留短暫的記憶。

Postback 可以把使用者的點擊送回後端,讓程式決定下一步。聽起來很方便,但不是所有按鈕都需要繞回伺服器。
我們最後把三種操作分開:
所以只有「換一批」和「更適合工作」使用 Postback。它們都需要後端拿出上一輪資料,再做一次新的搜尋。
這個選擇看起來很小,卻讓我避開一個常見的陷阱:不是因為想用某個 API,就把所有按鈕都改成同一種 action。先看使用者想做什麼,再決定工具,通常簡單很多。
使用者按下 Postback 後,LINE 會把一段 data 傳回來。
最直覺的做法,是把經緯度、搜尋偏好和上一批店名全部放進這段資料。但這樣不只很長,也會讓後端收到一包難以驗證的外部輸入。
最後我們只留下三件事:
v=1&a=reroll&s=abc123
v:資料格式版本a:這次按了什麼,例如 reroll
s:搜尋 session ID「更適合工作」也使用同一個格式,只把 action 改成 work_friendly。
這樣按鈕只需要表達「我想做什麼」,真正的位置、偏好與店家名單則由後端保存。
我也很喜歡 displayText 和 data 分開這個設計。使用者看到的可以是自然的「🔄 換一批咖啡廳」,程式收到的仍然是穩定、簡短的指令。以後就算改了按鈕文案,也不用跟著改後端流程。
至於 v=1,是為了處理聊天室裡的舊按鈕。LINE 訊息可能留很久,未來資料格式改版時,後端至少能知道這是哪一版,而不是猜一段舊字串代表什麼。
Postback event 只會說「有人按了按鈕」,不會自動帶回上一輪搜尋的完整內容。
要讓「換一批」成立,Bot 至少要記得:
這些資料放在 Cloud Run 記憶體並不可靠,因為服務可能重啟,也可能同時有多個 instance。第一次搜尋和下一次點擊,不一定會落在同一台機器上。
另一個做法是每次都請使用者重新傳位置,但那樣「換一批」就失去意義了。
所以我們選擇 Firestore,替每次搜尋建立一份有效 30 分鐘的 session。它不是永久會員資料,只是讓 Bot 暫時記得這一輪對話。
Session 大致保存:
搜尋位置
搜尋偏好
上一批店名
建立者與所在對話
到期時間
完整的 Gemini 回答、整張 Flex Message 或所有 Maps metadata 都不需要存,因為下一輪搜尋用不到。
這裡的原則很簡單:不是記得越多越好,而是只記住下一步真的需要的東西。
搜尋 session 會綁定原本的 LINE 使用者和對話。就算有人拿到 session ID,也不能在另一個群組或用另一個帳號操作它。
30 分鐘後再點,程式會請使用者重新分享位置。Firestore 的 TTL 之後再負責刪除過期資料。
這兩件事分工不太一樣:
Codex 當時用一句很好記的說法整理:程式負責守門,TTL 負責打掃。
我原本只想加兩顆按鈕,最後先做的卻是把按鈕與記憶分開:
這些東西不會直接出現在畫面上,卻決定「換一批」之後會不會變成難以維護的功能。
下一篇會接著把這份記憶用起來:怎麼真的避開上一批店家、保留工作偏好,以及使用者連點時,如何避免重複呼叫模型。
https://github.com/zonawang/codex-postback-action