Dify アーキテクチャ概観:6,000 ファイルの LLM プラットフォームを読み解く
前提知識
- ›Flask と Python Web アプリケーションの基礎知識
- ›Docker Compose の基本的な使い方
- ›LLM アプリケーションの概念的な理解
Dify アーキテクチャ概観:6,000 ファイルの LLM プラットフォームを読み解く
Dify は、チャットボット・RAG パイプライン・多段階ワークフロー・エージェント型ツールを、ビジュアルインターフェースと充実した API を通じて構築できるオープンソースの LLM プラットフォームです。Python と TypeScript で書かれた 6,000 以上のファイルからなるコードベースは、一見すると圧倒的なボリュームに感じられます。本記事では、モノレポの構成、デプロイトポロジー、起動シーケンス、ルーティング構造、設定システムを順に解説し、コードベース全体を自信を持って読み進めるための地図を提供します。
リポジトリ構成とドメイン駆動レイアウト
Dify のモノレポは、3つのトップレベルドメインで構成されています。それぞれに AGENTS.md が置かれており、そのドメインの規約が明文化されています。
| ディレクトリ | 言語 | 役割 | おおよそのファイル数 |
|---|---|---|---|
api/ |
Python (Flask) | バックエンド API サーバーと Celery ワーカー | 約 650 コアファイル |
web/ |
TypeScript (Next.js) | コンソールフロントエンドおよび埋め込みチャット UI | 約 5,100 ファイル |
docker/ |
YAML / Shell | Docker Compose オーケストレーションと SSRF プロキシ設定 | 約 20 ファイル |
プロジェクトルートの AGENTS.md には設計思想が明示されています。バックエンドはドメイン駆動設計(DDD)とクリーンアーキテクチャの原則に従い、非同期処理は Redis をブローカーとする Celery が担います。依存関係はコンストラクタインジェクションで解決し、エラーはドメイン固有の例外として適切なレイヤーで処理します。
api/ 内のディレクトリ構成は、DDD の境界を忠実に反映しています。
api/
├── controllers/ # HTTP層: blueprint、リクエストバリデーション
├── services/ # アプリケーションサービス: オーケストレーションロジック
├── core/ # ドメインコア: ワークフローエンジン、RAG、モデル管理
│ ├── app/ # アプリ実行パイプライン
│ ├── rag/ # Retrieval-Augmented Generation
│ ├── workflow/ # ワークフローエンジンとノードファクトリ
│ ├── tools/ # ツール抽象化とランタイム
│ └── plugin/ # プラグインデーモン連携
├── models/ # SQLAlchemy ORM モデル
├── configs/ # Pydantic 設定システム
├── extensions/ # Flask 拡張の初期化処理
└── tasks/ # Celery タスク定義
ヒント: Dify コードベースを初めて読む際は、
api/app.py→api/app_factory.py→api/extensions/の順にたどると全体像が掴みやすくなります。この起動チェーンを追うことで、各サブシステムがどのように組み合わされているかが見えてきます。
Docker Compose サービストポロジー
本番環境の Dify は、Docker Compose が管理する七つの主要サービスで構成されます。API サーバーを中心に据えたハブアンドスポーク型のアーキテクチャです。
flowchart TD
subgraph External
Client[Browser / API Client]
end
subgraph Docker Network
Nginx[nginx reverse proxy]
API[api - Gunicorn/Flask]
Worker[worker - Celery]
Beat[worker_beat - Celery Beat]
Web[web - Next.js]
PluginDaemon[plugin_daemon]
Sandbox[sandbox - Code Execution]
SSRFProxy[ssrf_proxy - Squid]
subgraph Data Stores
Postgres[(PostgreSQL)]
Redis[(Redis)]
end
end
Client --> Nginx
Nginx --> API
Nginx --> Web
API --> Postgres
API --> Redis
API --> PluginDaemon
API --> SSRFProxy
Worker --> Postgres
Worker --> Redis
Beat --> Redis
Sandbox --> SSRFProxy
PluginDaemon --> Postgres
PluginDaemon -->|backwards invocation| API
docker/docker-compose.yaml にサービストポロジーが定義されています。api と worker の両サービスは同一の Docker イメージ(langgenius/dify-api)を使用しており、MODE 環境変数によってのみ動作が切り替わります。HTTP サーバーとして起動する場合は api、Celery ワーカーとして起動する場合は worker を指定します。
SSRF プロキシ(Squid)は、意図的に設けられたセキュリティ境界です。ワークフローノードが URL の取得・API 呼び出し・Webhook の実行といった外向きの HTTP リクエストを行う際、これらはすべてこのプロキシ経由でルーティングされます。悪意あるワークフローが内部ネットワークのサービスを探索するような Server-Side Request Forgery(SSRF)攻撃を防ぐための仕組みです。サンドボックスのコード実行環境も同様に隔離されており、専用ネットワーク上の ssrf_proxy にのみ接続できます。
プラグインデーモンは、信頼されていないプラグインコードを分離されたプロセスで実行する独立した Go サービスです。INNER_API_KEY_FOR_PLUGIN で認証された内部 API を通じて Dify API に「バックワードインボケーション」チャネルで通信します。この仕組みの詳細は第 5 部で掘り下げます。
二プロセスアーキテクチャ:API サーバーと Celery ワーカー
Dify のバックエンドは、単一のコードベースから二種類のプロセスとして動作します。エントリーポイントである api/app.py は、どちらのプロセスにも対応しています。
flowchart TD
Start[app.py] --> IsDB{is_db_command?}
IsDB -->|Yes| MigApp[create_migrations_app<br/>minimal Flask + DB + Migrate]
IsDB -->|No| FullApp[create_app<br/>full Flask + 28 extensions]
FullApp --> FlaskApp[app = DifyApp]
FullApp --> CeleryApp[celery = app.extensions.celery]
FlaskApp --> Gunicorn[Gunicorn serves HTTP]
CeleryApp --> CeleryWorker[Celery processes tasks]
このファイルは驚くほどコンパクトです。Gunicorn ワーカーとして起動した場合、app は Flask の WSGI アプリケーションとして機能します。Celery ワーカーとして起動した場合も同じ app が生成され、Flask の extensions レジストリから celery オブジェクトが取り出されます。このコードベース共有パターンにより、両プロセスがモデル・サービス・設定に対して同一のアクセス手段を持つことが保証されます。
gevent モンキーパッチの適用戦略は、両方の起動パスで慎重に設計されています。Gunicorn の gunicorn.conf.py は post_patch イベントサブスクライバを使用しており、gevent が組み込みモジュールにパッチを当てた後に処理が実行されます。この順序は非常に重要です。gevent が標準ライブラリにパッチを当てる前に gRPC や psycopg2 にパッチを適用してしまうと、デッドロックが発生します。
def post_patch(event):
if not isinstance(event, gevent_events.GeventDidPatchBuiltinModulesEvent):
return
grpc_gevent.init_gevent()
pscycogreen_gevent.patch_psycopg()
一方、api/celery_entrypoint.py はよりシンプルなアプローチを取っており、インポート時に即座にパッチを適用します。Celery の gevent プールがこのモジュールの読み込み前にモンキーパッチ処理を済ませるためです。
ヒント: 開発中に原因不明のハングやデッドロックが起きた場合は、gevent パッチの適用順序を確認しましょう。
gunicorn.conf.pyにある issue #26689 へのコメントには、実際にこの問題が発生した事例が記録されています。
起動シーケンス:create_app() と 28 の拡張
起動シーケンスの中核は app_factory.py にあります。create_app() 関数は Flask アプリを生成して設定を適用した後、厳密に順序付けられたリストに従い 28 の拡張を初期化します。
この順序には明確な意図があります。それぞれの依存関係を反映したものです。
flowchart LR
subgraph Phase 1: Foundation
TZ[ext_timezone]
LOG[ext_logging]
WARN[ext_warnings]
IMP[ext_import_modules]
end
subgraph Phase 2: Core Infrastructure
DB[ext_database]
REDIS[ext_redis]
STOR[ext_storage]
CEL[ext_celery]
end
subgraph Phase 3: Application Layer
BP[ext_blueprints]
CMD[ext_commands]
OTEL[ext_otel]
SESS[ext_session_factory]
end
TZ --> LOG --> WARN --> IMP --> DB --> REDIS --> STOR --> CEL --> BP --> CMD --> OTEL --> SESS
各拡張モジュールのインターフェースはシンプルです。init_app(app) 関数を公開し、必要に応じて is_enabled() による有効化判定を実装します。app_factory.py#L171-L213 の初期化ループは、init_app() を呼び出す前に is_enabled() を確認し、デバッグモードでは各拡張の初期化にかかった時間もログに記録します。
for ext in extensions:
short_name = ext.__name__.split(".")[-1]
is_enabled = ext.is_enabled() if hasattr(ext, "is_enabled") else True
if not is_enabled:
if dify_config.DEBUG:
logger.info("Skipped %s", short_name)
continue
start_time = time.perf_counter()
ext.init_app(app)
順序に関する主な制約をまとめると次の通りです。
ext_databaseの後にext_migrate— マイグレーションにはデータベース接続が必要ext_storageの後にext_logstore— ログストレージはストレージバックエンドに依存ext_logstoreの後にext_celery— Celery タスクは初期化中にログアクセスが必要になる場合があるext_blueprintsはほぼ最後 — ルート登録は他のほぼすべての拡張に依存する
また、flask db コマンド実行時に使用される create_migrations_app() という関数も別に用意されており、こちらは ext_database と ext_migrate のみを初期化します。
blueprint 登録とルートグループ
Dify の HTTP API は七つの blueprint グループに整理されており、ext_blueprints.py で登録されます。
| Blueprint | URL プレフィックス | CORS ポリシー | 役割 |
|---|---|---|---|
console_app_bp |
/console/api |
オリジン制限あり、認証情報あり | 管理コンソール API |
service_api_bp |
/v1 |
Authorization ヘッダーを許可するオープン設定 |
外部開発者向け API |
web_bp |
/api |
オリジン制限あり、ルートごとに設定 | Web アプリ(エンドユーザー向け) |
files_bp |
/files |
CSRF トークン付きのオープン設定 | ファイルのアップロード・ダウンロード |
inner_api_bp |
/inner/api |
なし(内部専用) | プラグインデーモンのコールバック |
mcp_bp |
/mcp |
なし | Model Context Protocol エンドポイント |
trigger_bp |
/trigger |
Webhook ヘッダーを許可するオープン設定 | Webhook トリガーエンドポイント |
CORS 設定はヘルパー関数 _apply_cors_once() によって管理されており、テストインスタンスで blueprint が再利用された際の二重適用を防いでいます。最も複雑な CORS 設定を持つのは web_bp です。埋め込みボットのエンドポイント(/chat-messages)では認証情報なしの緩やかなクロスオリジンポリシーが適用される一方、認証が必要なエンドポイントではより厳格なオリジンチェックが要求されます。
flowchart TD
Request[HTTP Request] --> PathMatch{Path prefix?}
PathMatch -->|/console/api| Console[Console Blueprint<br/>Cookie auth + CSRF]
PathMatch -->|/v1| ServiceAPI[Service API Blueprint<br/>Bearer token auth]
PathMatch -->|/api| WebApp[Web Blueprint<br/>Passport token auth]
PathMatch -->|/files| Files[Files Blueprint<br/>Signed URL auth]
PathMatch -->|/inner/api| Inner[Inner API Blueprint<br/>API key auth]
PathMatch -->|/mcp| MCP[MCP Blueprint]
PathMatch -->|/trigger| Trigger[Trigger Blueprint<br/>Webhook auth]
設定システム:リモートソース対応の Pydantic 多重継承
Dify の設定は Pydantic の BaseSettings クラスを基盤としており、多重継承によって九つの設定グループを一つの DifyConfig クラスに集約しています。
classDiagram
class DifyConfig {
+model_config: SettingsConfigDict
+settings_customise_sources()
}
class PackagingInfo
class DeploymentConfig
class FeatureConfig
class MiddlewareConfig
class ExtraServiceConfig
class ObservabilityConfig
class RemoteSettingsSourceConfig
class EnterpriseFeatureConfig
class EnterpriseTelemetryConfig
DifyConfig --|> PackagingInfo
DifyConfig --|> DeploymentConfig
DifyConfig --|> FeatureConfig
DifyConfig --|> MiddlewareConfig
DifyConfig --|> ExtraServiceConfig
DifyConfig --|> ObservabilityConfig
DifyConfig --|> RemoteSettingsSourceConfig
DifyConfig --|> EnterpriseFeatureConfig
DifyConfig --|> EnterpriseTelemetryConfig
設定の解決は、settings_customise_sources() クラスメソッドで定義された六段階の優先順位チェーンに従います。
- Init settings — プログラムによる上書き
- 環境変数 —
os.environ - リモート設定 — Apollo または Nacos 設定サーバー(
RemoteSettingsSourceFactory経由) - dotenv ファイル —
.env - ファイルシークレット — Docker secrets
- TOML ファイル —
pyproject.tomlのデフォルト値
RemoteSettingsSourceFactory の実装は特に注目に値します。match 文を使い、REMOTE_SETTINGS_SOURCE_NAME の値に応じて ApolloSettingsSource または NacosSettingsSource に処理を振り分けます。これにより、同一のコードベースを維持しながらエンタープライズの設定管理システムへ設定を集中管理できます。
ヒント:
MiddlewareConfigグループだけで、30 種類以上のベクターデータベース・複数のキャッシュバックエンド・ストレージプロバイダーの設定が含まれています。設定値が見つからない場合は、親クラスを検索しましょう。設定はapi/configs/middleware/・api/configs/feature/など複数のファイルに分散しています。
次のステップ
アーキテクチャの基盤を把握できたところで、次は実際のリクエストがシステムに到達した際の流れを追ってみましょう。第 2 部では、HTTP リクエストがコントローラーレイヤーに届いてから AppGenerateService のモードディスパッチャーを経由する過程を追います。Generator → Runner → QueueManager → TaskPipeline という四段階の実行パターンを詳しく解説します。