我常常去咖啡廳,但每次走到櫃檯前,都會發生同一件事:
到底要喝什麼?
美式很安全,但今天好像不想喝得那麼苦;拿鐵也不錯,又會忍不住想看看有沒有比較特別的飲料。
如果菜單上還出現一排沒喝過的名字,我就會站在旁邊看很久。後面有人排隊時,又會因為不好意思,最後點回最熟悉的那一杯。
我的 LINE Cafe Bot 已經可以幫我找店、收藏想去的咖啡廳,也能安排時間。但真的走進店裡後,它的工作就結束了。
所以這次我想把它再往店裡帶一步:
如果我可以直接拍下菜單,讓 Bot 先幫我縮小選擇呢?
這次我和 Codex 一起完成了 line-cafe-menu-recommender。
如果只是把菜單上的文字全部抄出來,我還是得重新讀一次,選擇障礙並不會消失。
所以我沒有把需求寫成「幫我做 OCR」,而是:
看懂這是不是飲品菜單
↓
找出照片中真的存在的品項
↓
最多推薦三杯不同方向的選擇
↓
告訴我品名、價格和推薦理由
為什麼最多三杯?
因為這個功能本來就是要減少選項。如果 Bot 看完一張菜單,又回給我十幾杯飲料,我只是從看紙本菜單,變成看手機菜單而已。
三杯剛好可以讓我比較,又不會重新陷入選擇困難。
現在只要在 LINE 輸入:
拍菜單
Bot 就會顯示兩個按鈕:
選好照片後,使用流程是:
拍一張飲品菜單
↓
Bot 顯示分析中
↓
Gemini 看懂照片
↓
LINE 顯示最多三張推薦卡片
其實也可以直接傳圖片,不一定要先輸入「拍菜單」。那個文字入口只是讓第一次使用的人知道可以開啟相機或相簿。

我之前已經做過 LINE Camera Action,所以這次沒有重新研究如何打開相機。
真正的新問題是:使用者把照片送出後,我的後端拿到的是什麼?
一開始我以為 webhook 會直接收到整張圖片,實際上 LINE 傳來的內容比較像:
{
"type": "image",
"id": "圖片的 message ID"
}
它只告訴 Bot:「有人傳了一張圖片,這是它的 ID。」
真正的圖片還要再用 message ID,向 LINE Content API 取得:
使用者傳送照片
↓
Webhook 收到 message ID
↓
LINE Content API 下載圖片
↓
Gemini 分析圖片內容
這是我這次第一個學到的觀念:收到圖片事件,不等於已經收到圖片本身。
取得圖片後,我把照片和一組清楚的規則一起交給 Gemini。
我希望它回答幾件事:
最後呈現在 LINE 裡的內容,大致像這樣:
推薦 1
Cafe Latte
$140
想喝口感比較溫和的咖啡時,可以考慮這杯。
────────────
咖啡因:咖啡因含量中等
甜度:無法確認
每杯飲料各自放在一張 Flex Message 卡片裡,可以左右滑動比較。
我刻意沒有在卡片上塞更多功能。站在櫃檯前的我,只需要快點縮小選擇,不需要再進入另一套複雜流程。
我很在意一件事:推薦一定要來自照片中真的看得到的品項。
如果我拍到桌面、招牌,或照片反光到完全看不清楚,Bot 不應該為了完成任務,自己編出三杯飲料。
這種情況下,它會直接回答:
我還看不清楚這張照片中的飲品菜單,請一次拍一頁,避免反光,並讓品名和價格清楚入鏡。
價格也是一樣。
如果品名看得到、價格卻模糊,它會標示「菜單未清楚標示」,而不是猜一個看起來合理的數字。
對我來說,這比每次都很有自信地給出答案重要得多。
功能寫完後,單元測試都通過了,但我還是想知道:Gemini 真的看得到圖片裡的品項嗎?
所以我和 Codex 做了一張只有三個選項的測試菜單:
Americano $100
Cafe Latte $140
Black Tea $90
Gemini 最後正確讀出三個名稱和價格,沒有多編第四杯。
測試圖片下方還放了一句:
Please ask our staff about ingredients.
回覆裡也保留了「成分請向店員確認」的提醒。
這個結果看起來很簡單,卻同時確認了圖片傳送、模型權限、菜單辨識和回覆格式真的接得起來。
這次不是另外做一個只能辨識圖片的展示專案。
Codex 先讀取上一站的程式結構,保留原本的附近搜尋、偏好、行程、回訪、咖啡足跡和想去清單,再把圖片事件接進同一個 webhook。
它協助完成:
我負責說明真實使用情境:我不是想把菜單抄進手機,而是希望站在櫃檯前不要再選那麼久。
Codex 則把這個有點生活化的困擾,拆成可以實作、可以測試,也可以安全上線的功能。
原本的 Cafe Bot 處理的是:
去哪一家 → 什麼時候去 → 之後記得回來評分
現在又多了一步:
到了店裡 → 拍下菜單 → 決定喝什麼
它沒有真的治好我的選擇障礙,但至少可以先把整張菜單縮成三個方向,讓我不用在櫃檯前盯著幾十個品項發呆。
而在這個看起來很簡單的功能背後,還有另一半我沒有在這篇展開:圖片要不要保存、怎麼限制大小、如何避免模型亂猜,以及為什麼我不直接相信模型回傳的文字。
這些會放在下一篇,繼續整理這個功能從「可以用」走到「可以放心上線」的過程。
GitHub:
https://github.com/zonawang/line-cafe-menu-recommender
LINE Messaging API — Get content:
https://developers.line.biz/en/reference/messaging-api/#get-content
Gemini Image Understanding:
https://ai.google.dev/gemini-api/docs/image-understanding