ジェネラティブエージェンツの大嶋です。
もくもくDifyで「Difyソースコードリーディング #6 - DSLのYAMLファイルの扱いを読み解く」という勉強会を開催しました。
アーカイブ動画はこちらです。
Difyのソースコードは以下です。
今回も 戸塚さん と一緒に話しながらコードを読んでいきました!
今回のポイント
DifyにはアプリをDSLと呼ばれるYAMLファイルでexport・importする機能があります。
参考:https://docs.dify.ai/ja-jp/guides/application-orchestrate/creating-an-application
今回はDSLのYAMLファイルの内容がどのように作られるのかを読み解いていきました。
YAMLファイルのexportのAPI
まず最初に、YAMLファイルをexportするAPIを見つけました。
api.add_resource(AppExportApi, "/apps/<uuid:app_id>/export")
その処理では、AppDslServiceのexport_dslが使われていました。
return {"data": AppDslService.export_dsl(app_model=app_model, include_secret=args["include_secret"])}
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)
Workflowクラス(workflowsテーブル)
さらに読み進めていくと、DBのworkflowsテーブルに対応するWorkflowクラスに、このクラスのデータ(つまりworkflowsテーブルのレコード)をdictに変換する処理が実装されており、これが使われていることが分かりました。
def to_dict(self, *, include_secret: bool = False) -> Mapping[str, Any]:
workflowsテーブルにはgraphやfeaturesといったカラムがあり、ここにJSON形式でワークフローのグラフなどが保存されているようです。
graph: Mapped[str] = db.Column(db.Text) features: Mapped[str] = db.Column(db.Text)
そのJSONが具体的にどのように構成されているのかを確認しようと読み進めたところ、バックエンドではなくフロントエンドで定義されていることに辿り着きました。
(おそらくですが、バックエンドでのバリデーションなどはなさそうでした)
フロントエンドのNode・Edgeのtype
フロントエンドには、CommonNodeTypeやCommonEdgeTypeといったtypeが定義されていました。
export type CommonNodeType<T = {}> = {
export type CommonEdgeType = {
各ノードは、CommonNodeTypeを拡張して用意されていました。 たとえばLLMのノードのtypeは以下のようになっています。
export type LLMNodeType = CommonNodeType & { model: ModelConfig prompt_template: PromptItem[] | PromptItem :
React Flow
ワークフローのフロントエンドは、上記のtypeを使いつつ、React Flowというライブラリを使って実装されていることが分かりました。
次回のご案内
以上、DSLのYAMLファイルがどのように作られるのか、雰囲気を読み解いてみました。
今まではバックエンドのソースコードリーディングが多かったので、今回フロントエンドも少し扱えたのはよかったです。
次回は「ビルドやリリース時のバージョン番号付けなどを読み解く」というテーマです。 ご興味ある方はぜひ気軽にご参加ください!
また、水曜日にもDifyの活用についてのもくもく会があります。 こちらもご興味ある方はぜひ気軽にご参加ください!