我們昨天有使用 Dify 幫你設定好的外部工具,像是 duckduckgo 的服務。
到目前為止我們已經很熟悉 API 是怎麼運作的,不過幾乎都是去呼叫別人寫好的 API,如果我們要串接自己寫的 API 那該怎麼做呢?接下來我會用 FastAPI 這個超級方便的 Python Web 框架,來寫一個水果的 API,然後透過 Dify 去串接這個水果的 API。
不過在實際撰寫之前,我們要先知道 OpenAPI 這個東東 (注意:不是 OpenAI,是 OpenAPI)
OpenAPI 是一種用於描述 RESTful API 的規範。它允許開發者使用標準格式 (YAML 或 JSON) 清晰地定義 API 的結構、端點、請求和回傳格式等。有了 OpenAPI 統一 API 的描述,可以讓不同的開發者、工具和平台能夠更容易地理解和使用 API。
有關 READful API 的詳細介紹,可以參考這篇文章
以下是 OpenAPI Yaml 格式的範例,可以發現它很清楚的定義了 server, api 名稱, 資料格式類型等等
openapi: 3.0.0
info:
title: Sample API
description: A sample API for demonstration purposes
version: 1.0.0
servers:
- url: http://localhost:8000
paths:
/items:
get:
summary: Get a list of items
responses:
'200':
description: A list of items
content:
application/json:
schema:
type: array
items:
type: object
properties:
id:
type: integer
name:
type: string
那什麼是 Swagger 呢?是一個開源的 API 描述規範及相關工具套件,主要用來設計、建構、記錄和使用 RESTful API。它最初由 Wordnik 開發,現在已經變成 OpenAPI Specification (OAS) 的一部分了。
我們今天會用到 Swagger UI 這個 Swagger 的組件,可以直接在網頁上測試寫好的 API
FastAPI 是一個現代化的 Python Web 框架,專為構建高效能的 API 而設計。它基於 Starlette 和 Pydantic,提供簡潔的語法和自動數據驗證,使開發者能夠快速創建 RESTful API。使用 FastAPI,你能夠輕鬆定義數據模型、管理路由,並自動生成清晰的 OpenAPI 文檔和 Swagger UI。
FastAPI 除了語法簡單、資料型別驗證、支援非同步操作,更屌的是,你寫好的 API 可以直接透過 /docs
這個路由來測試,用 /openapi.json
來產生標準格式檔案,既強大又容易學習。
首先要安裝套件
pip install fastapi uvicorn
在程式碼中引入我們會用到的套件、常數
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
MAX_ITEMS = 10
DEFAULT_SKIP = 0
定義水果 model,這邊的 model 並不是機器學習的那個模型,而是指「資料庫結構」。我們會寫一個水果 model,裡頭有價格和名稱兩個屬性,然後繼承 BaseModel
類別。
# 定義水果模型
class Fruit(BaseModel):
name: str
price: float
用一個 list 當成模擬的資料庫
# 模擬資料庫
FRUIT_DATABASE: list[Fruit] = [
Fruit(name="蘋果", price=10.0),
Fruit(name="香蕉", price=5.0),
Fruit(name="橘子", price=8.0),
Fruit(name="芭樂", price=7.0),
Fruit(name="西瓜", price=15.0),
Fruit(name="梨子", price=6.0),
Fruit(name="櫻桃", price=20.0),
Fruit(name="葡萄", price=12.0),
Fruit(name="橙子", price=9.0),
Fruit(name="柚子", price=11.0),
]
再來就是設定這個 App 的資訊,好讓 FastAPI 可以幫我們撰寫規格,這邊比較重要的是 description 和 servers,有了 description 模型會更清楚這個 API 的功用;server 則是讓 Dify 知道要使用什麼 url
app = FastAPI(
title="水果 API",
description="一個用來回傳水果資料的 API",
version="1.0.0",
servers=[
{"url": "http://localhost:8000", "description": "本地伺服器"},
]
)
這邊我們寫三個 API,一個是打招呼的 API,等一下在 Dify 中方便測試,第二個是根據 ID,也就是水果在 List 中的順序,去取得水果的資訊;最後一個是取得特定範圍的水果資訊。
@app.get("/", operation_id="get_welcome_message")
async def get_welcome_message() -> dict[str, str]:
"""
回傳歡迎訊息。
Returns:
Dict[str, str]: 包含歡迎訊息的字典。
"""
return {"message": "歡迎使用水果 API!"}
@app.get("/fruits/{fruit_id}", operation_id="get_fruit_by_id")
async def get_fruit_by_id(fruit_id: int) -> Fruit:
"""
根據 ID 取得特定水果的資訊。
Args:
fruit_id (int): 水果的 ID。
Returns:
Fruit: 對應 ID 的水果資訊。
Raises:
HTTPException: 如果找不到對應 ID 的水果,則拋出 404 錯誤。
"""
try:
return FRUIT_DATABASE[fruit_id]
except IndexError:
raise HTTPException(status_code=404, detail="找不到指定 ID 的水果")
@app.get("/fruits/", operation_id="get_fruits")
async def get_fruits(skip: int = DEFAULT_SKIP, limit: int = MAX_ITEMS) -> list[Fruit]:
"""
取得水果列表
Args:
skip (int, optional): 要跳過的水果數量。默認為 0。
limit (int, optional): 要返回的最大水果數量。默認為 10。
Returns:
List[Fruit]: 符合條件的水果列表。
"""
return FRUIT_DATABASE[skip : skip + limit]
if __name__ == "__main__":
app.run()
稍微解釋一下這坨扣在做啥,以 get_fruit_by_id
為例,我們用了 python 的裝飾器 @app.get("/fruits/{fruit_id}", operation_id="get_fruit_by_id")
指的是使用 http GET 方法,路由為 /fruits/{fruit_id}
;這個 {fruit_id}
是我們可以去改的地方;最後 operation_id
則是定義這個操作在 OpenAPI 中要叫什麼名字。最後,這個 API 回傳的資料類型是 Fruit
,也就是我們前面定義好的 model
注意:如果要在 Dify 中使用 function calling api 的 LLM,那 operation_id
就只能由英文、數字、底線組成,不能有空白喔 (如同一般的變數命名)
用 uvicorn 來啟動服務
--reload
: 每次修改檔案存檔都會自動重新啟動服務,而不用中斷服務--host
: host url,設定成 0.0.0.0 則可以讓外網連進來--port
: 就是 port 看你喜歡設多少uvicorn main:app --reload --host=0.0.0.0 --port=8000
打開瀏覽器,輸入 http://localhost:8000/docs
我們可以來測試 API,舉例來說,我要測試 get fruit by id 我就把它點開 -> Try it out -> 填寫 id -> Execute
在瀏覽器中輸入 http://localhost:8000/openapi.json
取得 OpenAPI JSON Schema,看到這坨東西就對了,等等會用到它,你會發現剛剛寫得不論是 description, api 的註解還是資料格式都寫好在這個 JSON 了。如果你的 json 他沒有幫你排版可以用 JSON formatter來排版呦,強烈建議要排版一下
到工具 -> 建立自定義工具工具
把剛剛的 JSON 資料貼上來,你應該會看到「可用工具」那邊多了一些東西,就是我們剛剛在程式碼中定義的 API route
重要:如果你是自己 build Dify 那就沒關係,但如果你是使用 docker 來 build Dify,那要把 Schema 中的 server url 從 http://localhost:8000
改成 http://host.docker.internal:8000
,這樣才能訪問容器外的 API 喔!
改好之後可以來測試一下,用 get_welcome_message
進行測試,如果有回傳歡迎訊息表示有接通,已經快要成功了~
最後就是把工具引入,像昨天一樣
測試一下吧!這邊我用的是 gpt-4o-mini
,跟他打招呼
問全部的水果
針對特定水果詢問
今天的自建 API 的目的是讓你知道,除了使用別人寫好的 API 你也可以自己寫一個客製化的,我其實還有很多沒有講到,有機會一定要好好學一下 FastAPI。現在的模型越來越「聰明」,即使不是用 Prompt generator 產生的 API 描述,模型也大概能猜到這個 API 是做什麼的、什麼時候要呼叫,這也意味著你可以讓模型做任何事,只怕你沒有想像力而已。
明天來仔細介紹知識庫一番,也就是 Day8 提到的向量資料庫,我們已經距離一個完整的應用不遠了~~期待一下吧 🫶