iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0
ChatGPT & Codex

AI 時代的輕量化開發:ChatGPT 打造 LINE 多模態記帳與續訂預警 Agent系列 第 23

[Day 23] 一句話就能問帳:打造 SubWise 的自然語言財務搜尋引擎

  • 分享至 

  • xImage
  •  

前言

前幾天完成基礎消費查詢後,SubWise 已經能夠回答「我最近有哪些消費?」或「我這個月花了多少錢?」。但在實際測試與日常使用中,很快發現一個關鍵盲點:使用者想查的通常不只是「全部消費」,而是特定情境的支出

例如日常中更常見的問法是:「我這個月交通花多少?」、「最近 Food 花了多少錢?」。如果每次查詢都只能將整段期間的資料全部倒出來,查詢體驗依然不夠直覺。因此今天的開發核心,是讓 SubWise 的查詢能力從單純的「查詢某個期間」,進一步升級為 「查詢某個期間 + 指定消費分類」


今日實作實錄

一、擴充 Query 結構與語意解析職責

在目前的 Agent 架構下,Gemini 專注負責將自然語言轉譯為結構化 JSON,真正的資料過濾與計算則交由後端 Python 處理:

使用者輸入自然語言
        ↓
     Gemini
        ↓
    Query JSON (新增 category 欄位)
        ↓
 query_service.py
        ↓
  Google Sheets
        ↓
 整理並計算查詢結果
        ↓
    LINE 回覆

當使用者詢問「我這個月交通花多少?」,Gemini 會解析出帶有 category 條件的 Query 物件:

{
    "type": "query",
    "target": "expense",
    "period": "month",
    "keyword": null,
    "category": "Transport"
}

目前系統支援的標準分類包含:FoodTransportEntertainmentShoppingBillsHealthEducationSubscriptionOther。這種分工讓 AI 專注於意圖識別,後端專注於資料處理,維持系統的高內聚與低耦合。

二、在 query_service.py 實作分類篩選函式

在資料服務層新增 filter_expenses_by_category(records, category=None),確保函式職責單一且可組合:

def filter_expenses_by_category(records, category=None):
    """
    根據消費分類篩選資料。
    """
    if not category:
        return records

    filtered = []
    for record in records:
        record_category = str(record.get("Category", "")).strip()
        if record_category == category:
            filtered.append(record)

    return filtered

若使用者未指定分類,則回傳全量紀錄;若指定分類(如 Transport),則僅保留符合該類別的資料列。

三、重構 query_data() 處理管線:先縮小範圍再統計

將分類篩選無縫接入既有的 query_data() 處理流程中,實現「先縮小資料範圍,再進行統計與排序」的設計原則:

Google Sheets 原始紀錄
        ↓
 期間篩選 (filter_expenses)
        ↓
 分類篩選 (filter_expenses_by_category) ← 今天新增
        ↓
 統計總額與筆數
        ↓
 計算最高單筆與日期排序
        ↓
 格式化輸出 (format_expense_result)

核心程式碼片段如下:

records = get_expenses()
records = filter_expenses(records, period)
records = filter_expenses_by_category(records, category)

return format_expense_result(records, period)

這樣的流程確保了當使用者詢問「這個月交通花多少?」時,系統是針對「本月 + Transport」的子集合精準計算,而非撈出全月資料後再進行二次拼湊。

四、使用者體驗優化:分類名稱對照與豐富查詢版面

為了讓輸出更具產品感,除了總支出與消費筆數,還額外加入了最高單筆消費與按時間倒序的最近消費清單。同時建立分類顯示名稱對照表,將內部英文標籤轉換為帶有 Emoji 的繁體中文:

CATEGORY_DISPLAY_NAMES = {
    "Food": "🍜 餐飲",
    "Transport": "🚇 交通",
    "Entertainment": "🎮 娛樂",
    "Shopping": "🛍️ 購物",
    "Bills": "💡 生活帳單",
    "Health": "❤️ 醫療保健",
    "Education": "📚 學習",
    "Subscription": "🔔 訂閱",
    "Other": "📦 其他"
}

內部邏輯維持標準字串以利運算,對外呈現則轉化為友善介面,讓最終回覆在 LINE 上呈現清晰易讀的版面:

💰 SubWise 消費查詢

📅 查詢期間:month
💵 總支出:NT$350
🧾 消費筆數:5 筆

🔥 最高單筆消費
📅 日期:2026-08-20
💵 金額:NT$100
📂 分類:🚇 交通
📝 備註:禾誕股份有限公司

📋 最近消費:
• 2026-08-21|🚇 交通|NT$100|捷運
• 2026-08-20|🚇 交通|NT$100|禾誕股份有限公司
• 2026-08-17|🚇 交通|NT$50|捷運
• 2026-08-10|🚇 交通|NT$50|捷運
• 2026-08-08|🚇 交通|NT$50|MRT

測試結果

先針對 filter_expenses_by_category() 進行單元測試,驗證以 FoodTransport 篩選測試陣列時皆能精確過濾無誤。

接著透過 process_ai_message() 執行端到端自然語言整合測試:

  • 測試「我這個月交通花多少錢?」:Gemini 正確產出 period: monthcategory: Transport,並成功回傳總支出 NT$350(5 筆)及對應明細。
    https://ithelp.ithome.com.tw/upload/images/20260824/20178527v2rZ19O2ob.png

  • 測試「我最近 Food 花多少錢?」:成功解析為本週餐飲查詢,輸出「🍜 餐飲」之總支出 NT$550(4 筆)。
    https://ithelp.ithome.com.tw/upload/images/20260824/20178527UGpmU7zuih.png


今日完成

  • 在 Query JSON 架構中擴充 category 篩選欄位
  • 實作 filter_expenses_by_category() 分類過濾函式
  • 重構 query_data() 流程,實現「期間 + 分類」雙重條件查詢
  • 建立 CATEGORY_DISPLAY_NAMES 對照表,提升 LINE 輸出體驗
  • 完成單元測試與 LINE Agent 自然語言整合驗證

目前 SubWise 的自然語言查詢維度涵蓋:

查詢維度 範例問法 轉譯條件
📅 期間查詢 「我這個月花多少?」 period: month
📅 近期動態 「我最近有哪些消費?」 period: week
🏷️ 期間 + 分類 「我這個月交通花多少?」 period: month, category: Transport
🏷️ 近期 + 分類 「最近 Food 花多少?」 period: week, category: Food
📊 統計輸出 總額、筆數、最高單筆、按日排序明細 完整格式化輸出

明日預告

在消費查詢與分析逐步完善後,Day 24 將正式跨入核心場景之一的 「訂閱管理」 ——實作週期性訂閱項目的自然語言新增與 Google Sheets 寫入,讓 SubWise 也能妥善追蹤每月的固定扣款支出。我們明天見!


上一篇
[Day 22] 一句話問 AI,我的錢花去哪了?SubWise 查帳能力解鎖
下一篇
[Day 24] 從記帳 Bot 到生活 Agent:SubWise 開始管理訂閱
系列文
AI 時代的輕量化開發:ChatGPT 打造 LINE 多模態記帳與續訂預警 Agent24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言