iT邦幫忙

2024 iThome 鐵人賽

DAY 16
1
生成式 AI

Python 新手的 AI 之旅:從零開始打造屬於你的 AI / LLM 應用系列 第 16

【Day16】Dify 入門 (2):讓 Agent 使用自定義工具吧!(內含一咪咪 FastAPI)

  • 分享至 

  • xImage
  •  

前言

我們昨天有使用 Dify 幫你設定好的外部工具,像是 duckduckgo 的服務。

到目前為止我們已經很熟悉 API 是怎麼運作的,不過幾乎都是去呼叫別人寫好的 API,如果我們要串接自己寫的 API 那該怎麼做呢?接下來我會用 FastAPI 這個超級方便的 Python Web 框架,來寫一個水果的 API,然後透過 Dify 去串接這個水果的 API。

不過在實際撰寫之前,我們要先知道 OpenAPI 這個東東 (注意:不是 OpenAI,是 OpenAPI)

OpenAPI 與 Swagger

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

FastAPI 是一個現代化的 Python Web 框架,專為構建高效能的 API 而設計。它基於 Starlette 和 Pydantic,提供簡潔的語法和自動數據驗證,使開發者能夠快速創建 RESTful API。使用 FastAPI,你能夠輕鬆定義數據模型、管理路由,並自動生成清晰的 OpenAPI 文檔和 Swagger UI。

FastAPI 除了語法簡單、資料型別驗證、支援非同步操作,更屌的是,你寫好的 API 可以直接透過 /docs 這個路由來測試,用 /openapi.json 來產生標準格式檔案,既強大又容易學習。

開始實作

用 FastAPI 來查詢水果價格

首先要安裝套件

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,一個是打招呼的 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 就只能由英文、數字、底線組成,不能有空白喔 (如同一般的變數命名)

完整程式碼

用 Swagger UI 測試寫好的 API

用 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來排版呦,強烈建議要排版一下

新增 Dify 自定義工具

到工具 -> 建立自定義工具工具

把剛剛的 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 提到的向量資料庫,我們已經距離一個完整的應用不遠了~~期待一下吧 🫶


上一篇
【Day15】Dify 入門 (1):讓 Agent 幫報新聞、查資料、唬爛...還有好多好多
下一篇
【Day17】Dify 入門 (3):使用知識庫來做 RAG 並結合 Notion database
系列文
Python 新手的 AI 之旅:從零開始打造屬於你的 AI / LLM 應用30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言