鐵人賽進入倒數第二天。回頭看最開始的 SubWise,還只是一個很單純的想法:「能不能做一個真的可以幫我記帳、管理訂閱的 LINE Bot?」那時候從 Flask、LINE Webhook 開始,一步一步把功能拼起來。從最初只能 Echo「你剛剛說:你好」,到現在已經可以做到 AI 智慧記帳、發票/收據圖片辨識、消費與訂閱查詢、消費分析、訂閱管理、扣款提醒、消費修改與刪除、上下文理解、Quick Reply 互動操作、API 錯誤處理十項功能。
功能越來越多,也讓我開始發現一件事情:真正的應用程式,不是只有「功能可以成功」就夠了。 如果 API 暫時掛掉了呢?如果 Gemini 今天回傳 429 呢?如果 Google Sheets 沒有正常回應呢?如果使用者輸入了系統看不懂的內容呢?這些「不順利的情況」,其實也是產品的一部分。所以 Day 29,決定把重點放在:讓 SubWise 從「能運作」走向「更可靠」。
過去的 Gemini 呼叫方式比較簡單,如果 API 發生錯誤,就直接:
except Exception as e:
print(f"❌ Gemini API 發生錯誤:{e}")
return None
這種方式雖然可以避免程式直接崩潰,但有一個問題:**不知道到底發生了什麼類型的錯誤。**例如 429 是請求太頻繁或達到使用限制、503 是 Gemini 暫時無法提供服務,其他錯誤則可能是網路、設定或未知問題。如果全部都只回傳 None,到了 app.py 就很難針對不同情況做更好的處理。
在 gemini_client.py 中新增專門的錯誤類別:
class GeminiAPIError(Exception):
"""Gemini API 錯誤,用來把 API 錯誤狀態傳回 app.py。"""
def __init__(self, status_code=None, message=""):
self.status_code = status_code
self.message = message
super().__init__(message)
這個類別的目的很簡單:把 Gemini 發生的 HTTP 狀態碼一起帶出去,例如 GeminiAPIError(status_code=429, message="Test Gemini rate limit"),到了 app.py 之後就能知道 e.status_code 是多少。
修改 ask_gemini(),原本發生錯誤時只是印出訊息並回傳 None,現在改成先取得錯誤的 status_code:
status_code = getattr(e, "status_code", None)
raise GeminiAPIError(status_code=status_code, message=str(e))
這樣 API 層就負責「告訴上層到底發生什麼錯誤」,而 app.py 則負責「決定使用者應該看到什麼」,讓不同模組之間的責任更加清楚。
在 app.py 的 get_friendly_error_message() 中,分別針對兩種錯誤設計不同文案:
429(使用量較高):
"gemini_rate_limit":
"⏳ SubWise AI 目前使用量較高\n\n"
"請稍等一下,再重新輸入你的需求。",
503(服務暫時無法使用):
"gemini_unavailable":
"🔧 SubWise AI 目前暫時無法使用\n\n"
"AI 服務可能正在忙碌或維護中,"
"請稍後再試。",
兩種情況雖然都是「Gemini 出問題」,但對使用者而言,原因與下一步其實不太一樣——429 代表「等一下再試」,503 代表「服務本身有狀況」,區分開來能讓使用者更清楚該怎麼反應,這就是今天想補上的細節。
完成程式修改後,沒有直接假設它可以正常運作,而是先做基本測試。
try:
raise GeminiAPIError(status_code=429, message="Test Gemini rate limit")
except GeminiAPIError as e:
print("status:", e.status_code)
print("message:", e.message)
成功得到 status: 429、message: Test Gemini rate limit,代表錯誤物件可以正常保存 HTTP Status Code 與 Error Message。
分別執行 get_friendly_error_message("gemini_rate_limit") 與 get_friendly_error_message("gemini_unavailable"),都成功得到對應的友善提示文字。

新增錯誤處理後,最重要的是確認原本正常的功能沒有被改壞。實際在 LINE 輸入「午餐 120」,SubWise 依然可以正常完成記帳,回覆完整的日期、分類、金額、項目資訊——新增錯誤處理,不應該讓原本正常的功能壞掉。
GeminiAPIError 類別,攜帶 HTTP status_code 與錯誤訊息ask_gemini(),讓 API 層負責辨識錯誤類型最後一天,不打算再單純追求「多做一個功能」。Day 30 的目標,是把前面 29 天累積的功能,真正當成一個完整產品來驗收。
會進行最後的 End-to-End 測試,從使用者輸入開始,一路確認 LINE → Webhook → Intent/Context → Gemini AI → Service Layer → Google Sheets → 分析/查詢/訂閱 → LINE 回覆這整條路徑,同時重新檢查 AI 記帳、發票辨識、消費查詢、訂閱管理、扣款提醒、消費分析、修改/刪除、上下文理解、錯誤處理九大主要功能。
最後再整理這 30 天到底完成了什麼,以及這次用 ChatGPT / Codex 進行 Vibe Coding 的過程中,真正學到了什麼。Day 30,將會是這個 SubWise 專案的最後一章。 明天,正式完賽。我們明天見!