Sitemap

LINE Bot 為什麼不記得「第二間」?我把收藏功能優化成真正的想去清單

9 min readSep 1, 2026

--

之前其實做過收藏功能。

👉 我和 Codex 讓 LINE Cafe Bot 真的會做事:從「收藏第二間」開始的 Gemini Function Calling 實作

在 line-cafe-action-agent 版本中,我曾用 Gemini Function Calling 實作:

收藏第二間
查看我的收藏
刪除收藏第一間

但這次實際在目前版本輸入「收藏第二間」時,Bot 卻沒有把這句話和上方的推薦連起來。

問題不是 Gemini 突然忘記,而是原本的 Function Calling 收藏留在早期的獨立版本;後來 Bot 的主線改用新的 search session,文字訊息裡又沒有帶著 session ID,目前的 handler 也不會主動找回「最近一批推薦」。

對 Bot 來說,「第二間」因此只是一個失去上下文的編號。它知道使用者想收藏,卻不知道指的是哪一家店。

舊版 Gemini 會判斷使用者想執行哪個動作,再建立一筆等待確認的操作。使用者按下確認後,Bot 才把店家寫進 Firestore。當時也有暫存推薦內容,只是這套上下文銜接沒有跟著後續版本一起保留下來。

那一版已經證明自然語言可以控制收藏,不是假的功能,也不是只有畫面沒有資料。

所以這次更準確的說法,不是「替 Bot 第一次加入收藏」,而是針對這個斷掉的上下文重新優化:

把 Function Calling 階段的實驗版收藏,重新整理成正式的想去清單。

舊版收藏解決的是「AI 能不能採取行動」

Action Agent 當時最重要的問題是:Gemini 能不能理解「收藏第二間」,並安全地把它轉成程式操作?

因此它的重點放在:

  • 用 Function Calling 判斷收藏、查看、刪除或排行程。
  • 建立一筆 10 分鐘內有效的 pending action。
  • 寫入或刪除前,再讓使用者確認一次。
  • 用 Firestore Transaction 避免同一個操作重複執行。

這些設計很適合驗證「自然語言操作」是否可行。

但當 Bot 後來陸續加入偏好、足跡和主動回訪,舊版收藏沒有一起進入新的主線。它留在 line-cafe-action-agent,而不是目前正式服務的功能之一。

這次要解決的問題也不同。我關心的不再只是 Gemini 有沒有選對 function,而是收藏能不能被找到、會不會重複,以及幾天後能不能自然地接著使用。

收藏不是造訪紀錄

一開始,我曾想過直接把喜歡的店放進既有的咖啡足跡。

但兩者代表的事情完全不同:

想去清單:我對這間店有興趣,但還沒去
咖啡足跡:我真的去過,而且留下了體驗

如果混在一起,之後查看紀錄時,就分不清哪些是實際經驗,哪些只是暫時收藏。

所以新版把收藏重新定義成一條清楚的想去流程:

看到推薦
↓
加入想去清單
↓
之後查看收藏
↓
開啟地圖/安排時間/移除

它不會假裝使用者已經去過,也不會要求使用者收藏時就決定日期。

在最接近決定的地方放按鈕

舊版雖然也有「收藏這間」按鈕,但按下後會先送出「收藏第幾間」,再交給 Gemini Function Calling 判斷。

新版的收藏入口同樣放在每一張咖啡廳推薦卡片上,但按鈕會直接送出結構化 Postback。收藏哪一批推薦、其中第幾間,都已經是明確資料,不需要再請模型理解一次。

原本的卡片可以開啟 Google Maps、安排時間或直接記錄造訪;現在多了一個「加入想去清單」。使用者不需要先記住店名,再另外輸入收藏指令。

點下按鈕後,Bot 會回覆:

⭐ 已把「某間咖啡廳」加入你的想去清單。
[查看想去清單]

如果同一間店已經收藏過,Bot 會直接告知,而不是建立第二筆一模一樣的資料。這是舊版沒有處理的部分:舊版每次確認收藏,都可能產生一個新的隨機文件 ID。

這個小差異很重要。收藏按鈕很容易被重複點擊,網路也可能讓使用者不確定第一次是否成功。如果每按一次就多一張卡片,清單很快就會失去整理的意義。

Press enter or click to view image in full size

同一家店,不能只用店名判斷

要避免重複,首先得回答一個問題:Bot 怎麼知道兩筆推薦是不是同一家店?

只比較店名並不可靠。

不同地區可能有同名咖啡廳,同一家店的名稱也可能因為分店資訊、空格或語言而稍微改變。因此我採用推薦結果中的 Google Maps URI 作為識別來源。

程式會先把 URI 標準化,再計算 SHA-256,取其中一段作為 Firestore 文件 ID:

Google Maps URI
↓
標準化並計算雜湊
↓
固定的 wishlist item ID

同一位使用者再次收藏相同 URI 時,會落在同一份文件上;店名日後即使更新,也只會更新原本的收藏,不會多出重複項目。

新版資料放在:

cafe-user-wishlists/{ownerId}/entries/{wishlistItemId}

每位使用者都有自己的 entries,內容只保存這個功能真正需要的資料:

ownerId
cafe.title
cafe.uri
createdAt
updatedAt

沒有先替未來想像十幾個欄位,也沒有把完整搜尋結果全部複製進來。

這裡也有一個必須說清楚的現況:舊版使用 cafe-favorites,新版使用 cafe-user-wishlists,兩個 collection 目前不共用資料。

舊版:cafe-favorites/{ownerId}/items
新版:cafe-user-wishlists/{ownerId}/entries

因此以前 Action Agent 裡的收藏,不會自動出現在新版想去清單。如果舊 collection 裡已有需要保留的正式資料,下一步應該做一次 migration:讀取舊項目、用 Maps URI 產生新版 ID,再寫入新的 entries。

Get Zona Wang’s stories in your inbox

Join Medium for free to get updates from this writer.

這不是兩套收藏可以永遠並存的優點,而是這次回頭比對後發現、需要明確記錄的資料銜接問題。

收藏按鈕也要確認是誰按的

推薦卡片上的按鈕帶有兩項資訊:搜尋 session ID,以及咖啡廳在這批結果中的編號。

Bot 收到操作後,不會直接相信按鈕傳回來的店名,而是重新讀取原本的搜尋 session,確認:

  • 這次搜尋仍然有效。
  • 操作者是原本的使用者。
  • 按鈕來自同一個聊天室。
  • 編號真的對應到這批推薦中的店家。

通過檢查後,才把咖啡廳寫入使用者自己的清單。

這樣做也讓按鈕資料保持很短。LINE Postback 不需要夾帶一整段店家 JSON,更不需要相信可以被重新組合的外部輸入。

不靠 Gemini,也能用一句話叫出想去清單

收藏完成後,使用者可以輸入:

我的想去清單
想去清單
我的收藏
收藏的咖啡廳

Bot 會把最近收藏的店做成 Flex Message 輪播。每張卡片提供三個下一步:

  1. 在 Google Maps 查看。
  2. 安排喝咖啡時間。
  3. 移出想去清單。

如果清單是空的,Bot 不只回答「沒有資料」,還會附上「傳送目前位置」按鈕,讓使用者能立刻回到找店流程。

我把單次顯示限制在最近 10 間。想去清單的目的,是幫助使用者做下一個決定,不是一次塞進無限長的歷史資料。

這些固定指令會先由 wishlist text handler 判斷,不必每次都呼叫 Gemini。和舊版相比,結果更固定、回覆更快,也不會為「查看清單」這類明確操作產生模型成本。

Press enter or click to view image in full size

從收藏安排,不需要重新搜尋

這次最重要的銜接,是收藏卡片上的「安排喝咖啡時間」。

使用者可能在幾天前收藏一間店。等到週末有空時,只要開啟想去清單、選擇日期時間,就能延續原本的行程流程,不必重新分享位置,也不必期待同一家店再次被推薦。

新的地方只有「時間是從 wishlist item 選出來的」。後續的 Calendar 與行程處理仍沿用既有能力,沒有為收藏另外複製一套排程系統。

這也是我這次刻意遵守的原則:

新功能負責補上缺口,不重新發明已經能工作的部分。

新版比較完整,但不是每一點都更好

新版在去重、卡片呈現、固定入口和後續行程上比較完整,但舊版仍有一個值得保留的設計:刪除前會二次確認。

目前新版點下「移出想去清單」後會直接刪除,操作比較快,卻也比較容易因為誤觸而失去收藏。

如果繼續改善,我會在兩種方向中選一個:

  • 刪除前重新顯示確認按鈕。
  • 先標記為刪除,短時間內提供「復原」。

產品化不代表新版本的每個決定都一定勝過舊版。把兩次實作放在一起比較,反而更容易看見被自己不小心拿掉的保護措施。

測試不只確認畫面有按鈕

這次新增的測試主要集中在收藏自己的規則:

  • 加入與移除的 Postback 能正確解析。
  • 不安全或超出範圍的 ID 會被拒絕。
  • 收藏卡片包含地圖、安排與移除操作。
  • 空清單會引導使用者重新找店。
  • 同一個 Google Maps URI 會產生相同且安全的文件 ID。
  • 所有 Postback 都維持在 LINE 的長度限制內。

加上既有測試,目前共有 40 項測試全數通過,TypeScript typecheck 與 GitHub Actions 也保持成功。

測試數量不是重點。真正要確認的是:重複點擊不會產生垃圾資料、過期按鈕不能亂寫入,以及舊功能沒有因為推薦卡多一個按鈕而壞掉。

收藏替「喜歡」和「行動」留下一點距離

這次重做的功能不大,甚至沒有新的 AI 模型。

但它讓 Bot 的使用方式自然了很多。使用者不需要在看到推薦時立刻做決定,也不會因為今天沒空去,就失去一間感興趣的店。

現在流程多了一個很符合生活的停靠點:

現在看到 → 先收藏 → 有空再決定

對我來說,這也是產品逐漸完整的一個訊號。不是每次更新都要讓 Bot 更聰明,也不是曾經做過的功能就不能重做。實驗版回答「做不做得到」,產品版則要回答「找不找得到、用不用得久」。

本篇完整程式碼

👉 GitHub: https://github.com/zonawang/line-cafe-wishlist

👉 更多 LINE Bot 與 AI 實作紀錄: https://github.com/zonawang/zona-ai-learning-lab

--

--

Zona Wang
Zona Wang

Written by Zona Wang

搭建程式與人之間的橋樑, 讓技術被真正理解、使用,也被好好回饋。