上一篇,我替 LINE Cafe Bot 加上咖啡足跡。
使用者可以在推薦卡片上記錄造訪、給 1~5 分,再加入「安靜」、「有插座」、「適合工作」或「想再訪」等標籤。完成後,這些資料會進入 Firestore,也能用「我的咖啡足跡」隨時查看。
做到這裡時,功能看起來已經完成了:按鈕能點、資料存得到、歷史列表也很漂亮。
但我重新問了自己一個問題:如果評分完全不影響下一次搜尋,那它和一般的打卡紀錄有什麼不同?
Cafe Bot 的核心仍然是推薦。既然它已經知道哪些體驗真的讓我滿意,這些資料就不該只停在回顧頁面。
所以這一篇要處理的是咖啡足跡的下一步:讓真實體驗回到推薦流程,同時守住一條重要界線——學習使用者,不等於替店家編造資訊。
最初的流程是單向的:
分享位置 → 取得推薦 → 選一間店 → 結束
加入足跡後,我希望它變成:
推薦 → 造訪 → 評分 → 留下線索 → 改善下一次推薦
第一版先採用兩條簡單、容易理解的規則:
如果同一間店去過不只一次,程式只看最新紀錄。這樣後來的體驗可以修正以前的評價,不會因為一年前打過低分,就永遠被排除。
我刻意沒有把高分標籤直接寫進使用者的偏好設定。
明確偏好和體驗線索代表不同的事情:
因此搜尋時,明確設定的偏好仍然優先;從高分足跡推導出的標籤只協助 Gemini 排序。使用者打開「我的偏好」時,也不會突然看到系統擅自增加的項目。
這讓記憶有兩個層次:一層是我清楚說過的要求,另一層是過去行為提供的參考。兩者一起工作,但不混成同一份資料。

假設我以前常替「安靜、有插座」的店打高分,Bot 可以因此優先尋找類似選擇;但它不能看到一間新店後,因為我喜歡安靜,就直接說那間店很安靜。
店家的插座、Wi-Fi、噪音或時間限制,仍然必須有 Google Maps Grounding 的資料支持。足跡只決定搜尋方向,不是店家資訊的證據。
我把這個限制直接寫進推薦 prompt:
Past highly rated visits can be used as soft ranking signals.
Explicit preferences have priority.
Do not make unsupported claims about a cafe.
這條界線看起來保守,卻能避免個人化系統最常見的問題:把「使用者想要什麼」誤寫成「店家真的有什麼」。
每次搜尋前,Bot 會讀取最近完成的咖啡足跡,再整理成兩組資料:
type JourneyRecommendationProfile = {
preferences: CafePreference[];
avoidCafeNames: string[];
};
preferences 來自高分紀錄中的體驗標籤,avoidCafeNames 則保存最近被評為 1~2 分的店名。
處理時先用正規化店名去除同一間店的舊紀錄,只留下最新一次,再把 Journey tag 對應到既有推薦條件:
quiet → quiet
outlets → outlets
work → work_friendly
「想再訪」沒有直接轉成店家條件,因為它代表的是對某一間店的態度,不是可以套用到其他店的特徵。
最後,低分店名和「換一批」原本要排除的店名會合併去重,再一起交給下一次 Google Maps Grounding 搜尋。
這次最值得記下來的問題,不是某個 TypeScript error,而是驗收方式。
第一版完成時,評分、標籤、Firestore 和 Flex Message 全都正常,看起來已經很完整。如果驗收清單只有「畫面上能不能操作」,這個功能當時就會被判定完成。
但重新對照最初的目的後,我才發現少了最重要的一段:資料沒有回到推薦。
最後補上的程式量不算多,卻改變了整個功能的意義。它讓咖啡足跡從一份歷史列表,變成推薦系統的一部分。
這也提醒我,資料型功能不能只驗證「有沒有存進去」,還要確認它之後在哪裡被使用。
這次沒有直接覆蓋上一版 Cloud Run,而是建立新的 service:
line-cafe-journey
部署流程依序是:
/health 回傳 200。切換時也先保存原本的 endpoint。如果新版驗證失敗,程式會立刻切回舊服務。
這樣做的重點不是讓部署指令看起來更複雜,而是把「建立新版」和「讓真實使用者開始使用」拆成兩個決定。Cloud Build 還在跑、容器還沒 ready,或 LINE Verify 尚未通過時,原本的 Bot 都不受影響。
第一次安裝依賴時,共用 npm cache 裡有舊的權限問題。npm 建議調整全域資料,但這個專案沒有必要為了安裝套件去改動整台電腦。
最後改用專案專用的暫存 cache 完成安裝。測試與 audit 都通過,也沒有留下需要提交的本機設定。
原本的部署指令使用 --startup-cpu-boost,目前環境裡的 gcloud 卻只接受 --cpu-boost,因此第一次指令在建置前就停止。
確認雲端沒有產生半套服務後,我改用相容參數重新部署。
這不是應用程式本身的 bug,卻是部署工作很常見的一部分:文件、CLI 版本和執行環境不一定同步。看到錯誤時,不能只是重跑同一行指令,也要先確認它到底有沒有改變雲端狀態。
這個版本最後共有 26 項測試,除了保留原本的 Datetime Picker、偏好與推薦測試,也新增:
自動測試之外,部署後還要走一次手機流程:傳送位置、記錄造訪、評分、複選標籤、完成,再輸入「我的咖啡足跡」。因為這個功能的價值,最後仍取決於 LINE 裡的整條操作能不能順順走完。
偏好記憶保存的是使用者主動說出的需求;咖啡足跡留下的,則是實際去過之後的感受。
兩者不應該互相取代。有人可能平常想找安靜的店,某天卻替一間熱鬧的小店打了五分;一次體驗不代表整個偏好被改寫,但它確實值得成為下一次推薦的參考。
現在這個 Bot 已經不只會回答「附近有什麼咖啡廳」,也開始累積一份只屬於我的咖啡地圖:去過哪些地方、哪些真的喜歡,以及下一次可以少走哪些回頭路。
對我來說,這次最重要的改變不是多了一個評分功能,而是推薦終於形成了回路。Bot 不再每次把答案交出去就結束,它會把真實體驗帶回下一次選擇裡。
👉 GitHub: https://github.com/zonawang/line-cafe-journey
👉 更多 LINE Bot 與 AI 實作紀錄: https://github.com/zonawang/zona-ai-learning-lab