iT邦幫忙

2025 iThome 鐵人賽

DAY 17
0
生成式 AI

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

Day 17 - 部署自己的工具到雲端並在OpenWebUI上使用

  • 分享至 

  • xImage
  •  

大家好,鐵人賽堂堂邁入第十七天!

在過去幾天,我們學會了如何用 adk-mcp本地端建立和測試 MCP 伺服器與工具。但一個真正的 AI 工具,不能只活在開發者的電腦裡,它需要被部署到雲端,成為一個穩定、公開、可供全球任何 AI Host 呼叫的強大服務。

今天,我們將走完這「最後一哩路」。我們將以 Day 15 的天氣工具為基礎,使用 FastAPI 將其封裝成一個專業的 Web API,用 Docker 將其容器化,並將其免費部署到 Render.com 雲端平台,最終在我們熟悉的 OpenWebUI 中,成功呼叫這個部署在雲端的自訂工具!

這將是一段充滿成就感的旅程,讓我們開始吧!


階段一:從函式到服務 - API 開發

目標:將 Python 天氣查詢功能,從一個單純的函式,封裝成一個可透過網路呼叫的專業 API 服務。

1. 建立專案結構

一個結構清晰的專案是成功的開始。我們建立如下的目錄結構:

weather_tool/
├── app/
│   ├── .env              # 存放機敏資訊,如 API 金鑰
│   ├── main.py           # FastAPI 伺服器核心程式碼
│   └── requirements.txt  # 專案依賴的 Python 套件
├── .dockerignore         # Docker 建置時忽略的檔案
├── .gitignore            # Git 版本控制忽略的檔案
└── Dockerfile            # 容器化設定檔

截圖 2025-09-17 下午2.50.41

2. 定義依賴項 (app/requirements.txt)

我們需要以下幾個核心套件:

  • fastapi: 我們的主角,高效能的 Web 框架。
  • uvicorn: ASGI 伺服器,負責運行 FastAPI 應用。
  • requests: 用於呼叫外部 API (中央氣象署)。
  • python-dotenv: 方便在本地開發時讀取 .env 檔案。
  • pydantic: FastAPI 底層使用的資料驗證利器。

3. 撰寫 API 伺服器 (app/main.py)

這是我們專案的心臟。我們使用 FastAPI 框架來建立一個 Web 應用。
(您的 main.py 程式碼寫得非常棒,結構清晰、錯誤處理也很完善,我會直接沿用並在文章中強調其優點)

import os
import urllib.parse
import requests
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from dotenv import load_dotenv

# 載入 .env 檔案 (主要用於本地測試,部署時會直接用服務的環境變數)
load_dotenv()

app = FastAPI(
    title="台灣天氣預報 API (Taiwan Weather Forecast API)",
    description="一個簡單的 API,用於查詢台灣各縣市的即時天氣預報。",
    version="1.0.0",
)

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

def get_weather(city: str) -> dict:
    """根據城市名稱查詢天氣,並以結構化字典格式回傳。"""
    normalized_city = city.replace("台", "臺")
    if normalized_city not in TAIWAN_CITIES:
        raise HTTPException(status_code=404, detail=f"找不到城市 '{city}' 的天氣資訊。")

    auth_token = os.getenv("CWA_AUTH_TOKEN")
    if not auth_token:
        raise HTTPException(status_code=500, detail="伺服器未設定 CWA_AUTH_TOKEN。")
    
    base_url = "https://opendata.cwa.gov.tw/api/v1/rest/datastore/F-C0032-001"
    encoded_location_name = urllib.parse.quote(normalized_city)
    full_api_url = f"{base_url}?Authorization={auth_token}&locationName={encoded_location_name}"
    
    try:
        response = requests.get(full_api_url, timeout=10)
        response.raise_for_status()
        data = response.json()

        location_data = data['records']['location'][0]
        weather_elements = location_data['weatherElement']
        time_info = weather_elements[0]['time'][0]
        
        # 提取所有天氣元素
        weather_condition = next(p['parameter']['parameterName'] for p in weather_elements[0]['time'] if p['startTime'] == time_info['startTime'])
        rain_prob = next(p['parameter']['parameterName'] for p in weather_elements[1]['time'] if p['startTime'] == time_info['startTime'])
        min_temp = next(p['parameter']['parameterName'] for p in weather_elements[2]['time'] if p['startTime'] == time_info['startTime'])
        max_temp = next(p['parameter']['parameterName'] for p in weather_elements[4]['time'] if p['startTime'] == time_info['startTime'])

        return {
            "city": city,
            "start_time": time_info['startTime'],
            "end_time": time_info['endTime'],
            "weather_condition": weather_condition,
            "rain_probability_percent": int(rain_prob),
            "min_temp_celsius": int(min_temp),
            "max_temp_celsius": int(max_temp),
        }
    except (requests.exceptions.RequestException, KeyError, IndexError, StopIteration) as e:
        raise HTTPException(status_code=503, detail=f"查詢天氣資料失敗:{e}")

# --- API 端點定義 ---
class WeatherRequest(BaseModel):
    city: str

@app.post("/get_weather_forecast", summary="取得天氣預報")
def api_get_weather(request: WeatherRequest):
    """
    接收一個城市名稱,回傳該城市的天氣預報。
    這是給 Claude 使用的主要工具函式。
    """
    return get_weather(request.city)

@app.get("/")
def read_root():
    return {"status": "ok", "message": "天氣預報 API 正在運行"}

程式碼亮點解析:

  • 關注點分離: 核心查詢邏輯 (get_weather) 與 API 端點定義 (@app.post) 分開,程式碼更易於維護和測試。
  • 環境變數: CWA 的 auth_token 從環境變數讀取,避免了將機敏資訊寫死在程式碼中的安全風險。
  • 自動化文件: FastAPI 最強大的功能之一!只要我們的程式碼寫好,它就會自動在 /docs/openapi.json 路徑,生成符合 OpenAPI 標準的互動式 API 說明書。這正是接下來 OpenWebUI 能「讀懂」我們工具的關鍵!

4. 設定版本控制忽略項 (.gitignore)

建立 .gitignore 檔案,確保 app/.envvenv/__pycache__/ 等檔案不會被上傳到 GitHub,保障我們的 API 金鑰安全。

# Git and Docker files
.git
.gitignore
Dockerfile
.dockerignore

# Python (與 .gitignore 重疊)
__pycache__/
*.pyc
venv/
.venv/

# Secrets (與 .gitignore 重疊)
app/.env

階段二:標準化交付 - 使用 Docker 容器化

目標:將我們的 FastAPI 應用程式及其所有複雜的依賴項,打包成一個標準化、輕量且可移植的 Docker 映像檔。

1. 撰寫 Dockerfile

Dockerfile 就像一張「安裝說明書」,告訴 Docker 如何一步步建置我們的應用程式環境。
(您的 Dockerfile 寫得很專業,我會直接沿用並說明)

# 使用輕量的 Python 基礎映像檔
FROM python:3.11-slim

# 設定工作目錄
WORKDIR /code

# 複製 Python 應用程式碼與依賴項文件
COPY ./app/requirements.txt /code/requirements.txt
COPY ./app /code/app

# 安裝依賴項
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

# 開放 80 port (Render 預設會連接到容器的 80 port)
EXPOSE 80

# 容器啟動時執行的命令
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]

2. 建立 Docker 映像檔

在專案根目錄下,執行 docker build -t my-weather-tool . 指令。Docker 就會按照說明書,為我們打造一個包含所有程式碼和依賴項的應用程式。

階段三:走向世界 - 雲端部署

目標:將 Docker 映像檔部署到公開的雲端平台 (Render.com),讓它獲得一個全球皆可訪問的穩定網址。

1. 推送專案至 GitHub

將整個 weather_tool 專案(除了被 .gitignore 忽略的檔案)推送到您個人的 GitHub 倉庫。

2. 在 Render.com 上部署

Render 是一個對開發者非常友善的雲端平台,尤其對 Docker 專案的支援極佳。

  1. 註冊並登入 Render,選擇從 GitHub 倉庫建立一個新的 "Web Service"
  2. 授權 Render 存取你的 GitHub,並選擇 weather_tool 倉庫。
  3. Render 會自動偵測到 Dockerfile 並選擇以 Docker 方式部署。
  4. 在服務設定中,選擇 Free (免費) 方案。
  5. Environment Variables (環境變數) 的進階設定中,手動新增一筆 KeyCWA_AUTH_TOKEN 的變數,並將您有效的 API 金鑰作為 Value 填入。這是確保雲端伺服器能成功呼叫 CWA API 的關鍵步驟。
  6. 啟動建立服務,稍待幾分鐘,Render 就會自動完成從建置到部署的所有工作。

3. 取得公開 API 位址

部署成功後,從 Render 儀表板複製您的服務公開網址,例如 https://my-weather-tool.onrender.com

階段四:終極整合 - 在 OpenWebUI 中呼叫雲端工具

萬事俱備,只欠東風!現在,我們要讓本地的 OpenWebUI,去呼叫我們部署在雲端的個人專屬天氣工具。

1. 在 OpenWebUI 中註冊工具

  1. 進入 OpenWebUI 的 Settings -> Tools
  2. 點擊 Add a tool,並填寫您部署在 Render 上的服務位址:
    • API URL: https://my-weather-tool.onrender.com/openapi.json
    • Authentication: 保持 None
  3. 儲存設定。OpenWebUI 會自動去讀取 /openapi.json,並理解我們 API 的所有能力。

截圖 2025-09-17 下午3.05.21

2. 在聊天中啟用並執行工具

  1. 回到主聊天介面,開始一個新的對話
  2. 點擊模型選單旁的工具圖示 🛠️。
  3. 在彈出的選單中,勾選我們剛剛新增的天氣 API 工具。

截圖 2025-09-17 下午2.58.57

現在,你可以正式對你的 AI 下達指令了!

幫我查一下臺中的天氣怎麼樣?

AI 模型會接收到你的問題,發現它需要天氣資訊,然後跨越網際網路,去呼叫我們部署在 Render 雲端上的 FastAPI 服務,獲取即時、準確的天氣資料,最終生成完美的回答!

截圖 2025-09-17 下午2.57.08


五、今日總結

今天,我們完成了一項令人振奮的、端到端的全流程實戰。我們從一個本地的 Python 函式開始,最終將它變成了一個全球可用的雲端 AI 工具。

我們掌握了:

  • 使用 FastAPI 開發專業、自帶文件的 API。
  • 利用 Docker 將應用程式容器化,實現標準化交付。
  • 在 Render.com 上實現免費的持續部署。
  • 將自訂的 OpenAPI 工具無縫整合進 OpenWebUI。

這不僅僅是 MCP 的一次實踐,更是現代 AI 應用開發 (LLM Ops) 的一個縮影。你現在所擁有的,不僅是一個聊天機器人,而是一個具備了雲端擴充能力的、真正屬於你自己的 AI 平台。


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

尚未有邦友留言

立即登入留言