前面談 Agent 與 decision model,關注的是如何理解需求、選擇下一步,以及判斷任務是否完成。當任務涉及真實世界,這些決策還需要具體資料支持。
例如,匯入資料寫著「北車」,資料庫登錄的卻是「台北車站」,Agent 如何建立對應?另一個景點雖然位於不同縣市,但就在前往目的地的途中,Agent 又該如何確認它適合納入行程?
先前介紹 Google Maps Grounding Lite MCP 時,已經說明如何透過 search_places 取得地點與來源。這篇接著處理搜尋之後的問題:確認地點身分、對應自有資料庫,以及用路線資料判斷是否順路。
以下使用 Claude Code 作為 Agent 的操作入口。Google 已提供的能力直接透過官方 MCP 使用;自有資料庫查詢與沿途搜尋,則示範如何包成自己的 MCP 工具。
Google 目前提供的 Resolution API,可以將具體地點名稱、地址或 Google Maps 連結解析成 Place ID。Place ID 是 Google Maps 用來識別地點的代碼,可以交給其他 Maps API 使用。
這項服務也提供 Grounding Lite MCP 工具:
| 工具 | 用途 |
|---|---|
resolve_names |
將具體地點名稱或地址解析成 Place ID |
resolve_maps_urls |
將 Google Maps 地點連結解析成 Place ID |
名稱解析回應包含信心程度,也可以加入區域偏好。它提供的是外部地點的解析結果,並不會自動知道自有資料庫裡哪一筆紀錄代表這個地點。
先在 Google Cloud Console 的專案中啟用 Maps Grounding Lite API、帳務與可使用該服務的 API key。官方 MCP 端點為 https://mapstools.googleapis.com/mcp,使用 API key 時透過 X-Goog-Api-Key 標頭傳入。
在專案根目錄的 .mcp.json 加入以下設定;若檔案已存在,合併 mcpServers 內容即可:
{
"mcpServers": {
"google-maps": {
"type": "http",
"url": "https://mapstools.googleapis.com/mcp",
"headers": {
"X-Goog-Api-Key": "${GOOGLE_MAPS_API_KEY}"
}
}
}
}
啟動 Claude Code 前,在同一個終端設定環境變數:
export GOOGLE_MAPS_API_KEY="你的 Google Maps API key"
claude
進入 Claude Code 後,使用 /mcp 確認連線與工具是否載入,並依客戶端提示接受專案的 MCP 設定。
完成這一步,Agent 就能呼叫 Google 的地點解析工具。不過,要把解析結果對應到自有資料,還需要資料庫工具。
假設要匯入的 A 資料寫著「集合地點:北車」,已知縣市是台北市;資料庫則登錄「台北車站」。兩邊都沒有地址、座標或 Google Place ID。
我們希望建立的是:
A 資料的「台北市/北車」
→ 資料庫的「台北市/台北車站」
→ 內部地點 ID 101
這個案例需要處理名稱差異,不能只靠 SQL 比對文字。可以先讓 Google 分別解析兩邊名稱,再檢查是否對應到同一個 Google 地點。
以下是教學用的簡化資料表:
CREATE TABLE places (
id INTEGER PRIMARY KEY,
canonical_name TEXT NOT NULL,
city TEXT NOT NULL,
google_place_id TEXT
);
CREATE INDEX idx_places_google_place_id
ON places (google_place_id);
INSERT INTO places (id, canonical_name, city)
VALUES
(101, '台北車站', '台北市'),
(102, '松山車站', '台北市');
其中,id 是應用程式使用的內部識別碼;google_place_id 則是可選的外部識別碼。最終 A 資料應關聯的是內部 ID 101,而不是把原始文字直接改成另一個名稱。
資料庫查詢可以使用現有的 DBHub MCP server。DBHub 專案
先在專案建立 dbhub.toml:
[[sources]]
id = "places_db"
dsn = "sqlite:///absolute/path/project/places.db"
[[tools]]
name = "list_places_in_city"
description = "依縣市取得內部地點候選,供名稱解析與比對"
source = "places_db"
statement = """
SELECT id, canonical_name, city, google_place_id
FROM places
WHERE city = ?
ORDER BY id
LIMIT 21
"""
[[tools.parameters]]
name = "city"
type = "string"
description = "資料庫使用的縣市名稱,例如台北市"
required = true
這是本文自訂的查詢設定,由 DBHub 執行參數化 SQL。? 綁定傳入的縣市名稱,不需要讓模型自行拼接查詢。
在 Claude Code 的 .mcp.json 中,將以下項目合併到既有的 mcpServers,並保留前文設定的 google-maps:
"places-db": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@bytebase/dbhub@latest",
"--transport",
"stdio",
"--config",
"/absolute/path/project/dbhub.toml"
]
}
這個工具只定義一段 SELECT,不會寫入 mapping。DBHub 的自訂 SQL 工具會執行設定中的 statement,其讀寫範圍應由設定內容及資料庫權限控制。
Agent 呼叫 list_places_in_city:
{
"city": "台北市"
}
對前面的範例資料,會取得以下兩筆候選:
[
{
"id": 101,
"canonical_name": "台北車站",
"city": "台北市",
"google_place_id": null
},
{
"id": 102,
"canonical_name": "松山車站",
"city": "台北市",
"google_place_id": null
}
]
Agent 呼叫 Google 官方 MCP 的 resolve_names,將 A 資料的名稱與候選名稱一起送出:
{
"queries": [
{ "text": "北車,台北市,台灣" },
{ "text": "台北車站,台北市,台灣" },
{ "text": "松山車站,台北市,台灣" }
]
}
Google Resolution API 支援每批最多 20 筆查詢,結果依輸入索引對應,並提供解析信心程度與失敗資訊。因此,Agent 必須保留「哪筆結果屬於哪個原始名稱」的關係。
假設回傳結果整理後如下:
| 查詢名稱 | 解析結果 |
|---|---|
| 北車,台北市,台灣 | 地點 P |
| 台北車站,台北市,台灣 | 地點 P |
| 松山車站,台北市,台灣 | 地點 Q |
如果「北車」與「台北車站」都解析成地點 P,就可以建立以下對應證據:
A 資料:「北車」 ──────→ Google 地點 P
↑
內部 ID 101:「台北車站」 ────────┘
因此,Agent 可以提出 mapping 建議:
{
"source_id": "A001",
"original_mention": "北車",
"candidate_place_id": 101,
"candidate_name": "台北車站",
"match_basis": "兩個名稱解析為相同 Google Place ID",
"status": "proposed"
}
**真正完成名稱對應的關鍵,是兩個名稱解析到相同外部地點,再把該地點連回候選紀錄的內部 ID。**DBHub 只負責取出資料,Google 提供名稱解析,Agent 則串接與整理兩邊結果。
完成前述 Google Maps MCP 與 DBHub 設定後,可以在對話中輸入:
A 資料:
source_id = A001
description = 集合地點:北車。
city = 台北市
country = 台灣
請提出 A 資料與內部 places 資料表的 mapping 建議:
1. 呼叫 list_places_in_city,取得台北市的候選。
2. 若取得 21 筆,表示候選可能不完整,先停止本例的比對。
3. 保留每筆候選的內部 ID 與正式名稱。
4. 使用 Google resolve_names,解析原始的「北車」及候選名稱。
查詢加上已有的縣市、國家;每批不得超過 20 筆。
不要先把「北車」改寫成「台北車站」再當作驗證。
5. 檢查每筆解析是否成功及其信心程度,再比較 Place ID。
6. 只有一筆候選符合時,提出 mapping 建議並說明依據。
7. 解析失敗、沒有相同 ID、或多筆候選符合時,標示待確認。
不要寫入資料庫,也不要把工具錯誤當成查無對應。
如果 Google 無法解析「北車」,模型仍可以提出「台北車站」作為候選。
此時可以使用已確認的別名紀錄、能支持暱稱的來源,或人工確認。
此外,Google 官方指出,同一地點可能有多個 Place ID,ID 也可能改變。因此,兩邊 ID 不同時,不能直接斷定為不同地點。Place ID 官方說明
確認 mapping 後,可由應用程式保存來源 ID、原始名稱、內部地點 ID 與查核依據;也可以替內部地點保存已核對的 Google Place ID。下一次匯入相同脈絡的資料時,便能優先沿用已確認的關係,減少重複解析。
**可以結合 LLM 與 decision model,而且這比看到「附近」就直接搜尋,更貼近你的需求。**查到的開發者與研究案例也不全使用 Google:有的先用 LLM 擷取空間關係,再接 OpenStreetMap;有的把周邊搜尋與可達範圍整合成工具。
以下可替換原段落,將重點放在「先判斷這句話要做什麼,再選工具」。
「這家餐廳位於北車附近」和「幫我找北車附近的餐廳」,都包含「北車」「附近」「餐廳」,但需要執行的工作不同。
第一句是在描述一個已經提到的對象;第二句才是要求找出新的餐廳。如果只抽取地名,或看到「附近」就呼叫搜尋工具,就可能把資料描述誤當成搜尋指令。
這正好可以接回前面討論的 decision model:先由 LLM 理解任務、對象與位置關係,再根據缺少的資訊決定下一步。
| 輸入與任務脈絡 | 要處理的對象 | 北車的角色 | 下一步 |
|---|---|---|---|
| 匯入資料:「集合地點:北車」 | 集合地點 | 要辨識的地點本身 | 解析北車,對應內部地點紀錄 |
| 匯入餐廳介紹:「這家餐廳位於北車附近」 | 來源資料中的餐廳 | 參照地點 | 解析北車,保留「餐廳在其附近」的描述 |
| 使用者要求:「找北車附近的餐廳」 | 尚未選定的餐廳 | 搜尋中心 | 確認北車,再搜尋周邊餐廳 |
| 使用者詢問:「這家餐廳真的在北車附近嗎?」 | 已知餐廳與北車 | 距離比較的參照點 | 確認兩個地點,查距離或交通時間 |
判斷時也必須提供任務脈絡。同一句「這家餐廳位於北車附近」,放在「請匯入介紹」與「請查核這段介紹」後面,下一步就不同。
因此,LLM 的輸入應包含「目前在做什麼」,不能只丟一個孤立句子。
以匯入餐廳資料為例,交給 LLM 的輸入可以是:
{
"task": "extract_location_relation",
"source_id": "restaurant_A",
"text": "這家餐廳位於北車附近",
"context": {
"city": "台北市",
"country": "台灣"
}
}
下面是建議的解析結果,屬於應用程式自訂格式,不是 Google Maps 的 API Schema:
{
"intent": "record_relation",
"subject": {
"source_id": "restaurant_A",
"mention": "這家餐廳"
},
"relation": "near",
"reference_place": {
"mention": "北車",
"city": "台北市"
},
"distance_constraint": null,
"evidence_text": "這家餐廳位於北車附近",
"verification_status": "source_claim"
}
這個結果保留了三個重要區別:
restaurant_A,不是台北車站。後續即使成功將北車 mapping 到內部地點 ID 101,建立的也應是:
restaurant_A ──來源描述為附近──→ places.id 101
不能把餐廳自己的地點 ID 設成 101,也不能因為北車已經解析成功,就把附近關係標示為已驗證。
如果「這家餐廳」沒有對應的來源紀錄或先前對話,主體就仍未確定。Agent 應保留這個缺口,不能從北車周邊任意挑一家餐廳補上。
可以讓 LLM 先輸出任務類型,再由應用程式檢查必要資訊。以下是流程示意,不是可直接執行的工具程式:
辨識特定地點:
解析原始名稱 → 查內部紀錄 → 提出 mapping
保存來源描述:
解析參照地點 → 保留主體與關係 → 標示尚未驗證
搜尋周邊:
確認參照地點 → 決定搜尋範圍 → 查候選
驗證附近:
確認主體及參照地點 → 查距離/路線 → 套用判斷標準
LLM 適合處理「這句話中的北車扮演什麼角色」「使用者是要找餐廳,還是要辨識既有餐廳」這類語意判斷。
但經緯度、路線時間與實際距離應由工具提供。若要確保流程穩定,應用程式也要檢查:參照地點是否已確認、距離單位是否存在、資料描述是否被誤當成搜尋要求。結構化 JSON 有助於檢查,卻不代表語意一定正確。
在 Google 工具中,也有不同的使用方式:
| 情境 | 適合的工具 |
|---|---|
| 「北車附近適合聚餐的餐廳」這類自然語言探索 | Grounding Lite MCP 的 search_places |
| 已有中心座標,要找指定半徑與類型的地點 | Places API 的 Nearby Search(New) |
| 「走路十五分鐘內」 | 搜尋候選後,再用路線工具檢查步行時間 |
| 只要保存「餐廳位於北車附近」這段介紹 | 不一定需要搜尋周邊;先解析參照地點並保留關係 |
Nearby Search(New)接受中心座標、半徑與地點類型等結構化條件。它是 Places API,並非 Grounding Lite 的另一個同名 MCP 工具;若要讓 Agent 使用,仍需接入可呼叫此 API 的工具。
地點確認之後,才能進一步判斷是否適合行程。
Google Places 的 Search Along Route 可以根據預先計算的路線搜尋候選。搭配 routing summaries,回傳的兩段路線資訊分別代表「出發地到候選地點」及「候選地點到目的地」。
這是 Places API 與 Routes API 的能力。
Google Places API 提供沿途搜尋;若要讓 Agent 使用,可以將 Routes API 與 Places API 的呼叫流程封裝成自訂工具。
假設使用者說:
從 A 開車去 B,途中找一家咖啡廳,最多增加十五分鐘車程。
工具需要完成三件事:取得原路線、搜尋沿途候選,再計算繞經候選所增加的交通時間。使用前,Google Cloud 專案需啟用 Routes API 與 Places API(New),並配置可呼叫兩者的憑證。
先將已確認的出發地與目的地交給 Routes API 的 computeRoutes:
route_request = {
"origin": {"placeId": origin_place_id},
"destination": {"placeId": destination_place_id},
"travelMode": "DRIVE",
"routingPreference": "TRAFFIC_UNAWARE",
}
透過回應欄位遮罩要求 routes.duration 與 routes.polyline.encodedPolyline,取得兩項資料:
接著呼叫 Places API 的 Text Search,將原路線放進 searchAlongRouteParameters:
search_request = {
"textQuery": "咖啡廳",
"searchAlongRouteParameters": {
"polyline": {
"encodedPolyline": route_polyline
}
},
"routingParameters": {
"travelMode": "DRIVE",
"routingPreference": "TRAFFIC_UNAWARE",
},
}
回應欄位需包含地點 ID、名称、地址與 routingSummaries.legs.duration。沿途搜尋的路線摘要會提供「出發地 → 候選地點」及「候選地點 → 目的地」兩段交通時間。Google 沿途搜尋說明
搜尋结果偏向路線附近的地點,仍需由程式檢查是否符合使用者的時間限制。核心判斷如下,變數皆為已解析的秒數:
extra_seconds = (
origin_to_candidate_seconds
+ candidate_to_destination_seconds
- direct_route_seconds
)
within_limit = extra_seconds <= max_extra_minutes * 60
例如,直接抵達需要 60 分鐘,繞經咖啡廳後需要 70 分鐘,增加的交通時間就是 10 分鐘,符合十五分鐘門檻。這些數字僅用於說明計算方式。
處理回應時,應以相同索引對應地點與路線摘要;若缺少任一段時間,就標示為「尚未確認」,不能當成零分鐘或直接判定符合。
本例兩次請求都使用開車與 TRAFFIC_UNAWARE,比較的是未考慮即時交通的估計時間,也不包含喝咖啡的停留時間。若差值為負,應檢查路線差異,不能直接宣稱繞經該地點一定更快。
這個工具回傳候選地點、實際地址、額外交通時間及是否通過門檻,讓 Agent 根據結果整理建議。景點即使位於另一個縣市,也可以通過交通條件;是否安排進行程,再結合營業時間、停留時間與使用者偏好決定。
更新檔案並重新啟動本機 MCP server 後,可以在 Claude Code 輸入:
請根據對話中已確認的實際出發地與目的地,
搜尋開車途中可安排的景點,最多增加 15 分鐘交通時間。
使用 search_along_drive。
跨縣市可以,但請保留工具回傳的實際地址。
只將 within_limit 列為符合交通門檻;
unverified 需要補查,不能直接當作符合。
若出發地或目的地仍只有縣市名稱,請先釐清實際地點。
這次先評估交通條件,不代表營業時間與停留時間也已符合。
這裡的 search_along_drive 是本文自訂工具。它把底層 API 串接與數值判斷包起來,讓 Agent 不必自行拼接路線資料或計算時間差。
縣市歸屬與行程適合度也因此分開:Google 回傳地址,程式檢查交通門檻,Agent 再說明候選為何適合。
此外,Places 沿途搜尋不支援公共運輸路線;這個工具只適用於本文設定的開車情境。
現在,Claude Code 可以使用兩組工具:
| 來源 | 本文使用的能力 |
|---|---|
| Google 官方 MCP | 將名稱或地圖連結解析成 Place ID |
自訂 geo-tools MCP |
用 Place ID 查自有資料庫、搜尋並檢查開車沿途候選 |
地點解析結果回到 Agent 後,Agent 將識別碼交給資料庫工具;資料庫工具再回傳內部 ID。沿途需求則交給封裝好的路線工具,由程式完成 API 串接與數值門檻判斷。
這種設計讓 decision model 可以根據明確狀態決定下一步:
matched:取得唯一的已登錄地點,可以繼續處理 mapping。not_found 或 ambiguous:需要補查,不能直接建立對應。within_limit:通過本文定義的交通門檻。over_limit 或 unverified:排除,或取得更多資料後再評估。提示詞或 skill 可以要求 Agent 遵循處理順序,但真正需要穩定執行的規則,應由程式檢查。例如,本例的資料庫工具不提供寫入功能,沿途工具則直接計算是否超過門檻。
從 Agent 的決策走到工具,重要的就是這段資料流:外部名稱先取得地點身分,地點身分再對應內部資料,路線條件則交給可檢查的計算。
如此,Agent 回答時,背後才有具體的查詢結果與判斷依據。