「Difyソースコードリーディング #6 - DSLのYAMLファイルの扱いを読み解く」を開催しました #もくもくDify

ジェネラティブエージェンツの大嶋です。

もくもくDifyで「Difyソースコードリーディング #6 - DSLYAMLファイルの扱いを読み解く」という勉強会を開催しました。

dify-mokumoku.connpass.com

アーカイブ動画はこちらです。

youtube.com

Difyのソースコードは以下です。

github.com

今回も 戸塚さん と一緒に話しながらコードを読んでいきました!

今回のポイント

DifyにはアプリをDSLと呼ばれるYAMLファイルでexport・importする機能があります。

参考:https://docs.dify.ai/ja-jp/guides/application-orchestrate/creating-an-application

今回はDSLYAMLファイルの内容がどのように作られるのかを読み解いていきました。

YAMLファイルのexportのAPI

まず最初に、YAMLファイルをexportするAPIを見つけました。

api.add_resource(AppExportApi, "/apps/<uuid:app_id>/export")

https://github.com/langgenius/dify/blob/4373777871b33daa13ae55c52a06f8e76764cbfc/api/controllers/console/app/app.py#L369C1-L370C1

その処理では、AppDslServiceのexport_dslが使われていました。

        return {"data": AppDslService.export_dsl(app_model=app_model, include_secret=args["include_secret"])}

https://github.com/langgenius/dify/blob/4373777871b33daa13ae55c52a06f8e76764cbfc/api/controllers/console/app/app.py#L245

AppDslServiceのexport_dsl

AppDslServiceのexport_dslを読んでいくと、dictをYAMLに変換して返していることが分かりました。

    def export_dsl(cls, app_model: App, include_secret: bool = False) -> str:
            :
        export_data = {
            "version": current_dsl_version,
            :
        }
            :
        return yaml.dump(export_data, allow_unicode=True)

https://github.com/langgenius/dify/blob/4373777871b33daa13ae55c52a06f8e76764cbfc/api/services/app_dsl_service.py#L159

Workflowクラス(workflowsテーブル)

さらに読み進めていくと、DBのworkflowsテーブルに対応するWorkflowクラスに、このクラスのデータ(つまりworkflowsテーブルのレコード)をdictに変換する処理が実装されており、これが使われていることが分かりました。

    def to_dict(self, *, include_secret: bool = False) -> Mapping[str, Any]:

https://github.com/langgenius/dify/blob/4373777871b33daa13ae55c52a06f8e76764cbfc/api/models/workflow.py#L269

workflowsテーブルにはgraphやfeaturesといったカラムがあり、ここにJSON形式でワークフローのグラフなどが保存されているようです。

    graph: Mapped[str] = db.Column(db.Text)
    features: Mapped[str] = db.Column(db.Text)

https://github.com/langgenius/dify/blob/4373777871b33daa13ae55c52a06f8e76764cbfc/api/models/workflow.py#L122C1-L124C1

そのJSONが具体的にどのように構成されているのかを確認しようと読み進めたところ、バックエンドではなくフロントエンドで定義されていることに辿り着きました。

(おそらくですが、バックエンドでのバリデーションなどはなさそうでした)

フロントエンドのNode・Edgeのtype

フロントエンドには、CommonNodeTypeやCommonEdgeTypeといったtypeが定義されていました。

export type CommonNodeType<T = {}> = {

https://github.com/langgenius/dify/blob/4373777871b33daa13ae55c52a06f8e76764cbfc/web/app/components/workflow/types.ts#L43C1-L44C1

export type CommonEdgeType = {

https://github.com/langgenius/dify/blob/4373777871b33daa13ae55c52a06f8e76764cbfc/web/app/components/workflow/types.ts#L69C1-L70C1

各ノードは、CommonNodeTypeを拡張して用意されていました。 たとえばLLMのノードのtypeは以下のようになっています。

export type LLMNodeType = CommonNodeType & {
  model: ModelConfig
  prompt_template: PromptItem[] | PromptItem
    :

https://github.com/langgenius/dify/blob/4373777871b33daa13ae55c52a06f8e76764cbfc/web/app/components/workflow/nodes/llm/types.ts#L4

React Flow

ワークフローのフロントエンドは、上記のtypeを使いつつ、React Flowというライブラリを使って実装されていることが分かりました。

reactflow.dev

次回のご案内

以上、DSLYAMLファイルがどのように作られるのか、雰囲気を読み解いてみました。

今まではバックエンドのソースコードリーディングが多かったので、今回フロントエンドも少し扱えたのはよかったです。

次回は「ビルドやリリース時のバージョン番号付けなどを読み解く」というテーマです。 ご興味ある方はぜひ気軽にご参加ください!

dify-mokumoku.connpass.com

また、水曜日にもDifyの活用についてのもくもく会があります。 こちらもご興味ある方はぜひ気軽にご参加ください!

dify-mokumoku.connpass.com