大家好,鐵人賽第十五天,我們的 Python 實戰正式進入進階領域!
在昨天,我們成功地搭建了專案框架,並讓一個只認識「紐約」的 AI Agent 動了起來。今天,我們要對它進行一次「超進化」,將昨天的「玩具」徹底改造成真正能解決問題的「專業工具」。
我們今天的核心任務是:
get_weather
工具: 讓它去串接中央氣象署的真實 API,獲取即時天氣資訊。calculate
工具: 打造一個帶有安全性檢查的計算機,賦予 Agent 精確的運算能力。get_weather
工具 - 連接真實世界我們的 Agent 不能永遠只活在紐約(假資料)。現在,讓它學會查詢台灣的天氣。
requests
函式庫要從 Python 呼叫外部 API,requests
是最廣泛、最好用的函式庫。在你的虛擬環境中安裝它:
pip install requests
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
列表,並對使用者輸入的 "台" 做正規化處理,確保只處理我們能處理的請求。requests.get()
來呼叫真實的氣象署 API。try...except
區塊,分別捕捉了「網路請求失敗」和「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
的初始化設定,告訴它現在擁有了哪些新工具,以及該如何更好地使用它們。
請用以下程式碼,替換掉你 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
已經全面升級!
Ctrl + C
停止它,然後重新執行 adk run multi_tool_agent
來載入我們最新的程式碼。臺中市今天天氣如何?
(15 + 85) * 3 - 50 等於多少?
日本東京天氣怎麼樣?
(測試天氣工具的錯誤處理)10 / 0
(測試計算機工具的錯誤處理)如下圖,[user] 是我的提問,下方是 agent 的回覆,你會發現,我們的 Agent 不僅擁有了真實的能力,還能優雅地處理錯誤情況!
今天,我們的 AI Agent 真正地「長大」了。我們完成了從入門到進階的關鍵一步,學會了:
requests
函式庫串接真實世界的 API。
eval
函式來打造計算工具。
我們現在擁有一個功能強大、程式碼清晰、且具備基本錯誤處理能力的 Python Agent。