iT邦幫忙

2025 iThome 鐵人賽

DAY 15
0
生成式 AI

三十天解鎖上下文超能力:MCP 實戰系列 第 15

Day 15 - adk mcp tools:撰寫天氣狀況查詢tools和計算機tools

  • 分享至 

  • xImage
  •  

大家好,鐵人賽第十五天,我們的 Python 實戰正式進入進階領域!

在昨天,我們成功地搭建了專案框架,並讓一個只認識「紐約」的 AI Agent 動了起來。今天,我們要對它進行一次「超進化」,將昨天的「玩具」徹底改造成真正能解決問題的「專業工具」。

我們今天的核心任務是:

  1. 升級 get_weather 工具: 讓它去串接中央氣象署的真實 API,獲取即時天氣資訊。
  2. 新增 calculate 工具: 打造一個帶有安全性檢查的計算機,賦予 Agent 精確的運算能力。
  3. 優化 Agent 指令: 學習如何用更精準的 Prompt,引導 AI Agent 更可靠地使用工具。

一、升級 get_weather 工具 - 連接真實世界

我們的 Agent 不能永遠只活在紐約(假資料)。現在,讓它學會查詢台灣的天氣。

1. 安裝新夥伴:requests 函式庫

要從 Python 呼叫外部 API,requests 是最廣泛、最好用的函式庫。在你的虛擬環境中安裝它:

pip install requests

2. 編寫新版 get_weather 函式

請用以下程式碼,替換掉你 agent.py 中舊的 get_weather 函式:

import urllib.parse
import requests

TAIWAN_CITIES = [
    "臺北市", "新北市", "桃園市", "臺中市", "臺南市", "高雄市",
    "基隆市", "新竹市", "嘉義市", "新竹縣", "苗栗縣", "彰化縣",
    "南投縣", "雲林縣", "嘉義縣", "屏東縣", "宜蘭縣", "花蓮縣",
    "臺東縣", "澎湖縣", "金門縣", "連江縣"
]

def get_weather(city: str) -> dict:
    """
    Retrieves the 36-hour weather forecast for a specified city in Taiwan.
    """
    normalized_city = city.replace("台", "臺")
    if normalized_city in TAIWAN_CITIES:
        print(f"工具 [get_weather] 偵測到台灣城市:{normalized_city},正在呼叫 CWA API...")
        base_url = "https://opendata.cwa.gov.tw/api/v1/rest/datastore/F-C0032-001"
        auth_token = "CWA-A4AA97C5-E3C1-4BEB-B730-688876F81863"
        encoded_location_name = urllib.parse.quote(normalized_city)
        full_api_url = f"{base_url}?Authorization={auth_token}&locationName={encoded_location_name}"
        
        try:
            # 發送 GET 請求,並設定超時
            response = requests.get(full_api_url, timeout=10, verify=False)
            response.raise_for_status() # 如果狀態碼不是 2xx,則拋出異常
            data = response.json()

            # 解析複雜的 JSON 回應
            location_data = data['records']['location'][0]
            weather_elements = location_data['weatherElement']
            element_dict = {elem['elementName']: elem['time'][0]['parameter']['parameterName'] for elem in weather_elements}
            time_info = weather_elements[0]['time'][0]

            report = (
                f"為您查詢到 '{city}' 在 {time_info['startTime']} 到 {time_info['endTime']} 的天氣預報:\n"
                f"- 天氣狀況:{element_dict.get('Wx', 'N/A')}\n"
                f"- 降雨機率:{element_dict.get('PoP', 'N/A')}%\n"
                f"- 溫度:攝氏 {element_dict.get('MinT', 'N/A')} 至 {element_dict.get('MaxT', 'N/A')} 度"
            )
            return {"status": "success", "report": report}

        except requests.exceptions.RequestException as e:
            return {"status": "error", "error_message": f"API 請求失敗: {e}"}
        except (KeyError, IndexError) as e:
            return {"status": "error", "error_message": f"解析 API 資料時出錯: {e}"}
    else:
        return {"status": "error", "error_message": f"很抱歉,目前只提供台灣各縣市的天氣查詢。"}

程式碼解析:

  • 城市驗證: 我們建立了一個 TAIWAN_CITIES 列表,並對使用者輸入的 "台" 做正規化處理,確保只處理我們能處理的請求。
  • API 呼叫: 使用 requests.get() 來呼叫真實的氣象署 API。
  • 錯誤處理: 我們用 try...except 區塊,分別捕捉了「網路請求失敗」和「JSON 解析失敗」兩種最常見的錯誤,讓我們的工具更加強健。
  • JSON 解析: 這是實戰中最有價值的部分!我們深入一個複雜的、巢狀的 JSON 物件,從中提取出我們需要的幾個關鍵天氣參數。

二、新增 calculate 工具 - 安全地執行運算

大型語言模型 (LLM) 雖然強大,但在精確的數學計算上偶爾還是會出錯。提供一個計算機工具,能確保 Agent 在面對數字問題時的絕對準確性。

請將 agent.py 中舊的 get_current_time 函式,替換為以下新的 calculate 函式:

import re

def calculate(expression: str) -> dict:
    """
    Calculates the result of a mathematical expression.
    """
    print(f"工具 [calculate] 被呼叫,運算式:'{expression}'")
    
    # --- 安全性檢查 ---
    # 只允許數字、運算符號、括號、小數點和空格,防止程式碼注入
    allowed_pattern = re.compile(r"[^0-9\+\-\*\/\(\)\.\s]")
    if allowed_pattern.search(expression) or "**" in expression or "//" in expression:
        return {
            "status": "error",
            "error_message": "表達式包含不支援的字元或運算子。"
        }

    try:
        # 在確認安全後,才使用 eval 進行計算
        result = eval(expression)
        return {"status": "success", "result": str(result)}
    except Exception as e:
        return {"status": "error", "error_message": f"計算時發生錯誤: {e}"}

程式碼解析 - eval() 的危與機:

  • 危險: eval() 函式會執行傳入的任何 Python 程式碼,如果使用者輸入惡意程式碼(例如 os.system('rm -rf /')),將會造成災難。
  • 我們的解法: 在執行 eval() 之前,我們先用正規表達式 re 做了一層嚴格的「過濾」,只允許最基本的數學字元通過。這就像是在 eval() 周圍建立了一道防火牆,大幅提升了工具的安全性。

三、升級 Agent - 更精準的指令 (Prompt Engineering)

最後,我們需要更新 Agent 的初始化設定,告訴它現在擁有了哪些新工具,以及該如何更好地使用它們。

請用以下程式碼,替換掉你 agent.py 中舊的 root_agent 初始化區塊:

root_agent = Agent(
    name="weather_calculator_agent",
    model="gemini-1.5-flash", 
    description="一個可以回答台灣天氣和執行數學計算的代理。",
    instruction=(
        "你是一位天氣查詢與數學計算的專家。"
        "你的唯一工作就是使用被提供的工具來回答使用者的問題。"
        "對於任何與天氣、預報、氣溫相關的問題,你「必須」使用 'get_weather' 工具。"
        "對於任何數學運算問題,你「必須」使用 'calculate' 工具。"
        "「絕對不要」根據你自己的知識庫來回答,即使你認為你知道答案。"
        "如果工具回傳錯誤,請將工具的錯誤訊息如實地、友善地轉告給使用者。"
        "最終回覆必須使用繁體中文。"
    ),
    tools=[get_weather, calculate],
)

指令解析:

  • tools=[get_weather, calculate]: 我們更新了工具列表。
  • instruction: 這裡我們給予了 LLM 非常明確且強制的指令。使用「必須」、「絕對不要」等關鍵詞,是在強迫 Agent 優先使用工具,而不是依賴它自身的知識庫去「幻覺」或「編造」答案。這對於建立可靠的 Agent 至關重要。

四、整合與測試

現在,你的 agent.py 已經全面升級!

  1. 回到你的伺服器終端機,如果它還在運行,請按 Ctrl + C 停止它,然後重新執行 adk run multi_tool_agent 來載入我們最新的程式碼。
  2. 直接在終端機輸入下方指令
    • 臺中市今天天氣如何?
    • (15 + 85) * 3 - 50 等於多少?
    • 日本東京天氣怎麼樣? (測試天氣工具的錯誤處理)
    • 10 / 0 (測試計算機工具的錯誤處理)

如下圖,[user] 是我的提問,下方是 agent 的回覆,你會發現,我們的 Agent 不僅擁有了真實的能力,還能優雅地處理錯誤情況!

截圖 2025-09-08 下午3.30.32

五、今日總結

今天,我們的 AI Agent 真正地「長大」了。我們完成了從入門到進階的關鍵一步,學會了:

  • 使用 requests 函式庫串接真實世界的 API。
  • 解析複雜的巢狀 JSON 資料。
  • 如何安全地使用 eval 函式來打造計算工具。
  • 透過精準的系統指令 (Prompt Engineering) 來引導 Agent 的行為。

我們現在擁有一個功能強大、程式碼清晰、且具備基本錯誤處理能力的 Python Agent。


上一篇
Day 14 - adk mcp 入門:安裝與發送第一個請求
下一篇
Day 16 - adk-mcp 呼叫公開的 MCP 伺服器
系列文
三十天解鎖上下文超能力:MCP 實戰16
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言