前幾天已經完成 Google Sheets 資料層,也在 Day 08 成功把 Gemini API 串進 SubWise。到目前為止,SubWise 已經可以做到「使用者輸入一句話 → Gemini 理解 → 回傳自然語言回答」。
例如使用者說「我今天午餐花了 120 元」,Gemini 可以回答「好的!已幫您記錄這筆消費……」。但這裡存在一個很重要的問題:
Gemini 說它「記錄好了」,是真的記錄好了嗎?
答案是:還沒有。目前 AI 只是「回答我」,並沒有真的把資料交給後端程式。因此 Day 09 的目標,是讓 SubWise 從「會聊天的 AI」,進一步變成「可以把使用者的自然語言轉換成程式資料的 AI」。
假設使用者輸入「我今天午餐花了 120 元」,對人類來說很容易理解類型、分類、金額、項目、日期,但如果 Gemini 只回「好的,已幫你記錄這筆午餐消費!」,Python 完全不知道金額、分類、日期分別是什麼。因此需要一種「AI 與程式之間的共同語言」,也就是 JSON:
{
"type": "expense",
"category": "Food",
"amount": 120,
"item": "午餐",
"date": "2026-08-09",
"note": null
}
Python 可以直接把它轉換成 Dictionary,資料流變成:
Gemini → JSON → Python Dictionary → Google Sheets
今天的重點不是「讓 Gemini 回傳漂亮的文字」,而是讓 Gemini 成為系統裡真正的資料理解層。
一開始只打算讓 Gemini 判斷消費,但實際測試後發現 SubWise 至少會遇到三種情況:Expense(消費)、Subscription(訂閱)、Chat(一般對話,例如「你是誰?」)。因此把 Day 09 的設計擴充成 expense、subscription、chat 三種資料類型,這是今天最重要的設計之一。
1.Expense:type、category、amount、item、date、note
2.Subscription:不套用 Expense 的欄位,改用 name、amount、billing_cycle、next_billing_date、category、note。例如「我每個月訂 Netflix,月費 390 元,每月 15 號扣款」會轉成對應的訂閱結構。這裡特別要求 Gemini 不同資料類型不要混用其他類型的欄位,未來接 Google Sheets 時會比較容易維護。
3.Chat:純聊天訊息不需要產生一堆空的記帳欄位,只回傳 type: "chat" 與 message,後端看到 type == "chat" 就知道這不是資料寫入操作。
測試時發現 Gemini 曾經回傳過錯誤日期。解決方式是由 Python 先取得系統日期 date.today().isoformat(),再明確告訴 Gemini「今天的日期是:2026-08-09」,並在 Prompt 中規定使用者說「今天」「昨天」時要以系統提供的日期為準,大幅降低 AI 自己亂猜日期的機率。
例如使用者只說「我今天花了 120 元」,金額與日期已知,但買了什麼、屬於什麼分類並不知道。Gemini 不應該自己猜成「Food / 午餐」,而是回傳 category: null、item: null。這個設計非常重要——AI 不確定 ≠ AI 可以自己猜,在記帳系統裡,AI 猜錯一次分類或金額,最後可能直接造成錯誤的財務資料。
限制 Gemini 的分類只能從固定選項選擇:Food、Transport、Entertainment、Shopping、Bills、Health、Education、Subscription、Other。這樣未來統計資料時,不需要處理「餐飲 / 吃飯 / 食物 / Food / food / Meals」這種分類名稱不一致的問題。
加入 json.loads(),把 Gemini 回覆的文字轉換成真正的 Python Dictionary:
import json
text = response.text.strip()
data = json.loads(text)
return data
同時加入例外處理,因為 AI 畢竟不是傳統 API,即使 Prompt 要求「只回傳 JSON」,仍可能遇到格式不正確的情況:
try:
data = json.loads(text)
return data
except json.JSONDecodeError:
print("⚠️ Gemini 回傳的內容不是有效 JSON")
return None
1.AI 可能判斷錯誤日期
「今天」不是一個固定日期,如果完全讓 Gemini 自己判斷,很可能產生錯誤。解決方式是由 Python 取得系統日期,再明確傳給 Gemini。
2.AI 可能自行補齊不存在的資料
例如「我今天花了 120 元」,AI 很容易自動猜成「Food / 午餐」,但這其實不是使用者說的。解決方式是在 Prompt 明確規定:不確定就使用 null,不可以自行猜測。
3.不同資料不能全部使用同一種 JSON
消費需要 amount、item、date,訂閱則需要 name、billing_cycle、next_billing_date,如果全部混在一起,未來資料庫會變得很亂。解決方式是根據 expense、subscription、chat 三種類型,各自使用不同的欄位結構。
4.AI API 不一定每次都成功
今天測試時遇到 503 UNAVAILABLE,錯誤訊息顯示模型當下負載較高。這讓我再次體會到:呼叫 AI API 時,不能假設每一次請求都會成功。目前先用 Exception Handling 保護程式,未來再進一步加入 Retry 機制。
測試三種不同類型,全部成功:
一般消費:「我今天午餐花了120元」→ 成功解析出 expense / Food / 120 / 午餐 / 2026-08-09
訂閱服務:「我每個月訂 Netflix,月費390元,每月15號扣款」→ 成功解析出 subscription / Netflix / 390 / monthly / 2026-08-15,代表 SubWise 已經不只會「記帳」,也開始能理解訂閱服務資料,這對後面的續訂提醒功能非常重要。
一般對話:「你是誰?」→ 成功回傳 chat 類型與 AI 自我介紹,代表一般聊天也能和記帳資料分開處理。
目前 SubWise 的資料理解架構:
使用者 → 自然語言輸入 → Gemini AI(意圖理解與解析)
→ 結構化 JSON 資料 → Python Dictionary
→ expense / subscription / chat 三個分支
之前的 SubWise 比較像「我問 AI,AI 回答我」,但現在開始變成「我告訴 AI 一句話,AI 幫我把這句話轉換成系統可以執行的資料」,這已經開始有 AI Agent 的雛形了。
expense / subscription / chat 三種結構化資料類型json.loads() 解析,Gemini 回覆成功轉換成 Python Dictionarynull,避免 AI 自行亂猜fb47985)/ GitHub Push目前 Gemini 已經可以把「我今天午餐花了 120 元」理解成結構化 JSON,下一步就是讓 Python 接手,把這個資料流串起來:
使用者輸入 → Gemini → JSON → Python Dictionary → google_sheets.py → Expenses
如果這一步成功,SubWise 就不再只是「會回答記帳問題的 AI」,而是可以真正做到「使用者說一句話,AI 自動幫你把資料存進帳本」——這才是我真正想完成的 SubWise。Day 10,正式開始讓 AI 動手做事。我們明天見!