前兩天我們已經把檢索增強生成(RAG)與工具使用(Tool Use)的邊界講清楚:文件適合尋找「已經存在的知識」,但精確數字、最新狀態與計算結果,應該直接從結構化資料來源取得。今天就把這個觀念真正落地,讓 Data Machi 第一次透過工具查詢 Google Sheets。
這一篇的重點不是把整張試算表貼進 Prompt,再請大型語言模型幫忙算答案。剛好相反,我們希望把「資料存取與計算」交給程式,把「理解使用者意圖與整理答案」留給模型。這樣不但可以降低模型自行計算錯誤的風險,也讓每一個結果都能重新驗證。
整體流程可以先理解成:
使用者問題
↓
LLM 理解需求
↓
Google Sheets Tool
↓
讀取結構化資料
↓
Pandas / 程式篩選與計算
↓
精確結果
↓
LLM 整理成自然語言回答
如果後端程式需要自動讀取 Google Sheets,就不適合每一次查詢都等待某個使用者手動登入 Google 帳號。比較穩定的方式,是建立一個 Service Account(服務帳號),讓後端以一個獨立的「機器身分」存取指定的試算表。
可以把它想成替 Data Machi 建立一個專門的系統帳號。這個帳號不需要像一般使用者一樣登入 Google Workspace,而是由後端使用憑證證明自己的身分,再依照被授予的權限存取資料。
基本流程可以整理成:
Google Cloud Project
↓
啟用 Google Sheets API
↓
建立 Service Account
↓
建立 Credentials
↓
取得 Service Account Email
↓
將 Google Sheet 分享給該 Email
↓
Backend 使用憑證讀取 Sheet
建立時,可以先到 Google Cloud Console 選擇或新增一個 Project,接著到 API Library 啟用 Google Sheets API。如果程式未來還需要搜尋或列出 Google Drive 中的檔案,再依需求啟用 Google Drive API,不需要一開始把所有 API 都打開。
接著建立一個容易辨識用途的 Service Account,例如:
data-machi-sheets-reader
第一版的目標只是讀取指定的測試試算表,因此不需要給它 Project Owner 或其他過大的管理權限。建立完成後,可以取得 Service Account 的 Email,再把匿名測試試算表分享給這個 Email;如果目前只需要讀取資料,Google Sheets 權限先設定成 Viewer 即可。
這一步很容易被忽略。即使後端已經持有有效憑證,如果試算表本身沒有分享給 Service Account,程式依然沒有權限讀取內容。
建立 Service Account 後,後端需要一組憑證才能證明自己的身分。無論實際採用哪一種憑證管理方式,都應該遵守和 Gemini API Key 相同的原則:Secret 不應該直接進入 GitHub Repository。
本機開發時,如果暫時使用 JSON Credential File,至少應該將檔案加入 .gitignore,避免不小心 Commit。不過部署到 Render 時,比較方便的方式之一,是把需要的憑證內容透過 Environment Variables 提供給 Backend。
例如:
GOOGLE_SERVICE_ACCOUNT_JSON={"type":"service_account",...}
GOOGLE_SHEET_KEY=your_sheet_id
Backend 啟動時再從環境變數讀取:
Environment Variables
↓
Backend
↓
建立 Google Credentials
↓
Google Sheets API
這樣本機與正式環境可以使用不同的 Secret,而程式碼本身不需要跟著修改。
另外也要延續 Day 10 的安全原則:Google Service Account Credential 只能存在後端。 不要把它放進 React 或 Vercel 前端,因為前端程式最終會執行在使用者瀏覽器中。
第一次建立 Google Sheets Tool 時,不需要直接連接公司的正式營運資料。比較好的做法,是先建立一份結構清楚、內容可以公開的匿名測試資料,確認權限、欄位與計算流程都正確之後,再考慮接上真正的企業資料來源。
例如可以建立一張簡單的需求工單資料:
| Request ID | Created Date | Market | Category | Status | Owner |
|---|---|---|---|---|---|
| R001 | 2026-07-01 | Taiwan | Dashboard | Completed | User A |
| R002 | 2026-07-03 | Hong Kong | Data Query | In Progress | User B |
| R003 | 2026-07-04 | Taiwan | Dashboard | Completed | User A |
| R004 | 2026-07-08 | Taiwan | Analysis | In Progress | User C |
| R005 | 2026-07-12 | Hong Kong | Dashboard | Completed | User B |
這時候先不要問 AI「今年台灣哪一類需求最多」,而是先確認最基本的資料讀取是否成功。例如程式是否真的讀到 5 筆資料、欄位名稱是否正確,以及日期欄位有沒有被正確解析。
這裡仍然延續前面的原則:
一次只驗證一層。
整個測試順序可以按照:
Service Account 可以驗證
↓
Google Sheet 有正確授權
↓
Backend 可以讀到資料
↓
欄位型態正確
↓
Pandas 可以篩選與計算
↓
最後才加入 LLM
不要一開始就把 LLM、Tool Calling、Google 權限、資料計算與前端全部混在一起除錯。否則當回答出錯時,很難知道問題究竟發生在哪一層。
Google Sheets Tool 第一次串接時,最常遇到的通常不是 AI 問題,而是身分與資源權限問題。
| 現象 | 優先檢查 |
|---|---|
| 找不到 Spreadsheet | Sheet ID 是否正確 |
| Permission Denied | Sheet 是否分享給 Service Account |
| Credentials Error | Service Account Credential 是否正確載入 |
| API 未啟用 | Google Sheets API 是否已啟用 |
| 可以連線但沒有資料 | Worksheet 名稱、Range 或欄位設定 |
其中最常見的情況是:Service Account 已經建立成功,Backend 也可以完成驗證,但 Google Sheet 忘記分享給 Service Account Email。這時候並不是憑證失效,而是「你知道自己是誰,但這份文件沒有授權給你」。
把 Authentication(你是誰) 和 Authorization(你能不能存取這份資料) 分開理解,後面串接其他企業系統時也會非常有幫助。
當 Google Sheets 已經可以正確讀取後,下一步才是把自然語言問題轉換成資料查詢。
假設使用者詢問:
「今年哪一個市場的工單最多?」
比較可靠的方式不是把所有 Rows 全部塞給模型,再請它自己數,而是先把使用者問題轉換成程式可以理解的條件,再由 Pandas、SQL 或其他資料工具執行真正的篩選與計算。
自然語言問題
「今年哪一個市場的工單最多?」
↓
LLM 理解需求
↓
轉換成查詢條件
Year = 2026
Group By = Market
Metric = Request Count
↓
Pandas / 程式計算
↓
Taiwan = 3
Hong Kong = 2
↓
LLM 整理回答
模型可以理解「今年」、「市場」與「最多」代表什麼,但真正的 Count、Sum、Average、Group By、Sort 或比例計算,應該盡可能交給可重現的程式邏輯。
例如 Pandas 可以執行:
result = (
df[df["Created Date"].dt.year == 2026]
.groupby("Market")["Request ID"]
.nunique()
.sort_values(ascending=False)
)
程式得到精確結果之後,再把結果交給模型:
Taiwan: 3
Hong Kong: 2
最後由模型整理成:
2026 年目前台灣的工單數最多,共有 3 筆;香港則有 2 筆。
這個分工和 Day 12 提過的原則完全一致:
模型負責理解意圖,程式負責執行事實。
如果資料只有五列,看起來把整張表貼進 Prompt 好像也沒有什麼問題。但當資料增加到數千或數萬筆之後,這種方式會開始面臨幾個明顯限制:Context 會快速膨脹、Token 成本增加,而且模型仍然需要自己從大量文字中篩選與計算。
更重要的是,企業分析通常希望相同條件能產生相同結果。假設「Active Customer」在系統中有明確定義,就應該把計算邏輯保留在 SQL、Python 或資料工具中,而不是每一次都讓 LLM 根據文字描述重新理解一次。
因此比較穩定的架構是:
Google Sheets
↓
Data Tool
↓
程式篩選 / 計算
↓
小而精確的結果
↓
LLM
↓
自然語言回答
大型語言模型拿到的不需要是整張資料表,而是完成問題所需要的結果與必要 Context。
真實企業資料的欄位名稱,通常和人類平常說話的方式不完全一樣。例如資料表可能使用:
request_market
created_at
ticket_category
request_status
owner_name
但使用者實際上可能會問:
「今年台灣哪一種需求最多?」
他不會說:
「請 group by
ticket_category,然後 filterrequest_market = TW。」
因此 Tool Layer 通常需要處理一層 Alias Mapping(欄位別名對應),將人類使用的業務語言轉換成真正的資料 Schema。
例如:
| 使用者語言 | 真實欄位 |
|---|---|
| 市場、國家 | request_market |
| 建立日期、日期 | created_at |
| 類型、需求類別 | ticket_category |
| 狀態、進度 | request_status |
| 負責人 | owner_name |
當使用者詢問:
「今年台灣哪一類需求最多?」
系統就可以整理成:
時間 → created_at
市場 → request_market
需求類別 → ticket_category
接著再交給程式執行。
這一層做得好有兩個好處。第一,使用者不需要學習資料庫的欄位名稱,仍然可以使用平常工作的語言;第二,如果未來資料 Schema 改名,只需要修改 Mapping,而不需要重寫所有 Prompt 或 Tool Description。
自然語言中的日期特別容易產生歧義,例如:
模型可以協助理解這些文字,但真正送進資料工具前,最好轉換成明確的日期範圍。
例如:
使用者:
「最近三個月台灣有多少工單?」
↓
轉換成:
start_date = 2026-06-01
end_date = 2026-08-31
market = Taiwan
metric = request_count
接下來程式只需要按照明確條件執行,不需要再次猜測「最近三個月」到底代表什麼。
尤其當企業報表存在 Fiscal Year、Business Week 或特殊 Campaign Period 時,更不能假設每個人的「一季」與「一個月」定義完全相同。這些規則最後都應該進入可以驗證的 Business Logic。
如果 Google Sheet 有幾千筆資料,而且一天只更新幾次,就沒有必要每一次使用者問問題時,都重新完整下載一次資料。這時候就可以加入 Cache(快取)。
例如:
第一次查詢
↓
Google Sheets API
↓
載入資料
↓
保存 Cache
↓
執行計算
下一次查詢
↓
Cache 還在有效時間內?
↓
是 → 使用 Cache
否 → 重新讀取 Google Sheets
Cache 可以降低 API 呼叫次數,也能縮短回答等待時間,但它同時帶來另一個問題:快取中的資料不一定是最新的。
因此 Cache Duration 不應該單純設定成「越久越好」,而應該根據業務資料的更新頻率決定。一份每天更新一次的資料,和每五分鐘更新一次的營運 Dashboard,本來就不應該使用相同策略。
如果使用者明確說:
「我剛剛更新了 Sheet,請重新確認。」
或:
「請查目前最新狀態。」
系統也應該能選擇跳過既有 Cache,重新取得資料,而不是盲目使用舊結果。
當系統開始使用 Cache 或外部資料來源後,「這個數字是什麼」和「這個數字是什麼時間取得的」會同樣重要。
因此 Tool Result 可以順便保留:
data_source = Google Sheets
retrieved_at = 2026-08-24 15:30
row_count = 1520
最後回答就可以呈現:
截至 2026/08/24 15:30 取得的資料,台灣目前共有 328 筆工單,其中 Dashboard 類型最多,共 124 筆。
這樣使用者才知道目前看到的數字是否符合他對「最新」的期待。如果來源資料一天前才更新,也可以誠實呈現,而不是讓語言模型營造出「這就是現在即時數字」的錯覺。
還有一個非常容易被忽略的問題:使用者可能會問資料裡根本不存在的欄位。
假設目前 Sheet 只有:
Request ID
Created Date
Market
Category
Status
Owner
使用者卻詢問:
「哪一個工單的處理成本最高?」
資料裡根本沒有 Cost 欄位。這時候 Tool 不應該看到 Category 或其他看起來相關的欄位,就自行推測使用者可能想問別的東西。
比較正確的回覆是:
目前這份資料沒有「處理成本」欄位,因此無法根據現有資料回答。如果有成本資料來源,需要另外連接或加入對應欄位。
這個設計看起來比較保守,但對企業 AI 反而更重要,因為:
明確承認資料不存在,比使用錯誤欄位產生一個看似合理的答案更可靠。
完成工具之後,不要只測一個問題就宣告成功。至少可以從三個層級開始驗證。
第一類是簡單查詢,例如:
「目前共有多少筆工單?」
第二類是篩選與彙總,例如:
「2026 年台灣有多少筆工單?」
第三類是組合條件與排序,例如:
「2026 年台灣哪一類工單最多?」
可以整理成:
| 測試類型 | 問題 | 要驗證的能力 |
|---|---|---|
| 基礎計數 | 共有多少筆資料? | 資料是否完整讀取 |
| 條件篩選 | 今年台灣有多少工單? | 日期與 Market 篩選 |
| Group By | 哪一類工單最多? | 分組、計數與排序 |
| 組合條件 | 今年台灣哪一類最多? | 多條件查詢 |
| 不存在欄位 | 哪一個工單成本最高? | 能否正確拒答 |
每一次測試都應該把 Data Tool 的結果和原始 Google Sheet 人工核對,而不是只判斷模型最後說得是否通順。如果原始資料算出 Taiwan 是 3 筆,而 AI 說 4 筆,即使回答文字寫得再漂亮,這個工具仍然沒有通過驗證。
最好可以同時保存:
原始資料
+
使用者問題
+
Tool Parameters
+
程式計算結果
+
最終回答
這樣發生錯誤時,就能清楚知道到底是模型理解錯問題、參數 Mapping 錯誤、程式計算錯誤,還是最後回答整理錯誤。
實際把 Google Sheets Tool 放進企業工作流後,有三個問題值得再整理一次。
第一個是資料新鮮度。Cache 可以提升速度,但回答最好保留資料取得時間;如果使用者明確要求重新確認,就應該重新讀取來源。
第二個是時間條件。「上個月」、「最近三個月」、「Q1」等自然語言應該先轉成明確日期範圍,再交給資料工具執行。
第三個是Schema 邊界。如果資料來源根本沒有使用者要求的欄位,就應該明確回報無法回答,而不是自己選擇一個相似欄位替代。
這三件事情其實都指向同一個設計原則:
模型可以協助理解使用者在問什麼,但真正影響事實結果的條件,最好轉換成可以驗證的程式規則。
從今天開始,Data Machi 已經不只有文件搜尋能力,也開始能從結構化資料中取得精確結果。這也是從「企業知識問答」走向「企業資料工作流」很重要的一步。
今天的重點:
Data Machi 第一次擁有可驗證的結構化資料工具。模型不再負責「猜數字」,而是判斷什麼時候需要查資料,再由 Google Sheets Tool 與程式完成精確的篩選、計算與驗證。
下一篇,我們會把視角再拉大。企業知識從來不只存在於 Google Sheets 或 PDF,而可能散落在文件、資料庫、專案工具與各種 SaaS 系統中。先畫出企業的 Knowledge Map(知識地圖),才能決定下一個工具究竟應該接什麼。
我們下集見囉!