Read OSS

モデルプロバイダー、ツールエコシステム、プラグインアーキテクチャ

上級

前提知識

  • 第1〜3回:アーキテクチャ、リクエストフロー、ワークフローエンジン
  • LLM APIとトークン課金の基本知識
  • LLMにおけるfunction calling / tool useの理解

モデルプロバイダー、ツールエコシステム、プラグインアーキテクチャ

Difyが提供する価値の核心は「抽象化」にあります。どのLLMプロバイダーも、どのツールも、どのデータソースも、ワークフロービルダーやAPIコンシューマーが意識することなく使える統一インターフェースを通じて接続されています。本稿(最終回)では、相互に連携する3つのシステムを掘り下げます。100以上のLLMプロバイダーを標準化するモデルプロバイダー抽象化、エージェントやワークフローに外部機能を提供するツール分類体系、そして信頼できない拡張機能を隔離されたプロセスで安全に実行するプラグインデーモンアーキテクチャです。

ProviderManagerとクレデンシャル解決

Difyにおけるすべてのモデル呼び出しは、クレデンシャルの解決から始まります。ProviderManager は、テナント・プロバイダータイプ・設定の優先順位を考慮しながら、特定のモデルに対してどのクレデンシャルを使用するかを決定する責務を担います。

flowchart TD
    Request[Model invocation request] --> PM[ProviderManager]
    PM --> TenantLookup[Lookup tenant providers]
    TenantLookup --> CredCheck{Credential source?}
    CredCheck -->|Custom| Custom[Custom provider credentials<br/>user-supplied API keys]
    CredCheck -->|System| System[System-configured credentials<br/>admin-provisioned]
    CredCheck -->|Plugin| Plugin[Plugin daemon credentials<br/>from installed plugins]
    Custom --> PC[ProviderConfiguration]
    System --> PC
    Plugin --> PC
    PC --> PMB[ProviderModelBundle<br/>credentials + model type instance]
    PMB --> MI[ModelInstance]

ProviderManager は複数のデータベーステーブルを参照します。Provider(テナントとプロバイダーのバインディング)、ProviderCredential(暗号化されたクレデンシャルストレージ)、ProviderModel(モデルごとの設定)、LoadBalancingModelConfig(複数エンドポイントへの分散設定)です。

クレデンシャルの解決には優先順位があります。

  1. カスタムモデルクレデンシャル — ユーザーがモデルごとに設定したAPIキー
  2. カスタムプロバイダークレデンシャル — ユーザーがプロバイダーレベルで設定したAPIキー
  3. システムクレデンシャル — 管理者が設定した共有クレデンシャル
  4. ホスト済みクォータ — Dify Cloudが提供するトライアル/有料プランのモデルアクセス

クレデンシャルは ProviderCredentialsCache によってTTLベースでキャッシュされるため、モデル呼び出しのたびにデータベースへアクセスするコストを回避できます。

ModelInstance:統一されたLLM抽象化

ModelInstance クラスは、任意のLLMプロバイダーを一貫したインターフェースでラップします。OpenAI、Anthropic、ローカルのOllamaインスタンス、カスタムのプラグイン提供モデルのどれを呼び出す場合でも、呼び出し側コードから見えるAPIは常に同じです。

classDiagram
    class ModelInstance {
        +provider_model_bundle: ProviderModelBundle
        +model_name: str
        +credentials: dict
        +load_balancing_manager
        +invoke_llm()
        +invoke_text_embedding()
        +invoke_rerank()
        +invoke_tts()
        +invoke_speech2text()
        +invoke_moderation()
    }

    class ProviderModelBundle {
        +configuration: ProviderConfiguration
        +model_type_instance: ModelTypeInstance
    }

    class ProviderConfiguration {
        +provider: ProviderEntity
        +get_current_credentials()
    }

    class LoadBalancingManager {
        +invoke_with_fallback()
    }

    ModelInstance --> ProviderModelBundle
    ProviderModelBundle --> ProviderConfiguration
    ModelInstance --> LoadBalancingManager

ModelInstance の主な特徴を見ていきましょう。

  • ロードバランシングLoadBalancingModelConfig エントリが存在する場合、同じモデルの複数APIエンドポイントに呼び出しを分散します。単一のAPIキーでレートリミットに達しやすい本番環境では、この機能が非常に重要になります。
  • クレデンシャル管理 — クレデンシャルは ProviderModelBundle から取得され、インスタンスにキャッシュされます。
  • モデルスキーマ解決get_model_schema() はモデルの機能(コンテキストウィンドウ、対応機能、料金)を記述した AIModelEntity を返します。
  • 統一された呼び出しinvoke_llm()invoke_text_embedding()invoke_rerank() などが、一貫した呼び出し規約を提供します。

HostingConfiguration は Dify Cloud のホスト済みモデルプロバイダーを管理し、使用量制限付きのトライアルクォータと有料プランをサポートします。

ヒント: モデル呼び出しが ProviderTokenNotInitError で失敗する場合、ほぼ必ずクレデンシャルの解決に問題があります。対象のテナントが、そのプロバイダーとモデルの組み合わせに対して有効なクレデンシャルを設定しているか、Provider テーブルと ProviderCredential テーブルを確認してみましょう。

ツール型の分類体系

Difyはランタイム特性の異なる5種類のツールをサポートしています。

classDiagram
    class Tool {
        <<abstract>>
        +entity: ToolEntity
        +runtime: ToolRuntime
        +invoke()
        +tool_provider_type()
    }

    class BuiltinTool {
        Built-in tools shipped with Dify
    }
    class PluginTool {
        Tools from installed plugins
    }
    class ApiTool {
        OpenAPI/Swagger defined tools
    }
    class MCPTool {
        Model Context Protocol tools
    }
    class WorkflowTool {
        Workflows exposed as tools
    }

    Tool <|-- BuiltinTool
    Tool <|-- PluginTool
    Tool <|-- ApiTool
    Tool <|-- MCPTool
    Tool <|-- WorkflowTool

基底クラスである Tool が満たすべき契約を定義しています。

  • invoke() — ランタイムパラメーターをマージし、パラメーター型を変換して _invoke() に委譲する公開エントリーポイント
  • _invoke() — 各ツールサブクラスが実装する抽象メソッド
  • tool_provider_type() — ツールのカテゴリを識別する
  • fork_tool_runtime() — 異なるランタイムコンテキストを持つコピーを生成する

ToolManager は5種類すべてにわたってツールプロバイダーを解決します。探索先は以下のとおりです。

  • Builtin toolscore/tools/builtin_tool/providers/
  • Plugin toolsPluginToolManager 経由でプラグインデーモンから
  • API tools — データベース上の ApiToolProvider レコード
  • MCP tools — 設定済みのMCPサーバー接続から
  • Workflow tools — データベース上の WorkflowToolProvider レコード

各ツール型には専用のプロバイダーコントローラーがあります。例えばBuiltinToolProviderControllerApiToolProviderControllerMCPToolProviderControllerなどです。これらがクレデンシャル管理、パラメータースキーマ解決、ツールのインスタンス化を担当します。

ToolEngine:ランタイムでのツール実行

ToolEngine は、パラメーターの解析・呼び出し・ファイル管理・エラーハンドリングといった複雑な処理を引き受け、ツールの実行ランタイムを提供します。

sequenceDiagram
    participant Agent/Node
    participant ToolEngine
    participant Tool
    participant Callback

    Agent/Node->>ToolEngine: agent_invoke(tool, parameters)
    ToolEngine->>ToolEngine: Parse parameters (str → dict)
    ToolEngine->>Callback: on_tool_start()
    ToolEngine->>Tool: invoke(user_id, parameters)
    Tool-->>ToolEngine: Generator[ToolInvokeMessage]
    ToolEngine->>ToolEngine: Process messages<br/>transform files, extract text
    ToolEngine->>Callback: on_tool_end()
    ToolEngine-->>Agent/Node: (text, file_ids, meta)

tool_engine.py#L47-L79agent_invoke() 静的メソッドは、特に厄介なケースを処理します。LLMがツールのパラメーターをJSONオブジェクトではなく生の文字列として渡してくるケースです。ツールが LLM 形式のパラメーターを1つだけ持つ場合はその文字列をそのまま使用し、それ以外の場合はJSONパースを試み、失敗してもグレースフルにフォールバックします。

ツール呼び出しの結果は ToolInvokeMessage のジェネレーターで、テキスト・画像・ファイル・リンク・JSONを順次 yield できます。ToolFileMessageTransformer はバイナリファイルの出力を、署名付きURLを持つアップロード済みファイルへと変換します。

プラグインデーモンと逆方向呼び出し

プラグインデーモンは、信頼できないコードを安全に実行するためにDifyが採用したソリューションです。Go言語で書かれた独立したサービスで、プラグインを隔離されたプロセスで実行し、双方向チャネルを通じてDify本体のAPIと通信します。

flowchart TD
    subgraph Dify API Process
        API[Flask API Server]
        InnerAPI[Inner API Blueprint<br/>/inner/api]
        PluginImpl[core/plugin/impl/]
    end

    subgraph Plugin Daemon Process
        Daemon[dify-plugin-daemon<br/>Go service]
        PluginRuntime[Plugin Runtime<br/>isolated process per plugin]
    end

    API -->|Forward call| PluginImpl
    PluginImpl -->|HTTP| Daemon
    Daemon --> PluginRuntime
    PluginRuntime -->|Backwards invocation| InnerAPI
    InnerAPI --> API

    style PluginRuntime fill:#f9f,stroke:#333

このアーキテクチャには2方向の通信経路があります。

順方向呼び出し(Forward calls) — Difyがプラグインの機能(モデル推論・ツール実行・データソースアクセス)を必要とするとき、core/plugin/impl/ 内の実装クラスがプラグインデーモンにHTTP呼び出しを行います。デーモンはそれを適切なプラグインランタイムにルーティングします。

逆方向呼び出し(Backwards invocation) — プラグインがDifyに対してコールバックする必要があるとき、プラグインはデーモンを呼び出します。デーモンがそのリクエストをDifyのInner API(/inner/api)に転送します。対象となる処理はモデルの呼び出し、ストレージへのアクセス、データの暗号化、ノードの実行などです。core/plugin/backwards_invocation/ 内の逆方向呼び出しハンドラーは、6つのカテゴリを担当します。

モジュール 役割
model.py プラグインがLLMモデルを呼び出す
tool.py プラグインが別のツールを呼び出す
node.py プラグインがワークフローノードを実行する
app.py プラグインがアプリ状態とやり取りする
encrypt.py プラグインがクレデンシャルを暗号化/復号する
base.py 呼び出しの共通インフラ

PluginModelAssemblytenant_id を受け取り、完全なモデルスタック(runtime → factory → provider manager → model manager)を遅延構築します。プラグイン提供モデルへのアクセスにおける、整理されたエントリーポイントとして機能します。

プラグインデーモンのDocker Compose設定(docker-compose.yaml#L998-L1068)からセキュリティ境界の全体像がわかります。DIFY_INNER_API_URLDIFY_INNER_API_KEY を通じてAPIと通信し、署名検証(FORCE_VERIFYING_SIGNATURE)を有効にしてプラグインを実行し、タイムアウトやバッファサイズを細かく設定できます。

ヒント: プラグイン開発は PLUGIN_REMOTE_INSTALL_HOSTPLUGIN_REMOTE_INSTALL_PORT の設定を通じて行います。これらはデバッグ用エンドポイントを公開しており、開発中のプラグインをこのエンドポイントに接続することで、デーモンを再ビルドすることなくライブリロードテストが可能になります。

MCP統合とマルチテナンシー

core/mcp/ 配下のMCP(Model Context Protocol)統合は、MCPプロトコルに対応した外部ツールサーバーへの接続を実現します。core/tools/mcp_tool/ 内の MCPToolProviderControllerMCPTool クラスは、MCPサーバー接続をDifyのツールとしてラップし、ワークフローやエージェントの設定から利用できるようにします。

マルチテナンシーはモデルとツールのシステム全体に浸透しています。すべてのクレデンシャル・設定・クォータはテナント単位でスコープされます。アカウントモデルの TenantAccountRole enum がRBACを定義しています。

  • Owner — ワークスペース設定の完全な制御権
  • Admin — モデル・ツール・データセットの管理
  • Editor — アプリとワークフローの作成・変更
  • Normal — アプリとワークフローの使用
  • DatasetOperator — データセットの管理のみ

モデルのクレデンシャルはテナントスコープで暗号化され、ツールプロバイダーはテナントごとに解決され、レートリミットはテナント×アプリ単位で適用されます。これにより、Difyのマルチワークスペースアーキテクチャにおける完全な分離が保証されます。

シリーズのまとめ

この5回のシリーズでは、Docker ComposeのトポロジーからFlaskの起動シーケンス、リクエスト実行パイプライン、ワークフローグラフエンジン、RAGパイプラインまでを辿りました。最後に全体を支えるモデルとツールの抽象化レイヤーを解説しました。コードベースは大規模ですが、パターンには一貫性があります。

  • Factoryパターン — 拡張性のため(ノードFactory、ベクターDBファクトリー、インデックスプロセッサーファクトリー)
  • Layerパターン — 横断的関心事のため(クォータ・オブザーバビリティ・永続化)
  • Queueパターン — 疎結合のため(runnerとpipeline間のRedis pub/sub、非同期処理のCelery)
  • Adapterパターン — 統合のため(32のベクターデータベース、100以上のモデルプロバイダー、5種類のツール型)

個々のファイルを暗記しようとするのではなく、これらのパターンを理解することが、6,000ファイルに及ぶDifyのコードベースを自信を持って読み解く鍵になります。