Difyの全アプリのDSLファイルを一括でダウンロードする
概要
この記事では、Difyの内部API(console-api)を利用して、ダッシュボード内にある全アプリのDSLファイルを一括でダウンロードする方法を紹介します。
ユースケース
- Difyのバックアップのため全アプリのDSLを定期的に取得したい
- Difyの別環境でもDSLファイルを同期したい
対象者
- Difyを管理しているエンジニアや管理者
- Pythonを実行できる環境を持っている方(PCやサーバ)
通常のDSLファイルのダウンロード
通常アプリのDSLファイルをダウンロードするとき、画像の通り一つ一つ操作が必要です。
例えば、アプリのバックアップを定期的に取りたい場合、これだと大変です
Difyの内部API
Difyはユーザ向けに用意されているAPI(ワークフローやチャットフローを実行するためのエンドポイント)、いわゆるAPP APIとは別に、Difyの内部処理を担うエンドポイント、いわゆるCONSOLE APIがあります。
今回は、CONSOLE APIを利用して全アプリのDSLファイルを一括でエクスポートする方法を紹介します。
リポジトリ
GitHubのリポジトリは以下の通りです。
早く利用したい人は、セットアップを行い使用ください。一括でDSLファイルをダウンロードできるはずです。
前提条件
このスクリプトの実行には、以下の環境が必要です。
- Python 3.13以上
- Poetry(パッケージ管理ツール)
- Dify v.1.0.0(動作確認)
インストール方法
- リポジトリをクローン
$ git clone https://github.com/sattosan/dify-apps-dsl-exporter.git
$ cd dify-apps-dsl-exporter
- Poetryを使って依存関係をインストール
$ poetry install
-
.env
ファイルを作成して必要な環境変数を設定
$ cp .env.example .env
.env
ファイルの内容を編集し、Difyのログイン情報を設定します。
DIFY_ORIGIN=http://localhost # DifyのURLを指定
EMAIL=your-email@example.com # ログイン用メールアドレス
PASSWORD=your-password # ログイン用パスワード
実装の紹介
以下に、コードの主要な処理を順を追って解説します。
アクセストークンの取得
まず、Difyの内部APIを利用するためにトークンを用意する必要があります。
DifyのログインAPIは POST /console/api/login
を使用し、リクエストボディには email
と password
を含める必要があります。成功すると、レスポンスには access_token
が含まれ、このトークンは以降のAPIリクエストで利用します。
async def login_and_get_token() -> str:
params = {"page": page, "limit": limit}
headers = {"Authorization": f"Bearer {access_token}"}
for attempt in range(retries):
response = await client.get(f"{BASE_URL}/apps", headers=headers, params=params)
if response.status_code == 200:
return response.json()
else:
print(
f"Attempt {attempt + 1} failed: {response.status_code} - {response.text}"
)
await asyncio.sleep(0.5) # Wait before retrying
ダッシュボードにあるアプリの情報一覧を取得
取得したアクセストークンを用いて、Difyに登録されている全アプリの情報を取得します。
DSLのダウンロードにはアプリごとのIDが必要なためこのAPIをコールする必要があります。
Difyの全アプリの情報取得は GET /console/api/apps
を使用し、リクエストには Authorization: Bearer <access_token>
ヘッダーとページングをパラメータに含める必要があります。
※ リトライ処理も含めているためネストが深い
async def fetch_app_per_page(
access_token: str, page: int, limit: int, retries: int = 3
) -> dict:
params = {"page": page, "limit": limit}
headers = {"Authorization": f"Bearer {access_token}"}
for attempt in range(retries):
response = await client.get(f"{BASE_URL}/apps", headers=headers, params=params)
if response.status_code == 200:
return response.json()
else:
print(
f"Attempt {attempt + 1} failed: {response.status_code} - {response.text}"
)
await asyncio.sleep(0.5) # Wait before retrying
raise Exception("Failed to fetch app list.")
async def get_app_list(access_token: str) -> tuple[list, int]:
app_list = []
page = 1
limit = 30
app_num = 0
while True:
content = await fetch_app_per_page(access_token, page, limit)
if page == 1:
# Calculate total pages from the first API response
app_num = content.get("total", 0)
max_page_num = app_num // limit + (app_num % limit > 0)
print(f"Total apps: {app_num}, Total pages: {max_page_num}")
if app_num == 0:
return [], 0
if page > max_page_num:
break
app_per_page = [
{"id": app.get("id"), "name": app.get("name")}
for app in content.get("data", [])
]
app_list.extend(app_per_page)
page += 1
return app_list, app_num
アプリの情報一覧のレスポンス例
レスポンスには、アプリのIDや名前を含むデータのリストがJSON形式で返されます。
ここにレスポンス例を紹介します。
- page:取得したアプリ一覧に対応するページ番号
- limit:リクエストしたlimitに対応
- data:アプリ情報がリストになっています
- 今回は
id
とname
のみ抽出します
- 今回は
{
"page": 1,
"limit": 30,
"total": 4,
"has_more": false,
"data": [
{
"id": "9906b07e-bad4-4fec-809f-5fab43164d36",
"name": "テストチャット",
"max_active_requests": null,
"description": "テストチャットボットです",
"mode": "advanced-chat",
"icon_type": "emoji",
"icon": "older_adult",
"icon_background": "#D5F5F6",
"icon_url": null,
"model_config": null,
"workflow": null,
"use_icon_as_answer_icon": false,
"created_by": "0c4123cb-23b3-48c8-88ff-a15cea1f51bf",
"created_at": 1734187926,
"updated_by": "0c4123cb-23b3-48c8-88ff-a15cea1f51bf",
"updated_at": 1734187961,
"tags": []
},
{
"id": "0e3110ac-66d9-47f4-ac6a-f15c5a5d3790",
"name": "testset",
"max_active_requests": null,
"description": "",
"mode": "workflow",
"icon_type": "emoji",
"icon": "🤖",
"icon_background": "#FFEAD5",
"icon_url": null,
"model_config": null,
"workflow": {
"id": "e77b6145-57b9-4117-8e01-f4c6105b917f",
"created_by": "0c4123cb-23b3-48c8-88ff-a15cea1f51bf",
"created_at": 1733542770,
"updated_by": null,
"updated_at": 1733541702
},
"use_icon_as_answer_icon": false,
"created_by": "0c4123cb-23b3-48c8-88ff-a15cea1f51bf",
"created_at": 1733501248,
"updated_by": "0c4123cb-23b3-48c8-88ff-a15cea1f51bf",
"updated_at": 1733542793,
"tags": []
}
]
}
DSLファイルをダウンロード
取得したアプリのid
をパスパラメータapp_id
として指定して、DSLファイルをダウンロードします。
DifyのDSLファイル取得APIは GET /console/api/apps/{app_id}/export?include_secret=true
を使用し、リクエストには Authorization: Bearer <access_token>
ヘッダーを含める必要があります。レスポンスには該当アプリのDSLファイルがYAML形式で返されます。
※ リトライ処理を追加しているためネストが深くなっています
async def download_yml_files(access_token: str, apps: list):
create_dsl_folder() # Create folder to save YML files
tasks = [asyncio.create_task(download_yml_file(access_token, app)) for app in apps]
await asyncio.gather(*tasks) # Run all download tasks concurrently
async def download_yml_file(access_token, app, retries=3):
app_id = app["id"]
app_name = app["name"]
headers = {"Authorization": f"Bearer {access_token}"}
url = f"{BASE_URL}/apps/{app_id}/export?include_secret=true"
# Limit the maximum number of concurrent tasks using a semaphore
async with semaphore:
for attempt in range(retries):
response = await client.get(url, headers=headers)
if response.status_code == 200:
dsl_data = response.json().get("data").encode("utf-8")
file_name = f"{DSL_FOLDER_PATH}/{app_name}.yml"
with open(file_name, "wb") as file:
file.write(dsl_data)
print(f"Downloaded: {file_name}")
return
else:
print(
f"Attempt {attempt + 1} failed: {response.status_code} - {app_name}"
)
await asyncio.sleep(0.5) # Wait before retrying
raise Exception("Failed to fetch app list.")
実行して全DSLをダウンロード
以下コマンドで実行するとダウンロードが開始されます
$ poetry run python ./src/main.py
ダウンロードが成功すれば以下の様に ./dsl
が新規作成され、DSLファイルが配置されます。
$ ls ./dsl
xxxx.yml
xxxx.yml
xxxx.yml
まとめ
本記事では、Difyの内部APIを活用して、ダッシュボード内の全アプリのDSLファイルをダウンロードする方法を紹介しました。
重要なポイント
- PythonとPoetryを用いた環境構築
- アクセストークンの取得
- APIを利用したアプリ情報の取得
- DSLファイルのダウンロード
このスクリプトを活用することで、Difyのアプリ設定を一括管理しやすくなります。
Discussion