iT邦幫忙

2026 iThome 鐵人賽

DAY 7
0
ChatGPT & Codex

這隻 LINE Bot 不是我寫的:30 天讓 Codex 從零幫我做到上線系列 第 7

Day 7 - 「換一批」怎麼真的換?從搜尋 Session 到 LINE Postback Handler

  • 分享至 

  • xImage
  •  

上一篇,我和 Codex 決定讓按鈕只帶一個短 session ID,位置、偏好與上一批店名則暫存在 Firestore。

接下來才是使用者真正感受得到的部分:按下「換一批」後,怎麼確保不是把同樣的咖啡廳再送一次?按下「更適合工作」後,下一輪搜尋又該怎麼接著用這個偏好?

這篇會把整條流程接起來,但不鑽進每個型別或 Firestore 欄位,重點放在幾個實際會遇到的問題。


Postback 進來後,先確認這顆按鈕還能不能用

原本的 LINE handler 主要處理文字和位置訊息。加入 Postback 後,我們先在事件入口把它分流出去:

if (event.type === 'postback') {
  await handlePostbackEvent(event);
  return;
}

新的 handler 收到按鈕資料後,不會立刻搜尋,而是先做幾個確認:

  1. 這是不是認得的資料版本和 action?
  2. Session 還存在、也還沒過期嗎?
  3. 操作的人和聊天室,跟建立 session 時相同嗎?
  4. 上一個搜尋是不是還在進行?

只要其中一項不符合,就不會往下呼叫 Gemini。

例如舊按鈕過期,Bot 會請使用者重新傳位置;按鈕屬於其他人,就請目前使用者建立自己的搜尋。比起統一顯示「發生錯誤」,直接告訴使用者下一步要做什麼,實際上好用很多。


「換一批」不是把同一個搜尋再跑一次

如果座標和 prompt 完全相同,模型很可能再次推薦附近最熱門的幾間店。

所以每次搜尋完成後,我們會把這一批店名寫回 session。下一次按「換一批」時,再把它們當成排除清單:

const result = await findNearbyCafes(
  session.latitude,
  session.longitude,
  {
    preference,
    excludeNames: session.previousCafeNames
  }
);

Prompt 的語氣是「附近還有其他合理選擇時,優先避開上一批」,而不是一律禁止重複。

這個差別很重要。有些地方附近本來就沒有很多咖啡廳,如果為了硬湊不同店家,把搜尋範圍拉得太遠,結果反而更差。

「更適合工作」則會把 session 裡的偏好改成 work_friendly。之後再按「換一批」,這個偏好會繼續保留,不會突然跳回一般搜尋。

不過這顆按鈕只是調整搜尋方向,不是承諾每間店都有插座、Wi-Fi 或不限時。沒有明確 Maps 證據時,模型仍然不能自己補上這些資訊。


使用者連點時,不要真的搜尋好幾次

AI 搜尋需要一點時間。畫面還沒出現結果時,使用者很容易再點一次「換一批」。

如果每次點擊都真的呼叫 Gemini,不只聊天室會收到重複訊息,也會浪費 API 用量。

單純在 Node.js 裡放一個 isProcessing 並不夠,因為 Cloud Run 可能同時有多個 instance,彼此看不到對方記憶體裡的狀態。

所以我們把一個短暫的處理鎖放進 Firestore,並用 transaction 讀取與上鎖。兩個幾乎同時抵達的點擊,只有一個可以繼續;另一個會收到:

上一個搜尋還在進行中,請稍等結果出現。

搜尋完成或失敗後,程式會釋放鎖。這個設計不只防止畫面重複,也保護模型用量。

https://ithelp.ithome.com.tw/upload/images/20260818/20183556u1KAfmJioD.png


新功能壞掉時,原本的搜尋還是要能用

第一次位置搜尋完成後,程式才會嘗試建立 session。

  • 建立成功:顯示「換一批」與「更適合工作」
  • 建立失敗:照常送出咖啡廳,只顯示「重新選位置」

用比較生活化的說法,就是續杯服務暫時壞了,第一杯咖啡還是應該端上桌。

Quick Reply 也會跟著後端能力變化。有 session 才顯示兩顆 Postback 按鈕;沒有 session,就不放一顆按了必定失敗的按鈕。

這讓 Postback 成為原本功能的加強,而不是新的單點故障。


測試不需要猜模型會推薦哪一家店

Google Maps 資料和模型回答都可能改變,不適合把某間咖啡廳寫死在單元測試裡。

我們真正鎖住的是自己能控制的部分:

  • 支援的 action 能正常產生與解析
  • 錯誤版本或 session ID 會被拒絕
  • 有 session 時,Quick Reply 會出現正確按鈕
  • 沒有 session 時,只保留重新選位置
  • 原本的 Maps 來源去重仍然正常

最後再執行 typecheck 和單元測試,確認加入 Postback 後沒有破壞原本流程。

測試不需要假裝外部世界永遠不變,而是把我們自己定義的規則固定下來。


實戰小結

到這裡,「換一批」已經不只是畫面上的按鈕,而是一條完整流程:

收到 Postback
  → 驗證按鈕與 session
  → 取得處理鎖
  → 帶著上一批店名重新搜尋
  → 更新 session
  → 推送新結果

這次最有感的不是多了一個 handler,而是每個失敗情況都有清楚邊界:過期就重新開始、連點就先等待、Firestore 寫入失敗也不拖垮第一次搜尋。

功能在本機完成後,最後還有一個現實問題:怎麼把新版接到正式 LINE Bot,又不讓原本可用的服務一起冒險?下一篇會記錄這次的平行部署、Webhook Verify 和自動回復流程。


本篇完整程式碼

https://github.com/zonawang/codex-postback-action


上一篇
Day 6 - 我想替 LINE Bot 加「換一批」按鈕,結果先碰到一個問題:它怎麼記得上一輪?
系列文
這隻 LINE Bot 不是我寫的:30 天讓 Codex 從零幫我做到上線7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言