以下の方法で簡単にDocker Composeを使ってDifyを起動することができます。Docker及びDocker Comoposeがインストールされている必要があります。
ここからは、より高度なアプリケーションを作成していきます。生成AIの今ナウくて熱いユースケースであるRAG(Retrieval Augmented Generation)を作成してみましょう。
そもそもRAGってなに?
OpenAIやAzure OpenAI Serviceは、インターネットなどによって公開されている膨大な量の情報を収集し、モデルをトレーニングして、そのモデルに基づき回答を生成しています。
しかしながら、自社が保有している情報をベースに回答を生成したいというユースケースはあると思います。例えば、社内の就業規約に関するQAなどです。育児休業の申請方法は、企業によってその方法が違うのはもちろんですし、そしてそのような情報は社内にてクローズドに管理されている就業規約に書かれていることがほとんどです。そんな就業規約のような独自データに基づいた回答を生成AIがしてくれたら、とっても便利だというのは言うまでもありません。今までセコセコ就業規約を検索して調べてたのが、生成AIに「育児休業の申請方法ってどうすればいいの?」と聞くだけで、回答が返ってくるなんて、とってもステキです。
実現方法として真っ先に思いつくのは、モデルに独自データを追加して学習させることです。一般的には「モデルの微調整」と言われたりしていまして、Azure OpenAI Serviceにもそういうサービスがあります。ただし、モデルの微調整は大変な作業です。モデルの微調整は、学習済みのモデルに追加の情報やデータを組み込むことで、その性能や反応を調整するプロセスなのですが、新しいデータセットの用意、学習の設定やパラメータ調整、そして再学習の実行など、多くの手間と時間がかかります。マイクロソフトも「モデルの微調整は最後の手段」と言っています。
そこで、モデルの微調整の代わりに、RAG(Retrieval Augmented Generation)の手法が推奨されます。時間やコストのかかるモデルの微調整をするのではなく、既存のドキュメントを外部データベースに登録し、ユーザーの質問と関連のあるドキュメントを外部データベースから取得します。そして、その内容に基づき、LLMに回答を指示することで、独自データに基づいて回答させるという手法です。
チャンク化とベクトルデータベース
RAGの仕組みに詳しい方は、この節は読み飛ばしていただいて結構です。ここでは、チャンク化とベクトルデータベースに付いて説明します。
ベクトルデータベース
従来の検索は、検索対象の言葉がどのくらいドキュメントに含まれるかを数えることで行われていました。これを全文検索と呼ぶこととします。全文検査k検索は、厳密には、TF-IDF(Term Frequency-Inverse Document Frequency)という手法を使って、単語の重要度を計算して、検索結果の並び替えを行うなど、複雑な処理が内部では行われています。
この手法だと、微妙な言葉の揺らぎや、同じ意味の言葉の違いなどを考慮することが難しいです。そこでベクトル検索という技法が使われます。
ベクトル検索は、全文検索検索と比べて、より意味を理解することができます。たとえば、全文検索では「リンゴ」と入力すれば、「リンゴ」が書かれたページを見つけますが、ベクトル検索なら「フルーツ」や「健康食品」のような関連する言葉のページも見つけられます。これはベクトル検索が単語の意味を把握し、それに基づいて検索するからです。そのため、もっと広い範囲の情報を見つけやすくなり、また、同じ意味の言葉が違う言語で書かれていても、ベクトル検索ではうまく検索できることが多いです。これにより、より幅広い情報にアクセスしやすくなります。
さて、ここでベクトル検索における比較を理解しやすくするために、例として「甘み」と「価格」という2つの要素を用いて、いくつかの食べ物について考えてみましょう。まず、各食べ物について、どれだけ甘いかという「甘み」、そしていくらかかるかという「価格」に基づいて数値を割り当てます。この数値を使って、それぞれの食べ物を2次元のグラフにプロットします。
このグラフ上で、各食べ物は点として表され、点の位置はその食べ物の「甘み」と「価格」の数値によって決まります。例えば、リンゴは甘さが中程度で価格も手頃なため、中間の位置に点が来ます。一方で、レモンはあまり甘くないため、甘みのスケールでは低い位置に、価格が低いため価格のスケールでも低い位置に点が来ます。
次に、これらの点をベクトルとして考え、原点から各点へと向かう線分を描きます。これがそれぞれの食べ物の「特徴ベクトル」です。ベクトルの角度が小さいほど、2つの食べ物は特徴が似ていると言えます。つまり以下の図でθ1とθ2を見てみます。
リンゴとイチゴは、甘みも価格も似ているためθ1は、小さい角度になります。一方でリンゴとハバネロは価格こそ似ていれど、甘み大きく異なるため、θ2は大きい角度になります。
この角度を計算することにより、これらの果物がどの程度似ているのかを比較しますが、コンピューターは分度器を持っていないので、角度を比較するための計算式が必要となります。そこでコサイン類似度を使います。ベクトルAとベクトルBの内積をベクトルAの長さとベクトルBの長さの積で割ったものになります。数式に表すと以下になります。
コサイン類似度は、2つのベクトルがどれだけ同じ方向を向いているかを測る数値で、1に近いほど似ていると言えます。
では、リンゴとイチゴのコサイン類似度を求めてみます。
0.999となり1にかなり近く、リンゴとイチゴは非常に似ているということがわかります。
では、θ2つまりリンゴとハバネロはのコサイン類似度はどうでしょうか?価格はそこそ近いですが、甘みは大きくかけ離れています。まぁ、イチゴに比べればハバネロはとても辛いので感覚的にわかりますが、先程のコサイン類似度を使って数値として求めてみます。
リンゴとイチゴのコサイン類似度よりも1から遠い値になりました。なるほど、やっぱりリンゴとイチゴのほうがお互い似ていて、リンゴとハバネロはあんまり似ていないということがデータでわかりました。
このように、コサイン類似度を計算することで、グラフ上の位置の違いを超えて、2つの食べ物がどれだけ「特徴」が似ているかを数値で表すことができるのです。
今までは「甘み」と「価格」という2つの次元で類似度を計算していましたが、この考え方は自然言語処理においても応用可能です。自然言語の場合、単語や文書を表すために、何千から何万にも及ぶ多次元ベクトルを使用します。これらの高次元ベクトルには、言語の複雑な特徴が埋め込まれており、文書や単語間の意味的類似度を計算する際に使用されます。この方法により、テキスト間の意味的な距離を定量的に捉えることができます。
前置きが長くなりましたが、このベクトル化されたデータを格納するのものが、「ベクトルデータベース」となります。先の例で言えば、リンゴやイチゴ、ハバネロの特徴ベクトルをデータベースに格納しておき、ユーザーが「リンゴ」と入力したときに、リンゴの特徴ベクトルを取り出し、他の特徴ベクトルとのコサイン類似度を計算して、最も近いものを返すということができます。
環境変数を見る限りでは、コミュニティ版のDifyは以下のベクトルデータベースに対応しているようです。非常に多様な種類に対応していますね。
- Weaviate (デフォルトではこれ)
- Qdrant
- Milvus
- MyScale
- Relyt
- PGVector
- PGVecto-RS
- Chroma
- OpenSearch
- TiDB Vector
- Oracle
- Tencent Vector
- ElasticSearch
- AnalyticDB
- Couchbase
- VikingDB
- OceanBase
- Upstash Vector
- Lindorm
以下の環境変数を参考にしました。
チャンク化
RAGではドキュメントをベクトル化して、ベクトルデータベースに格納します。このベクトル化する際に、ドキュメントを小さな単位に分割することがあります。この小さな単位を「チャンク」と呼びます。チャンク化する理由は、Azure OpenAI ServiceやOpenAIで提供される文章をベクトル化するEmbeddings APIのトークン数の制限に引っかからないようにするためです。このAPIはリクエストあたりのトークン数に上限があり(たとえばtext-embedding-ada-002では8191トークン)、そのためテキストを細かく分割する(チャンク化する)必要があります。
また、RAGではLLMに対して、ユーザーの質問に加えて、外部データベースからドキュメントを取得し、そのドキュメントに基づいてユーザーの質問に回答してと指示を出します。こうすることで、ユーザーが保持する独自の情報をベースに回答できるようになります。
このときに、LLMに送るドキュメントのサイズにもまた限界があります。例えば、ものすごいページ数の多いドキュメントをいっぺんにLLMに送ってもエラーになります。つまり、APIのリクエストサイズにも制限があるのです。
ここでもドキュメントのチャンク化が有効になります。ドキュメントをチャンク化し、細かいサイズに分割し、ユーザーの質問に関連のある必要なドキュメントの断片だけをお送ることでAPIの制限を回避することができます。
またチャンク化する際にオーバーラップという処理を施します。チャンクのオーバーラップとは、テキストを小さな部分(チャンク)に分割する際に、隣接するチャンク間で一部のテキストが重複するようにすることです。
チャンクのオーバーラップにより、バラバラになった文章を関連のある一つの文章としてAIが認識できるようになります。これにより、テキストの意味が途中で切断されるのを防ぎ、チャンク間での文脈の連続性が保たれます。結果として、検索や自然言語処理の精度が向上し、AIによる回答生成がより正確かつ関連性の高いものとなります。オーバーラップは、テキストをチャンクに分割する際に重要な情報が欠落するのを防ぐと同時に、バラバラだった文章を一つの関連性のある文章として認識するのに役立ちます。このように、チャンクのオーバーラップは、テキストの処理と理解を効率的に行うための重要な手法となります。
イメージにしますと以下のようにドキュメントを分割します。それぞれのチャンクが重なっているところがオーバーラップしている部分です。
ナレッジ
先ほどRAGは外部データベースにドキュメントを格納することがキモだと説明しました。Difyは、そのドキュメントを「ナレッジ」という単位で論理的にグループ化します。
開発チームメンバーがドキュメントをファイル単位でDifyに登録します。そのドキュメントは、ナレッジという単位でグループ化され、チャットボットやワークフローなど様々なアプリから参照されます。
そして、先程のチャンク化の項でも説明しましたが、Difyもドキュメントをチャンク化し、ベクトルデータベースに登録しています。
具体例として、架空の会社ホゲホゲ株式会社の社内の就業規約を登録する例を上げてみましょう。ホゲホゲ株式会社は、社内規約を以下のカテゴリごとに単一のPDFファイルに分けているとします。
- 人事規程 (hr.pdf)
- 経理規程 (accounting.pdf)
- 情報システム関連規程 (it.pdf)
- 個人情報保護規程 (privacy.pdf)
- 品質保証関連規程 (quality.pdf)
以下の図のようなイメージでDifyにはナレッジとして登録されます。
RAGを作ってみよう
それでは、実際にRAGを作成してみましょう。
■ ナレッジの追加画面を開く
まずはナレッジを追加する必要があります。「ナレッジ」(①)→「ナレッジを作成」(②)の順にクリックします。
■ ドキュメントをアップロードする
ナレッジに追加するドキュメントをアップロードします。
以下のURLからダウンロードできる「モデル就業規則」のPDF版をダウンロードしてください。
「テキストファイルからインポート」をクリックします(①)。そして、ダウンロードしたPDFファイルを選択してアップロードします(②)。最後に「次へ」をクリックします(③)。
■ チャンクの設定を行う
チャンクの設定を行います。チャンクの区切り文字や、チャンクの大きさなど、チャンクをする際に必要な設定を行います。ここではデフォルトの設定として、「保存して処理」をクリックします。チャンクの設定の詳細については「第15章: チャンキング」を参照してください。
■ チャンクを行う
チャンクの処理が開始されると、「埋め込み処理中…」と表示されます。しばらく待つと、チャンクの処理が完了し、「埋め込み処理が完了しました」と表示されます。「ドキュメントに移動」をクリックします。
■ ドキュメントの追加完了を確認する
先ほど追加したドキュメントのステータスが「利用可能」となっていることを確認します。これで、ナレッジの追加は完了です。
■ アプリの種類選択画面を開く
Difyの管理画面トップにアクセスして、「最初から作成」をクリックします。
■ アプリの種類を選択する
どのアプリを作成するかを選択する画面が開きます。ここではチャットボットを作成したいので、「チャットボット」を選択します(①)。
「アプリのアイコンと名前」にはアプリの名前を入力します。ここでは「簡単なチャットボット」と入力します(②)。
「説明」はアプリの説明となります。任意で入力してください(③)。
最後に、「作成する」をクリックします(④)。
■ コンテキストの追加
RAGに必要なナレッジを追加します。「追加」をクリックします。
■ ナレッジを選択する
参照するナレッジを選択します。先程追加した「モデル就業規則」を選択します(①)。そして、「追加」をクリックします(②)。
■ 動作確認を行う
これでRAGが完成しました。質問を入力して、回答を確認してみましょう。
質問に対して、適切な回答が返ってくることが確認できました。
ファイル名のところをクリックしてみましょう。この回答を作成するために参照したチャンクが表示されます。
このチャンクを確認することで、チャットボットの回答の根拠を確認することができます。
■ アプリを更新する
今まで設定を行ってきた画面は、開発者がアプリを作成するための画面です。この画面で行った設定をアプリに更新する必要があります。
「公開する」(①)→「更新」(②)をクリックします。こうすると、修正内容がアプリに反映されます。
■ アプリを実行する
動作確認するためにアプリを実行します。「アプリを実行」をクリックします。
■ アプリの動作確認をする
アプリが起動し、チャットボットが表示されます。質問を入力して、回答を確認してみましょう。
利用者に使ってもらうためには、このアプリのURLを配布して、アクセスしてもらいます。
以上で、RAGの作成は完了です。とっても簡単にRAGを作成することができましたね。
第10章: ワークフローを作ってみよう
ここからは、ワークフローを作成していきます。Difyのワークフローを使うと、本来Pythonなどのプログラミング言語で作成しなければいけなかったLLMアプリケーションをノーコードで作成することができます。
これはまさにDifyの強みであり、Difyによる「AIの民主化」に資するものです。プログラミングができない人でも、Difyを使ってAIを活用することができるのです。今までLLMは一部の専門家のものでしたが、Difyによって一般の人々も使うことができるようになりました。
以降では、Difyのワークフローについて、その基礎概念から詳しく説明します。
ワークフローの基本
Difyに限ったことではないですが、ワークフローは一般的に、入力・処理・出力の3つの要素から構成されます。Difyのワークフローも同様で、入力値を受け取り、処理を行い、出力値を返すという流れになります。図解すると以下のようになります。
入力と出力は一般的なワークフローと同等です。入力では、まずこのワークフローに最初に入力する値を定義し、出力では最終結果を出力します。
処理の部分は、Difyには「ブロック」「ツール」の2つがあります.
ブロックは、ワークフローの中で、処理を行うための基本的な要素です。たとえば、条件分岐や繰り返し処理などがあります。他にもOpenAIなどのLLMへの接続を行うブロック、Pythonのコードを実行するブロックなどがあります。
ツールは、インターネット検索やQRコード生成といった、特定の処理を行うためのツールです。他にも株価検索や天気予報取得など、様々なツールが用意されています。
ツールとブロックは順番関係なくつなげていくこともできますし、2つ以上の複数のブロックやツールをつなげることもできます。また、ブロック/ツールを並行に処理させることもできます。
これから作るワークフローの概要
本章で作成するワークフローの概要について説明します。
企業内には様々な契約書があります。契約書にはもちろん様々な契約事項が書いてあり、その中には契約満了期間といった、期日に関わる重要な事項もあります。契約満了期間を把握しておくことは、契約の管理において非常に重要です。ましてや契約期間が切れているなんてもってのほかです。しかし契約書の中には相当大量の契約事項が書かれているものもあり、その中から契約期限切れのおそれがある部分を探すのは大変です。
そこで、DifyのワークフローによるLLMアプリの出番です。そんな課題を解決すべく、以下のワークフローを作成します。イメージが付きやすいようにいきなり完成形をお見せします。
このワークフローは、企業内に散在するPDF形式の契約書から、そのテキストを抜き出し、LLMで解析させて、契約書に内在する契約満了期間のリスクを洗い出します。LLMは現在の時間はわからないので、現在時刻を取得するツールを使って現在時刻を取得し、それをLLMに投げます。LLMは契約書のテキストと現在時刻を入力として、契約満了期間のリスクを洗い出します。最後に契約満了期間のリスクを出力します。
このワークフローは以下のパーツで構成されています。
- 開始: ワークフローの開始地点です。契約書のPDFファイルが入力されます。
- ツール(CURRENT TIME): 現在時刻を取得します。契約書内の契約満了期間と現在時刻を比較するために必要です。
- ブロック(テキスト抽出ツール): 契約書のPDFファイルからテキストを抽出します。
- ブロック(LLM): 現在時刻と契約書のテキストを入力として、契約満了期間のリスクを洗い出します。
- 終了: ワークフローの終了地点です。契約満了期間のリスクが出力されます。
処理の流れは以下の通りとなります。
1. 契約書のPDFファイルを入力として受け取ります。
2. ツール(CURRENT TIME)で現在時刻を取得します。LLMは現在の時刻を取得することができないため、このツールを使って現在時刻を取得します。
3. ブロック(テキスト抽出ツール)で契約書のPDFファイルからテキストを抽出します。
4. ブロック(LLM)で契約書のテキストと現在時刻を入力として、契約満了期間のリスクを洗い出します。以下のようなプロンプトを作成して、LLMに投げます。
現在の日付は{CURRENT TIMEで取得した現在時刻}です。以下の契約書について、契約期間の満了が迫っている場合や、期間に関するリスクが存在する場合は、それらを特定し、具体的に列挙してください。
{テキスト抽出ツールで抽出した契約書のテキスト}
5. 終了で契約満了期間のリスクが出力されます。
今回のワークフローで使うPDFは以下のURLからダウンロードできます。
「第2条(契約期間)」には契約期間が記載されています。この契約期間が満了する日付を洗い出し、リスクを明確化することがこのワークフローの目的です。
ワークフローの構成
今回作成するワークフローの構成を説明します。
まず、「開始」では、契約書のPDFファイルを入力として受け取ります。
「CURRENT TIME(ツール)」「テキスト抽出ツール(ブロック)」が並行して処理されます。まず、「CURRENT TIME(ツール))」で現在時刻を取得します。次に、「テキスト抽出ツール(ブロック)」で契約書のPDFファイルからテキストを抽出します。とてもわかりにくいのですが、「テキスト抽出ツール」は、ツールと言う名前がありますが、実際にはブロックです。
「LLM(ブロック)」では、契約書のテキストと現在時刻を入力として、契約満了期間のリスクを洗い出します。具体的には、「CURRENT TIME(ツール)」「テキスト抽出ツール(ブロック)」の処理結果を以下のようにプロンプトに埋め込んで、LLMに投げます。
現在の日付は{CURRENT TIMEで取得した現在時刻}です。以下の契約書について、契約期間の満了が迫っている場合や、期間に関するリスクが存在する場合は、それらを特定し、具体的に列挙してください。
{テキスト抽出ツールで抽出した契約書のテキスト}
{CURRENT TIMEで取得した現在時刻}の部分は、CURRENT TIME(ツール)の処理結果を埋め込んでいます。{テキスト抽出ツールで抽出した契約書のテキスト}の部分は、テキスト抽出ツール(ブロック)の処理結果を埋め込んでいます。
「CURRENT TIME(ツール)」「テキスト抽出ツール(ブロック)」によって取得した値が埋め込まれたプロンプトは以下のようになります。
現在の日付は2022-01-01です。以下の契約書について、契約期間の満了が迫っている場合や、期間に関するリスクが存在する場合は、それらを特定し、具体的に列挙してください。
契約書
第1条(契約の目的)
本契約は、○○株式会社(以下「甲」という)と△△株式会社(以下「乙」という)との間で、XXXXXに関する契約の条件を定めるものである。
...以下省略...
ワークフローの作成
では、実際にワークフローを作成していきましょう。
■ アプリの種類選択画面を開く
「最初から作成」をクリックします。
■ アプリの種類を選択する
どのアプリを作成するかを選択する画面が開きます。ここではワークフローを作成したいので、「ワークフロー」を選択します(①)。
「アプリのアイコンと名前」にはアプリの名前を入力します。ここでは「契約書チェック」と入力します(②)。
「説明」はアプリの説明となります。任意で入力してください(③)。
最後に、「作成する」をクリックします(④)。
■ 入力フィールド追加画面を開く
ワークフローの入力フィールドを追加するための画面を開きます。「追加」をクリックします。
■ 入力フィールドを追加する
ワークフローの最初の入力となる契約書のファイルを入力するフィールドを追加します。以下のように入力して、最後に「保存」(⑤)をクリックします。
– ①フィールドタイプ: 単一ファイル
入力の形式を選択します。単一のテキストや、ラジオボタンのような選択形式、複数行のテキスト等様々な入力形式があります。ここでは契約書のファイルを一つだけアップロードするので、「単一ファイル」を選択します。
– ②フィールド名: file
アップロードした契約書のファイルの内容が格納される変数名になります。後のブロックやツールなどで使います。
– ③ラベル名: ファイル
フォームに表示されるラベル名です。ここでは「ファイル」と入力します。
– ④サポートされたファイルタイプ: ドキュメント
どの種別のファイルアップロードを許可するかを設定します。画像、音声等様々なファイルがアップロード可能ですが、ここではPDFをアップロードするので「ドキュメント」を選択します。
– ⑤ アップロードされたファイルのタイプ: ローカルアップロード
ファイルのアップロード方法を選択します。ローカルアップロードは、自分のPCからファイルをアップロードする方法です。URLからアップロードする方法もあります。ここではローカルアップロードを選択します。
– ⑥ 必須: チェックを入れる
ファイルのアップロードを必須にするかどうかを設定します。ここでは契約書のファイルをアップロードしないとワークフローが進められないので、チェックを入れます。
■ テキスト抽出ツールを追加する
テキスト抽出ツールのブロックを追加します。「開始」の右側にある「+」をクリックします(①)。様々なブロックやツールが表示されます。各ブロックやツールの詳細は後ほど説明しますが、ここでは「テキスト抽出ツール」を選択します(②)。
■ テキスト抽出ツールの設定を行う
テキスト抽出ツールの設定を行います。まず入力変数の設定を行います。入力変数はこのブロックに入力される値です。Difyのワークフローではこのブロックに限らず、すべてのブロックやツールはその入力となる値をいれる必要があります。これは当然で、何らかの処理を行う場合、基本的に入力→処理→出力の流れになるためです。
ここでは、「開始」の部分で設定した「file」を入力変数に設定します。こうすると、開始の部分で選択した契約書のファイルがこのブロックに入力されることになります。
■ テキスト抽出ツールの設定内容を確認する
下図のように表示されればOKです。これは「開始」で設定した「file」という変数がこのブロックに入力されることを示しています。
■ CURRENT TIME(現在時刻を取得するツール)を追加する
CURRENT TIME(現在時刻を取得するツール)を追加するために、「開始」の右側にある「+」をクリックします(①)。「ツール」をクリックすると(②)、ツールの一覧が表示されますので、検索ボックスに「current」と入力して(③)、「CURRENT TIME」を選択します(④)。
■ CURRENT TIME(現在時刻を取得するツール)の設定を行う
ツールを追加したあとは、そのツールの設定を行います。これは各ツールごとに設定内容が異なります。「CURRENT TIME」の場合は以下になります。
① FORMAT: %Y-%m-%d %H:%M:%S
現在時刻のフォーマットを指定します。ここでは「年-月-日 時:分:秒」のフォーマットを指定します。
② TIMEZONE: Asia/Tokyo
現在時刻のタイムゾーンを指定します。ここでは日本時間を指定します。このタイムゾーンを正確に指定しないと、正しい現在時刻を取得できません。UTCのままだと日本時間から9時間ズレた値となりますので、ご注意ください。
■ LLM(ブロック)を追加する
契約書の内容と現在時刻を入力として、契約満了期間のリスクを洗い出すLLMのブロックを追加します。「テキスト抽出ツール」のブロックの右側にある「+」をクリックします(①)。「LLM」のブロックを選択します(②)。
■ CURRENT TIME(ツール)とLLM(ブロック)を接続する
「CURRENT TIME(ツール)」と「LLM(ブロック)」を接続します。これは「CURRENT TIME(ツール)」の処理結果を「LLM(ブロック)」に入力として渡すための接続です。「CURRENT TIME(ツール)」の右側にある「+」をクリックして(①)、そのままドラッグして、「LLM(ブロック)」に接続します(②)。
以下のようになればOKです。
■ LLM(ブロック)の設定を行う
LLM(ブロック)の設定を行います。まず、LLMのブロックにはそれを処理するモデルの指定が必要です。ここでは、「チャットボット」の章で作成したモデルを指定します(①)。
次にプロンプトを設定します。「+メッセージを追加」をクリックします(②)。
■ プロンプトの設定を行う
LLMに投げるプロンプトを設定します。プロンプトは、LLMに投げる質問のようなものです。ここでは以下のように設定します。
現在の日付はです。以下の契約書について、契約期間の満了が迫っている場合や、期間に関するリスクが存在する場合は、それらを特定し、具体的に列挙してください。
現在時刻や契約書のテキストを埋め込む部分が設定されていませんが、気にしないでください。次のステップで設定します。
■ 現在時刻をプロンプトに埋め込む
先ほど追加したプロンプトの「現在の日付は」の後に、キーボードで「/」(スラッシュ)を入力します。すると、変数の一覧が表示されます。ここから「CURRENT TIME」の「text」を選択します(①)。これは「CURRENT TIME(ツール)」のtextという変数に現在時刻が格納されており、それをプロンプトに埋め込むための作業です。
以下のようになればOKです。
■ 契約書のテキストをプロンプトに埋め込む
同様に、テキスト抽出ツールのブロックで抽出した契約書のテキストをプロンプトに埋め込みます。プロンプトの末尾で「/」(スラッシュ)を入力します(①)。すると、変数の一覧が表示されます。ここから「テキスト抽出ツール」の「text」を選択します(②)。
以下のようになればOKです。
■ 終了を追加する
最後に「終了」を追加します。これを追加しないと、ワークフローの最終結果が出力されません。LLMのブロックの右側にある「+」をクリックして(①)、「終了」を選択します(②)。
■ 終了の設定を行う
出力変数を設定します。出力変数はワークフローの最終結果となる値です。変数名は「result」とします(①)。そして、このresultに格納される値を設定します。ここでは「LLM(ブロック)」の「text」を設定します(②)。そうすると、LLMのブロックで洗い出した契約満了期間のリスクがresultに格納されます。
以下のようになればOKです。
■ ワークフローを実行する
これでワークフローは完成です。では、ワークフローを実行します。「実行」をクリックすると(①)、ワークフローを開始するために必要な入力フォームが表示されます。「開始」の部分で定義したように、契約書のPDFファイルをアップロードするためのフォームが表示されます。「ローカルアップロード」をクリックして、契約書のPDFファイルをアップロードします(②)。
以下のようになればOKです。「実行を開始」をクリックします。
■ 結果を確認する
ワークフローの実行結果を確認します。契約書の内容を見て、現在日時と比較して、契約満了日が迫っているものを洗い出して、教えてくれています。
■ トレースを確認する
開始や終了、各ブロックやツールの詳細な処理内容を確認することができます。それぞれのブロックやツールをクリックすると入力内容や出力結果が表示されます。詳細な処理内容を確認したいときや、ワークフローがうまく動かないときに使います。
これでワークフローの作成は完了です。Difyは、複雑なLLMアプリケーションをノーコードで作成することができるため、非常に便利です。これにより、プログラミングができない人でも、AIを活用することができるようになります。
第11章: チャットフローを作ってみよう
本章では、チャットフローを作ります。チャットボットとの違いは、複雑なチャットボットを作れることです。
下図のようにチャットボットは、ナレッジを参照して答えるだけの単純なチャットボットです。一方でチャットフローは、例えば質問の内容を解析して、その内容に沿ったナレッジを参照させる等、高度な処理を行うことができます。その高度な処理は、ワークフローのように複数のブロックやツールを組み合わせて作ることができます。
つまり、チャットフローは、チャットボットとワークフローを組み合わせたものと言えます。
これから作るチャットフローの概要
これから作るチャットフローの概要を説明します。このチャットフローは、質問の内容に応じて、それぞれナレッジの参照先を変えるチャットフローです。自社製品に関するナレッジと自社製品に関するナレッジがあり、ユーザーの質問の内容をLLMが解析して、その質問に応じた適切なナレッジを参照して回答するRAGです。質問に関連したナレッジのみを参照することにより、回答を生成するための情報に余計なノイズが入り込むことを防止し、回答精度を向上させることができます。
まずは、「質問分類器」というブロックによって質問の内容をLLMが解析します。その結果、質問の内容を「自社製品に関する質問」「福利厚生に関する質問」「その他」にカテゴライズします。そのカテゴリに応じて、それぞれのナレッジを参照して回答を生成します。
「自社製品に関する質問」の場合は、「知識取得」というブロックに遷移し、自社製品に関するナレッジを参照します。次にLLMのブロックによって、質問の内容とナレッジを入力として、回答を生成します。最後に「回答」のブロックによって、回答を出力します。この回答のブロックがないと、チャットフローはユーザーに回答を出力できません。
「福利厚生に関する質問」の場合は、参照するナレッジが福利厚生になるだけで、それ以外の処理は「自社製品に関する質問」と同じです。
「その他」の場合は、「回答」のブロックで、「回答ができません」という固定の回答を返すようにします。
ナレッジの作成
まずは、ナレッジを作成します。ナレッジは、チャットフローの中で参照される情報です。自社製品に関するナレッジと福利厚生に関するナレッジを作成します。
■ ナレッジの作成画面を開く
ナレッジを作成するための画面を開きます。「ナレッジ」をクリックし(①)、「ナレッジを作成」をクリックします(②)。
■ ドキュメントをアップロードする
ナレッジに追加するドキュメントをアップロードします。
以下のリンクにて、自社製品に関するナレッジのPDFファイルをダウンロードしてください。こちらは私が作成した架空の自社製品に関するナレッジです。
product
「テキストファイルからインポート」をクリックします(①)。そして、ダウンロードしたPDFファイルを選択してアップロードします(②)。最後に「次へ」をクリックします(③)。
■ チャンクの設定を行う
チャンクの設定を行います。チャンクの区切り文字や、チャンクの大きさなど、チャンクをする際に必要な設定を行います。ここではデフォルトの設定として、「保存して処理」をクリックします。チャンクの設定の詳細については「第15章:チャンキング」を参照してください。
■ チャンクを行う
チャンクの処理が開始されると、「埋め込み処理中…」と表示されます。しばらく待つと、チャンクの処理が完了し、「埋め込み処理が完了しました」と表示されます。「ドキュメントに移動」をクリックします。
■ ドキュメントの追加完了を確認する
先ほど追加したドキュメントのステータスが「利用可能」となっていることを確認します。これで、ナレッジの追加は完了です。
■ ナレッジの追加を繰り返す
同様の手順で、福利厚生に関するナレッジのPDFファイルをアップロードして、ナレッジを追加します。
以下のリンクにて、福利厚生に関するナレッジのPDFファイルをダウンロードしてください。こちらは私が作成した架空の福利厚生に関するナレッジです。
benefits
チャットフローの作成
それでは、チャットフローを作成していきましょう。
■ アプリの種類選択画面を開く
「最初から作成」をクリックします。
■ アプリの種類を選択する
どのアプリを作成するかを選択する画面が開きます。ここではチャットフローを作成したいので、「チャットフロー」を選択します(①)。
「アプリのアイコンと名前」にはアプリの名前を入力します。ここでは「賢いチャットフロー」と入力します(②)。
「説明」はアプリの説明となります。任意で入力してください(③)。
最後に、「作成する」をクリックします(④)。
■ 「開始」以外のブロックを削除する
デフォルトでは、LLMや回答などのブロックがありますが、今回はゼロから作成したいため、これらのブロックを削除します。削除するには、各ブロックの右上にある「…」をクリックして(①)、「削除」をクリックします(②)。
■ 質問分類器を追加する
まずは質問分類器を追加します。「開始」の右側にある「+」をクリックして(①)、「質問分類器」をクリックします(②)。
■ 質問分類器のモデルを選択する
質問分類器のモデルを選択します。「モデル」をクリックすると(①)、別のウィンドウがポップアップします。「モデル」をクリックすると(②)、モデルの一覧が表示されます。ここでは「チャットボット」の章で作成したモデルを選択します(③)。
■ 質問分類器の入力変数を設定する
質問分類器の入力変数が「sys.query」に設定します。おそらくデフォルトでsys.queryが設定されてると思います。これはユーザーが入力した質問の内容が格納される変数です。チャットフローでは固定で定義されているシステム変数となります。
■ クラスの設定
質問を分類分けするためのクラスを設定します。このクラスで設定した説明をもとに質問を分類します。ここでは「自社製品に関する質問」「福利厚生に関する質問」「その他」の3つのクラスを設定します。
「+ クラスを追加」をクリックして(①)、3つのクラスを表示させます。それぞれのクラスに対して、「自社製品に関する質問」「福利厚生に関する質問」「その他の質問」を入力します(②)。
そして、設定したクラスごとに、次に遷移するブロックを定義します。
■ 自社製品に関する知識取得ブロックを追加する
先の質問分類器で自社製品に関する質問がされた場合に、自社製品に関するナレッジを参照するための知識取得ブロックを追加します。
知識取得ブロックとは、ナレッジを参照するためのブロックです。質問を入力して、その質問でナレッジに対して検索を行い、検索結果を出力します。
質問分類器のクラス1の右側にある「+」をクリックして(①)、「知識取得」をクリックします(②)。
■ 自社製品に関する知識取得ブロックの設定を行う
「クエリ変数」が「sys.query」になっていることを確認します。これはユーザーが入力した質問の内容が格納される変数です。ここで定義した変数によって、次に設定するナレッジに対して検索が行われます。
次にナレッジを追加するために、「+」をクリックします(①)。
■ 自社製品に関するナレッジを選択する
先ほど追加した自社製品に関するナレッジを選択します(①)。そして、「追加」をクリックします(②)。
■ 自社製品への質問に対する回答を生成するLLMブロックを追加する
自社製品に関するナレッジを参照して、質問に対する回答を生成するLLMブロックを追加します。先ほど作成した知識取得ブロックの右側にある「+」をクリックして(①)、「LLM」をクリックします(②)。
■ LLMブロック(自社製品への質問に対する回答生成用)のモデルを設定する
LLMブロックのモデルを設定するのモデルを選択します。「モデル」をクリックすると(①)、別のウィンドウがポップアップします。「モデル」をクリックすると(②)、モデルの一覧が表示されます。ここでは「チャットボット」の章で作成したモデルを選択します(③)。
■ LLMブロック(自社製品への質問に対する回答生成用)のコンテキストを設定する
LLMブロックのコンテキストを設定します。コンテキストとは、LLMが回答を生成する際に参照する情報です。ここでは、先ほど追加した知識取得ブロックの出力結果を格納する変数であるresultを設定します。
■ LLMブロック(自社製品への質問に対する回答生成用)のプロンプトを設定する
LLMに投げるプロンプトを設定します。システムメッセージは、「あなたは製品情報に答えるチャットボットです。」と入力します(①)。
ユーザーメッセージは以下のように入力します。以下のようにプロンプトを設定することで、LLMの回答を生成する際に、知識取得ブロックで検索した内容から得られる情報に基づいて回答を生成することができます。いわゆるRAG(Retrieve and Generate)の手法です。ただし、肝心の「#質問」「#取得した情報」の中身の部分は、まだ未設定なのでこれから設定します。
「#質問」に書かれている質問を「#取得した情報」に基づいて回答してください。
#質問
#取得した情報
■ LLMブロック(自社製品への質問に対する回答生成用)のプロンプトに「#質問」を埋め込む
「#質問」の内容を埋め込みます。「#質問」の後に「/」(スラッシュ)を入力します(①)。すると、変数の一覧が表示されます。ここから「sys.query」を選択します(②)。この変数はユーザーが入力した質問の内容が格納される変数です。チャットフローでは固定で定義されているシステム変数となります。
■ LLMブロック(自社製品への質問に対する回答生成用)のプロンプトに「#取得した情報」を埋め込む
「#取得した情報」の内容を埋め込みます。「#取得した情報」の後に「/」(スラッシュ)を入力します(①)。すると、変数の一覧が表示されます。ここから「コンテキスト」を選択します(②)。これは先程設定したコンテキストを示すものであり、コンテキストは知識取得ブロックで検索した内容が格納される変数です。
■ LLMブロック(自社製品への質問に対する回答生成用)のプロンプトの設定を確認する
以下のようになればOKです。
■ 回答ブロックを追加する
LLMブロックで回答を生成した後、その回答を出力するための回答ブロックを追加します。この回答ブロックを作成しないと、チャットで回答が返ってきませんので、ご注意ください。LLMブロックの右側にある「+」をクリックして(①)、「回答」をクリックします(②)。
■ 回答ブロックの設定を行う
回答ブロックの設定を行います。「回答」というフィールドに、チャットに出力させたい回答を定義します。「/」(スラッシュ)を入力します(①)。すると、変数の一覧が表示されます。ここから「LLM」の「text」を選択します(②)。これはLLMブロックで生成した回答が格納される変数です。
■ 回答ブロックの設定を確認する
以下のようになればOKです。
■ 福利厚生に関する知識取得ブロックを追加する
次に、福利厚生に関する知識取得ブロックを追加します。質問分類器のクラス2の右側にある「+」をクリックして(①)、「知識取得」をクリックします(②)。
■ 福利厚生に関する知譆取得ブロックの設定を行う
「クエリ変数」が「sys.query」になっていることを確認します。これはユーザーが入力した質問の内容が格納される変数です。ここで定義した変数によって、次に設定するナレッジに対して検索が行われます。
次にナレッジを追加するために、「+」をクリックします(①)。
■ 福利厚生に関するナレッジを選択する
先ほど追加した福利厚生に関するナレッジを選択します(①)。そして、「追加」をクリックします(②)。
■ 福利厚生への質問に対する回答を生成するLLMブロックを追加する
福利厚生に関するナレッジを参照して、質問に対する回答を生成するLLMブロックを追加します。先ほど作成した知識取得ブロックの右側にある「+」をクリックして(①)、「LLM」をクリックします(②)。
■ LLMブロック(福利厚生への質問に対する回答生成用)のモデルを設定する
LLMブロックのモデルを設定するのモデルを選択します。「モデル」をクリックすると(①)、別のウィンドウがポップアップします。「モデル」をクリックすると(②)、モデルの一覧が表示されます。ここでは「チャットボット」の章で作成したモデルを選択します(③)。
■ LLMブロック(福利厚生への質問に対する回答生成用)のコンテキストを設定する
LLMブロックのコンテキストを設定します。コンテキストとは、LLMが回答を生成する際に参照する情報です。ここでは、先ほど追加した知識取得ブロックの出力結果を格納する変数であるresultを設定します。
■ LLMブロック(福利厚生への質問に対する回答生成用)のプロンプトを設定する
LLMに投げるプロンプトを設定します。システムメッセージは、「あなたは福利厚生に答えるチャットボットです。」と入力します(①)。
ユーザーメッセージは以下のように入力します。以下のようにプロンプトを設定することで、LLMの回答を生成する際に、知識取得ブロックで検索した内容から得られる情報に基づいて回答を生成することができます。いわゆるRAG(Retrieve and Generate)の手法です。ただし、肝心の「#質問」「#取得した情報」の中身の部分は、まだ未設定なのでこれから設定します。
「#質問」に書かれている質問を「#取得した情報」に基づいて回答してください。
#質問
#取得した情報

■ LLMブロック(福利厚生への質問に対する回答生成用)のプロンプトに「#質問」を埋め込む
「#質問」の内容を埋め込みます。「#質問」の後に「/」(スラッシュ)を入力します(①)。すると、変数の一覧が表示されます。ここから「sys.query」を選択します(②)。これはユーザーが入力した質問の内容が格納される変数です。チャットフローでは固定で定義されているシステム変数となります。
■ LLMブロック(福利厚生への質問に対する回答生成用)のプロンプトに「#取得した情報」を埋め込む
「#取得した情報」の内容を埋め込みます。「#取得した情報」の後に「/」(スラッシュ)を入力します(①)。すると、変数の一覧が表示されます。ここから「コンテキスト」を選択します(②)。これは先程設定したコンテキストを示すものであり、コンテキストは知識取得ブロックで検索した内容が格納される変数です。
■ LLMブロック(福利厚生への質問に対する回答生成用)のプロンプトの設定を確認する
以下のようになればOKです。
■ 回答ブロックを追加する
LLMブロックで回答を生成した後、その回答を出力するための回答ブロックを追加します。この回答ブロックを作成しないと、チャットで回答が返ってきませんので、ご注意ください。LLMブロックの右側にある「+」をクリックして(①)、「回答」をクリックします(②)。
■ 回答ブロックの設定を行う
回答ブロックの設定を行います。「回答」というフィールドに、チャットに出力させたい回答を定義します。「/」(スラッシュ)を入力します(①)。すると、変数の一覧が表示されます。ここから「LLM」の「text」を選択します(②)。これはLLMブロックで生成した回答が格納される変数です。
■ 回答ブロックの設定を確認する
以下のようになればOKです。
■ その他の質問に対する回答を生成する
最後に、その他の質問に対しては、「回答ができません」という固定の回答を返すようにします。「その他の質問」のクラスの右側にある「+」をクリックして(①)、「回答」をクリックします(②)。
■ その他の質問に対する回答を設定する
回答ブロックの設定を行います。「回答」というフィールドに、「回答ができません」と入力します。
■ チャットフロー全体を確認する
これでチャットフローの作成は完了です。全体を確認してみましょう。
■ 動作確認: 自社製品に関する質問
それでは、チャットフローを動作確認してみましょう。まずは、自社製品に関する質問を入力してみます。
自社製品に関するナレッジに基づいて回答しているのがわかります(①)。また、「ログを表示」をクリックして(②)、トレースをクリックしてみると(③)、質問分類器で「自社製品に関する質問」と判定されていることがわかります(④)。その後の流れを見てみると、確かに自社製品に関するナレッジを参照して回答を生成していることがわかります(⑤)。
■ 動作確認: 福利厚生に関する質問
次に、福利厚生に関する質問を入力してみます。福利厚生に関するナレッジに基づいて回答しているのがわかります(①)。また、「ログを表示」をクリックして(②)、トレースをクリックしてみると(③)、質問分類器で「福利厚生に関する質問」と判定されていることがわかります(④)。その後の流れを見てみると、確かに福利厚生に関するナレッジを参照して回答を生成していることがわかります(⑤)。
■ 動作確認: その他の質問
最後に、その他の質問を入力してみます。その他の質問に対しては、「回答ができません」という固定の回答を返しているのがわかります(①)。また、「ログを表示」をクリックして(②)、トレースをクリックしてみると(③)、質問分類器で「その他」と判定されていることがわかります(④)。その後の流れを見てみると、確かにその他の質問に対しては固定の回答を返していることがわかります(⑤)。
第12章: AIエージェントを作ってみよう
本章では、Difyを使ってAIエージェントを作成する方法を解説します。
AIエージェントとは?
まずAIエージェントの概要をお伝えします。AIエージェントとは、ユーザーの指示に基づいて適切なツールを選び、処理の流れを自動で組み立てて実行するAI です。従来のアプリケーションでは、開発者があらかじめ「何をどう処理するか」をプログラムする必要がありましたが、AIエージェントはプロンプト(指示文)とツール を与えるだけで、最適な方法を自ら考えて実行します。
例えば「このデータを調査して、要点をまとめて報告して」という指示を出すと、
- まずインターネットで検索
- 次に関連する情報を整理
- さらに結果を要約
- 最後にレポートとして出力
といった一連の流れをAIが自動で判断して実行します。従来のアプリでは開発者がロジックを組まなければならなかった部分をAIが担うため、より柔軟で手軽に高度な処理を行うことが可能になります。
わかりやすく伝えるため、AIにエージェントがない世界(従来型のアプリケーションの場合)と、AIエージェントがある世界を比較してみましょう。
AIエージェントがない世界
先程の図の「従来型のアプリケーション」の部分を見てみます。従来型のアプリケーションでは、UI設計と開発の手間が生じます。従来のアプリでは、ユーザーが操作するためのUI(ユーザーインターフェース)を設計し、実装しなければなりません。例えば、ユーザーが検索ワードを入力し、「QRコードを作成する」オプションを選べるようなUIを作成し、検索結果を表示する画面も用意する必要があります。
また、もし新しい機能(例えば「結果をPDFに変換する」機能)を追加したい場合、UIに新しいチェックボックスやボタンを増やし、それに対応する処理を実装しなければなりません。つまり、新しい機能を追加するたびにUIの変更が必要になり、開発コストが増加します。
処理の流れも明示的に定義しなければなりません。ユーザーが検索ワードを入力し、検索実行ボタンを押した後に、
1. インターネット検索関数を呼び出す
2. 結果を取得
3. QRコードを作成する(オプション)
という 明確な処理の流れをプログラムとして定義し、開発者がコードを書かなければなりません。
開発者が関数の呼び出し順やデータの流れをすべて設計し、実装する必要があるため、開発負担が大きくなります。
AIエージェントがある世界
先程の図の「AIエージェントの場合」の部分を見てみます。AIエージェントを使うと、ユーザーは「RAGの情報がほしい」と自然言語で指示するだけ でOK。特定のUIを設計しなくても、AIが適切な処理を組み立てて実行してくれます。
まず、AIエージェントを使うと、ユーザーは 「RAGの情報がほしい」と自然言語で指示するだけでOKです。特定のUIを設計しなくても、AIが適切な処理を組み立てて実行 してくれます。
また、機能追加のたびにUIを変更する必要がありません。新しい機能(例えば「URLのQRコードを作成する」)を追加したい場合、プロンプトを「URLのQRコードを作成してください。」と書くだけで対応可能 です。開発者は新しいUIを作成する必要がなく、AIエージェントに新しいツールを追加するだけで済みます。
また、処理の流れもAIが決定します。AIエージェントは、
1. どの関数を呼び出すか(インターネット検索 → QRコード作成)
2. どの順番で処理を実行するか(検索してからQRコード作成)
をLLM(大規模言語モデル)が自動で判断してくれます。開発者は個別の処理の流れを実装する必要がなく、ツールとプロンプトを用意するだけで良いのです。
つまり、AIエージェントを活用すると、従来のような「UI設計」「処理の順番の決定」「機能追加に伴うUI改修」といった開発の手間がなくなり、柔軟で拡張性の高いアプリを簡単に作れるようになります。
AIエージェントの作成
では、早速AIエージェントを作成してみましょう。AIエージェントに依頼された情報をインターネットで検索し、情報検索元のサイトのURLのQRコードを表示します。
動作イメージは以下のとおりです。
■ アプリの種類選択画面を開く
「最初から作成」をクリックします。
■ アプリの種類を選択する
どのアプリを作成するかを選択する画面が開きます。ここではチャットフローを作成したいので、「エージェント」を選択します(①)。
「アプリのアイコンと名前」にはアプリの名前を入力します。ここでは「情報検索エージェント」と入力します(②)。
「説明」はアプリの説明となります。任意で入力してください(③)。
最後に、「作成する」をクリックします(④)。
■ プロンプトを設定する
AIエージェントに与えるプロンプトを設定します。このプロンプトはAIエージェントの振る舞いを決定する重要な要素です。以下のように入力します。
「〜の情報がほしい」といったような検索依頼があったら、以下のことを実施してください。
- 依頼された内容に関する情報をインターネットで検索する。
- 情報提供元のサイトのURLのQRコードを作成する。
■ Google Search APIのサインアップ画面を開く
Google Search APIを利用してインターネット検索を行います。Google Search APIを利用するためには、サインアップしてAPIキーを取得する必要があります。
以下のURLにアクセスします。
https://www.searchapi.io/
「Get 100 Free Reqests」をクリックします。
■ サインアップする
サインアップの画面が表示されます。必要な項目を入力してサインアップします。
■ メールを確認する
サインアップ後、登録したメールアドレスに確認メールが届きます。メール内のリンク(Confirm my acount)をクリックして登録を完了します。
■ APIキーを取得する
Google serach APIのURL(https://www.searchapi.io/)にアクセスし、APIキーを取得します。「Dashboard」をクリックして(①)、APIキーをコピーします(②)。
■ ツールの追加画面を開く
インターネットを検索するためのツール「Google Search API」を追加します。ツールの追加画面を開くために、「+ 追加」をクリックします。
■ Google Search APIを追加する
「Search」をクリックして(①)、「Google Search API」の隣りにある「+追加」をクリックします(②)。
■ APIキーを設定する
APIキーを設定します。先程取得したAPIキーを入力して(①)、「保存」をクリックします(②)。
■ Generate QR Codeを追加する
QRコードを生成するためのツール「Generate QR Code」を追加します。「Utilities」をクリックして(①)、「Generate QR Code」の隣りにある「+追加」をクリックします(②)。
■ ツールの追加を確認する
以下のようになればOKです。
■ AIエージェントに指示をする
これでAIエージェントは完了したので、AIエージェントに指示をしてみましょう。デバックとプレビューの画面で以下のように入力します。
■ 結果を確認する
インターネットを検索した結果と、その検索元のサイトのURLのQRコードが表示されていることがわかります。
どうでしょうか?Difyを使ってAIエージェントを作成することができました。AIエージェントを使うことで、従来のアプリケーション開発よりも柔軟で拡張性の高いアプリを簡単に作成することができます。さらにDifyを使えば、AIエージェントの作成も簡単に行うことができます。ぜひ、Difyを使ってAIエージェントを作成してみてください。
第13章: AIエージェントに独自機能を追加してみよう
先ほど作成したAIエージェントに機能を追加してみましょう。先ほどはDifyに標準で提供されているツールを使って、インターネットを検索して情報を取得し、QRコードを生成するという処理を行いました。本来であれば、AIエージェントに与えるツールの追加はプログミングによってゼロから開発しなければいけませんが、Difyが標準で提供しているツールを使えば、ノーコードでツールを利用でき、AIエージェントがサクッと作れます。
しかしながら、標準で提供されているツールでは対応しきれないことが少なからずあります。
例えば、社内のシステムと連携して特定の処理を行いたいという場合もあるでしょう。社内にあるデータベース内の情報をAIエージェントに与えたいという場合です。「XXXの情報を検索して」とAIエージェントに伝えたら、インターネットへの情報検索に加えて、社内のデータベースを検索して独自ナレッジを取得できたら更に便利です。Difyの標準のツールで提供されていないSaaSサービスにアクセスしたいという場合もあると思います。例えば、ある特定のWordPressのサイトの記事を検索できたら便利です。
Difyにはそのようなニーズを実現するための機能が用意されています。それが「カスタムツール」です。
カスタムツールとは?
カスタムツールとは、Difyの標準ツールにはない機能を追加するためのツールです。カスタムツールを使うことで、AIエージェントに独自の機能を追加することができます。カスタムツールを使うことで、AIエージェントに与えるプロンプトに対して、標準ツールでは対応できない処理を追加することができます。
その仕組を図解したのが以下になります。
図中には組み込みツールの他に「カスタムツール」があります。これが今回のキモなのですが、カスタムツールとして追加したいサービスはWeb API(RestではなくてもOK)で公開している必要があり、さらにそのOpenAPIのスキーマをDifyに登録する必要があります。登録したOpenAPIのスキーマを元に、Difyが自動でAPIクライアントを生成し、そのAPIクライアントを使ってカスタムツールを作成します。
SaaSサービスの場合は、大抵の場合はWeb APIが公開されているので、そのAPIを使ってカスタムツールを作成することができます。社内のシステムの場合は、APIが公開されていないこともあるかもしれませんので、その場合はそのAPIを作り込む必要はあります。
つまり、OpenAPIのスキーマを登録すれば、カスタムツールもノーコードで作成できるというわけです。
コラム: OpenAPIとは
API(Application Programming Interface)は、異なるシステムやアプリケーション同士がデータをやり取りするためのルールを定めたものです。しかし、APIの仕様が開発者ごとにバラバラだと、使い方を理解するのが大変になってしまいます。そこで登場するのが OpenAPI です。
■ OpenAPIとは?
OpenAPIは、APIの設計を標準化し、分かりやすく定義するための仕様 です。かつて「Swagger」と呼ばれていた技術が発展し、現在は OpenAPI Initiative(Linux Foundation 傘下の団体)が管理しています。
簡単に言えば、APIの取扱説明書を機械が理解できる形式で書くためのルール です。OpenAPIを使うことで、APIのリクエストやレスポンスの仕様を明確にし、開発者が迷わず利用できるようになります。
■ OpenAPIを使うメリット
– APIの設計が統一され、わかりやすくなる
– 自動でドキュメントを生成できる(Swagger UI など)
– コードの自動生成ができ、開発を効率化
– APIの動作をテストしやすくなる
たとえば、次のような OpenAPI 定義を作成すると、自動でAPIのドキュメントを作ったり、プログラムのひな形を生成したりできます。
openapi: 3.0.0
info:
title: サンプル API
version: 1.0.0
paths:
/hello:
get:
summary: 挨拶を取得
responses:
200:
description: 成功時のレスポンス
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: "こんにちは!"
このように、OpenAPIを活用すると、APIの仕様が一目で分かるだけでなく、ドキュメントの自動生成やテストの自動化ができるため、開発効率が大幅に向上 します。
APIを設計・開発する際は、ぜひ OpenAPI を活用してみましょう!
カスタムツールを利用したAIエージェントの作成
それでは、カスタムツールを利用してAIエージェントに独自機能を追加してみましょう。「AIエージェントを作ってみよう」で作成したAIエージェントに機能を追加してみます。以下のような構成図となります。
「AIエージェントを作ってみよう」で作成したAIエージェントは、ユーザーに依頼された情報をインターネットで検索し、情報検索元のサイトのURLのQRコードを表示するものでした。これにWordPressの記事を検索する機能を追加してみましょう。
つまり、ユーザーが「RAGに関する情報が知りたい」とAIエージェントに伝えたら、インターネット検索に加えて、WordPressの記事を検索して情報を取得し、その情報提供元のサイトのURLのQRコードを表示するという処理を行います。
WordPressはRest APIを公開しているので、そのAPIを使ってカスタムツールを作成します。いくつかいろんなAPIを公開していますが、今回は記事を検索するAPIを使います。APIの仕様は以下のとおりです。
■ エンドポイント:
GET /wp-json/wp/v2/posts
■ リクエストヘッダー:
Content-Type: application/json
■ クエリパラメータ:
search: 検索キーワード
■ ステータスコード:
200: 成功
■ レスポンス:
[
{
"title": {
"rendered": "記事タイトル"
},
"link": "記事のURL"
},
...
]
ホスト名がhoge.example.com、RAGという単語に関する記事を検索したい場合、APIのリクエストURLとレスポンスは以下のようになります(実際のレスポンスにはもっといろんなフィールドが出力されますが、説明の都合上割愛しています)。
■ APIのリクエストURL
https://hoge.example.com/wp-json/wp/v2/posts?search=検索キーワード
■ レスポンス
[
{
"title": {
"rendered": "RAGについて"
},
"link": "https://hoge.example.com/rag"
},
...
]
では、早速作ってみましょう。
■ ツール追加画面を開く
AIエージェントに追加するナレッジを作成するために、ナレッジ追加画面を開きます。「ツール」(①)→「カスタム」(②)→「カスタムツール」(③)の順にクリックします。
■ ツールの名前を入力する
ツールを一意に識別する名称を入力します。なんでもいいです。ここでは「WordPress検索」ツールとします。
■ OpenAPIのスキーマを入力する
OpenAPIのスキーマを入力します。

以下のOpenAPIのスキーマを入力してください。これはWordPressの記事を検索するAPIです。
openapi: 3.0.0
info:
title: SIOS Tech-Lab Posts API
description: "のりのAzureブログの記事を検索するAPI"
version: 1.0.0
servers:
- url: https://hoge.example.com/wp-json/wp/v2
paths:
/posts:
get:
summary: "検索ワードに一致するブログ記事を取得"
description: "指定した検索ワードに一致するブログ記事の一覧を取得します。"
parameters:
- name: search
in: query
required: true
description: "検索したいワード"
schema:
type: string
responses:
"200":
description: "検索結果の一覧"
content:
application/json:
schema:
type: array
items:
type: object
properties:
link:
type: string
format: uri
description: "記事のURL"
title:
type: object
properties:
rendered:
type: string
description: "記事のタイトル"
"400":
description: "不正なリクエスト"
"500":
description: "サーバーエラー"
APIの詳細な仕様は以下のURLを参照してください。
https://developer.wordpress.org/rest-api/reference/pages/
コラム: APIの認証
カスタムツールには、呼び出すAPI側に認証が設定されている場合、その認証情報を設定する必要があります。今回ご紹介したWordPressのAPIには認証は必要ありませんが、認証が必要になる場合も多いです。Difyのカスタムツールが送信できる認証情報のタイプは、以下のような方法があります。
■ ベーシック認証(Basic Authentication)
ユーザー名とパスワードを組み合わせて認証します。HTTPヘッダーに Authorization: Basic {base64_encoded_username_password}を設定します。{base64_encoded_username_password} は ユーザー名:パスワードをBase64 エンコードしたものになります。
Difyでの設定方法は以下のとおりです。
(1) 「APIキー」→「ベーシック」を選択。
(2)「キー」に Authorization を入力。
(3)「値」に Base64エンコードされた資格情報を入力。
■ ベアラー認証(Bearer Authentication)
APIトークンを使用する認証方式になります。Authorization: Bearer {access_token} をHTTPヘッダーに設定します。
Difyでの設定方法は以下のとおりです。
(1) 「APIキー」→「ベアラー」を選択。
(2)「キー」に Authorization を入力。
(3)「値」に アクセストークンを入力。
■ カスタム認証(Custom Authentication)
APIによっては、独自の認証方式を持っている場合があります。その場合は、APIの仕様に従って認証情報を設定します。
Difyでの設定方法は以下のとおりです。
(1) 「APIキー」→「カスタム」を選択。
(2)「キー」に APIの仕様に従ったキーを入力。
(3)「値」に APIの仕様に従った値を入力。
■ プロンプトを修正する
AIエージェントに与えるプロンプトを修正します。
以下のように入力します。
「〜の情報がほしい」といったような検索依頼があったら、以下のことを実施してください。
- 依頼された内容に関する情報をインターネットで検索する。
- のりのAzureブログの記事を検索する。
- 検索の結果、抽出されたサイトすべてのURLのQRコードを作成する。
最初に定義したプロンプトに加えて、「のりのAzureブログの記事を検索する」という処理を追加しています。これは、先ほど作成したカスタムツールを使ってWordPressの記事を検索するための指示です。
そして、「検索の結果、抽出されたサイトすべてのURLのQRコードを作成する。」は、インターネットを検索した結果に加えて、WordPressの記事を検索した結果に対しても、そのサイトのURLのQRコードを作成するための指示です。
■ カスタムツール追加画面を表示する
先ほど作成したカスタムツールを追加するために、カスタムツール追加画面を表示します。「追加」をクリックします。
■ カスタムツールを追加する
「カスタム」をクリックして(①)、「WordPress検索ツール」の「posts_get」隣りにある「+追加」をクリックします(②)。
■ 動作確認を行う
「Azureの情報がほしい」と入力して、AIエージェントに指示を出します。すると、QRコードを表示してくれます。
さらに、インターネットを検索した情報に加えて、WordPressのブログを検索した結果も答えてくれています。
第14章: ブロック
ここでは、Difyが提供する様々なブロックの説明をします。
LLM
LLMに接続してプロンプトを発行し、その結果を得るためのブロックです。
■ モデル
LLMに接続するためのモデルを選択します。モデルごとに詳細なパラメターを設定することができます。
■ コンテキスト
LLMが回答を生成する際に参照する情報(コンテキスト)を設定します。主に後述する「知識取得」のブロックとセットで使われることが多く、知識取得ブロックで取得した情報をコンテキストとしてLLMに渡すことができます。つまり、知識取得で取得した情報を元に、LLMは回答を生成します。
知識取得は、resultという出力変数に結果を格納します。LLMブロックはその出力変数resultをコンテキストとして参照します。
■ メッセージ
LLMに渡すプロンプトを設定します。プロンプトにはシステムメッセージ、ユーザーメッセージ、アシスタントメッセージの3種類があります。
システムメッセージ
モデルの振る舞いを決めるメッセージです。例えば「あなたは親切なAIアシスタントです」と設定すると、モデルはその指示に従って返答しようとします。対話のトーンやルールを決める役割があります。
ユーザーメッセージ
ユーザーが入力するメッセージです。実際の質問や指示がここに含まれます。例えば「今日の天気は?」と送ると、LLMはその情報をもとに応答を生成します。
アシスタントメッセージ
LLMが返答するメッセージです。ユーザーメッセージに対する回答がここに含まれます。例えば「今日の東京の天気は晴れです」といった形で返されます。
■ ビジョン
この機能を有効にすると、LLMに送るプロンプトに画像を含めることが可能です。ただし、この機能はgpt-4oなどのマルチモーダルLLMをモデルとして選択している場合にのみ有効です。
知識取得
以下の図の左側は知識取得の流れを表しており、右側は知識取得ブロックの設定画面になります。
知識取得は、まずInputとして例えばユーザーの質問があります。ここでは、「有給は何日取得できる?」という質問をInputとして設定しています。そのInputをもとにナレッジを検索して、Outputとして検索結果を出力します。
■ クエリ変数
先の図のナレッジに対するInputである「有給は何日取得できる?」がクエリ変数になります。クエリ変数は、知識取得ブロックに入力された検索クエリを表します。前のブロックの出力結果である任意の変数を受け取ることができます。
■ ナレッジ
ナレッジを追加する画面です。いわゆる社内規約などあらかじめナレッジとして登録したものを設定します。
■ 出力変数
このブロックが出力する変数の形式を表します。resultという配列にcontent、title、url、icon、metadataの5つの要素を持つオブジェクトを格納します。
以下がその変数の出力例になります。
results: [
{
"metadata": {
"_source": "knowledge",
"dataset_id": "cdf159c1-5a94-45bb-941d-400614336357",
...以下省略...
},
"title": "001018385.pdf",
"content": "① 年次有給休暇を取得した期間\r\n② 産前産後の休業期間\r\n③ 育児・介護休業法に基づく育児休業及び介護休業した期間\r\n④ 業務上の負傷又は疾病により療養のために休業した期間\r\n7 付与日から1年以内に取得しなかった年次有給休暇は、付与日から2年以内に限り\r\n繰り越して取得することができる。\r\n8 前項について、繰り越された年次有給休暇とその後付与された年次有給休暇のいず\r\nれも取得できる場合には、繰り越された年次有給休暇から取得させる"
},
...以下省略...
]
質問分類器
質問分類器は、任意のブロックから入力されたデータを受け取り、その質問をLLMが解析をし、特定のブロックにルーティングする機能を持ちます。下図を例に説明します。
例えば、何らかの任意のブロックから「有給は何日取得できる?」というデータが質問分類器に入力されたとします(①)。このブロックは例えばチャットフローであれば、開始のブロックに相当します。つまり、ユーザーが入力した質問などです。
そのデータを受けて、質問分類器に設定したLLMは、クラス(質問をカテゴリ分けするための説明が入力されたもの)の内容と照らし合わせて、質問がどのクラスに該当するかを判断します(②〜③)。この図の例で言えば、「有給は何日取得できる?」という質問は、クラス2の内容(社内規定に関すること)にマッチするので、クラス2にカテゴリ分けされます。
各クラスは事前にルーティング先の任意のブロックとの紐づけを行っておき、LLMが判断したクラスに基づいて、そのルーティング先のブロックに任意のデータを送ります(④)。
以降では各設定項目について説明します。
■ モデル
LLMが度のクラスに分類するかを判断するためのモデルを選択します。
■ 入力変数
質問分類器に入力されるデータを設定します。前のブロックの出力結果である任意の変数を受け取ることができます。
■ クラス
質問分類器が分類するクラスを設定します。クラスは、質問をカテゴリ分けするための説明が入力されたものです。例えば、クラス1は「天気に関する質問」、クラス2は「社内規定に関する質問」などです。
■ ビジョン
この機能を有効にすると、質問分類器に画像を含めることが可能です。ただし、この機能はgpt-4oなどのマルチモーダルLLMをモデルとして選択している場合にのみ有効です。
■ 高度な設定
質問分類器に入力されたデータ(例えばユーザーの質問など)をLLMが解析してどのクラスに分類するかを決定する際、補足の指示をLLMに対してすることができます。
例えば下図では、「「謎の製品X」について聞かれた場合は、商品に関する質問だけど、クラス4に分類してください。」としています。こうすると、「製品Xについて教えて」というと、クラス4に分類されて、「わかりません」と回答します。製品に関する質問には答えるけど、ある特定の製品については回答させたくないといったことを想定しています。
■ 出力変数
このブロックが出力する変数は、以下のように各クラスに設定した説明を出力します。
{
"class_name": "商品に関するもの",
"class_id": "1"
}
IF/ELSE
条件分岐を行うためのブロックです。プログラミングのif文やelse文に相当します。
ここでは以下の図の様に、チャットフローで質問文に「機密」という文字が含まれていたら、「機密情報です!!」と回答し、それ以外だったら「機密ではありません」と回答するという例を説明します。
まず条件文を設定します。①は条件式の左辺の部分です。sys.queryつまりユーザーの質問文が格納されている変数になります。
演算子は「含む」を選択します(②)。図にあるように様々な演算子を設定できます。
右辺の部分には「機密」という文字列を入力します(③)。
④では、それぞれの条件の場合に次に遷移するブロックを指定します。ここでは最初のIF文にマッチした場合は「回答1」というブロック、それ以外は「回答2」というブロックに遷移するように設定しています。
またもっと複雑な条件式も設定できます。以下の図は、ユーザーの質問が、「機密」もしくは「秘密」もしくは「ひ・み・つ」のいずれかを含む場合は「機密情報です!!」と回答し、「ほげ」だったら「意味不明です」と回答し、それ以外だったら「機密ではありません」と回答する例です。
イテレーション
イテレーションは、繰り返し処理を行うためのブロックです。プログラミングのfor文に相当します。
任意のブロックから配列に格納された複数のデータがイテレーションのブロックに送られたとします(①)。
イテレーションの設定画面では、①で送られてきたデータを順次処理する任意のブロックを定義しておき、そのブロックでデータを処理します(②)。
最終結果を任意のブロックに出力します(③)。
例えば以下のケースを考えてみましょう。ユーザーはイチゴと人物の顔写真の2つのファイルをアップロードし、その画像の特徴をLLMブロックで処理して、その結果を結合して、最終的に回答のブロックに出力します。
実例をお見せするのが早いので、設定の手順を説明しながら進めていきます。
■ 開始ブロックを定義する
チャットフローの開始ブロックにて、「ファイルリスト」を設定します(①)。変数名は「files」として(②)、ファイルの種別は「画像」に設定します(③)。これで画像をアップロードすることができるようになります。
■ イテレーションブロックを定義する
次にイテレーションブロックを定義します。イテレーションブロックには、①で定義した「ファイルリスト」の変数「files」を入力します。これで、イテレーションのブロックに送られるデータが設定されました。
送られてきたデータを順次処理するためのブロックを定義するために、「ブロックを追加」をクリックします(②)。
■ LLMブロックを定義する
イテレーションブロックに送られてきたデータを処理するためのブロックを定義します。ここではLLMブロックを定義します。
USERのプロンプトに「この画像の特徴を一言で表して」と入力します(①)。
「ビジョン」を有効にして、画像を含めることができるようにします(②)。
そして、画像の入力は「現在のイテレーション」の「{x} item」に設定します(③)。こうすることで、イテレーションでぐるぐる回したそれぞれの画像を一つずつLLMブロックの入力にすることができます。
■ イテレーションブロックの出力変数を定義する
LLMブロックの出力結果をイテレーションブロックの出力変数に設定します。この様に設定すると、複数のLLMブロックの出力結果を結合したものを出力することができます。
■ 回答ブロックを定義する
回答のブロックを定義します。ここでは、LLMブロックで処理した画像の特徴を結合して、最終的な回答を出力します。
■ 最終的なチャットフローを確認する
以下のようなチャットフローになっていることを確認します。
■ 動作確認を行う
適当な画像をアップロードして動作確認を行います。プロンプトは何でも構いません。ここでは人物の写真とイチゴの画像をアップロードしています。
確かにアップロードした画像の特徴が出力されています。
コード
任意のPythonコードもしくはJavaScriptが動かせるブロックです。
上図のブロックの詳細な図解は以下の通りとなります。他のブロックと同じように入力変数を定義します(①)。変数の値は、他のブロックからの出力結果を受け取ることができます(②)。入力変数の名前は、コード内で定義したmain関数の引数名として使われます(③)。そして、戻り値の変数名とその値を定義します(④)。戻り値の変数名と出力変数の名前は、同じである必要があります(⑤)。
テンプレート
Jinjaテンプレートによるテキストの生成を行うためのブロックです。これは先に説明したコードブロックと非常に似通った機能です。Python/JavaScriptコードブロックがプログラム言語による処理を行うのに対し、テンプレートブロックはJinjaテンプレートエンジンによるテキストの生成を行います。
このテンプレートでは、入力変数にarg1という変数を定義しています(①)。そして、テンプレート内でarg1を使ってテキストを生成します(②)。最後に、生成したテキストを出力変数outputに格納します(③)。
コラム: Jinjaテンプレートとは?
テンプレートエンジンとは、あらかじめ決まった形式のテンプレートに、変数やデータを差し込むことで動的なコンテンツを生成するためのツールです。テンプレートエンジンを使うことで、同じテンプレートを使っても、異なるデータを埋め込むことで、異なるコンテンツを生成できます。これにより、同じ構造のコンテンツを繰り返し生成する際に、手作業でコンテンツを作成する手間を省くことができます。
例えば、ウェブアプリケーションで複数のユーザーに「こんにちは、[名前]さん」というメッセージを表示する場合、テンプレートエンジンを使えば、一つのテンプレートに「[名前]」の部分だけを差し替えて、何度も異なるメッセージを生成できます。これにより、同じ形式の文書やHTMLページを効率的に作成できるのです。
Difyでも使われるJijnja2の例を見てみましょう。Jinja2は、Pythonのウェブアプリケーションなどでよく使われるテンプレートエンジンです。Jinja2では、テンプレートの中に変数や制御文を埋め込むことができ、動的なページやコンテンツを生成します。
以下のようなテンプレートを考えてみましょう。
<h1>こんにちは、{{ name }}さん!</h1>
<p>今日は{{ date }}です。</p>
このテンプレートに対して、Pythonコードからデータを渡してレンダリングします。
from jinja2 import Template
template = Template('<h1>こんにちは、{{ name }}さん!</h1><p>今日は{{ date }}です。</p>')
output = template.render(name='武井', date='2024年9月15日')
print(output)
このコードを実行すると、以下のようなHTMLが生成されます。
<h1>こんにちは、武井さん!</h1>
<p>今日は2024年9月15日です。</p>
このように、テンプレートエンジンを使うことで、変数を埋め込んで動的にコンテンツを生成することができます。
質問分類器で、ユーザーのクエリを分類して、適切な知識取得ブロックへルーティングし、その結果をLLMブロックで処理して、最終的に回答ブロックに出力するというチャットフローを作成しました。
この組み方だと、知識取得ブロックが増える、つまり参照したいナレッジが増えれば増えるほど、LLMブロックと回答ブロックが増殖していき、可読性も保守性も悪いフローになります。今回はまだ2つだからいいですが、これが10個20個になると、その煩雑さは想像に難くありません。
変数集約器を使うと、以下のようにスッキリします。つまり、知識取得ブロックの出力結果を変数集約器で集約して、LLMブロックに一つの変数として渡すことができます。
これも実際に作ってみることで理解を進めてみましょう。
一旦、すべてのLLMブロックと回答ブロックを削除して、以下のようにします。
■ 変数集約器を追加する
知識取得ブロック(どちらでもいいです)の隣の「+」をクリックして(①)、「変数集約器」をクリックします(②)。
■ 他の知識取得ブロックと変数集約器を接続する
もう一つの知識取得ブロックを変数集約器に接続します。知識取得ブロックの+をクリックして(①)、「変数集約器」にドラッグアンドドロップします(②)。
■ 変数を代入する
知識取得ブロックの出力結果を一箇所にまとめるために、変数集約器に変数を代入します。「+」をクリックして(①)、「知識取得」の出力変数であるresultをクリックします(②)。知識取得2のブロックについても同様に、resultをクリックします(③)。
■ 変数集約器の設定を確認する
以下の図のようになっていることを確認します。この様になっていれば、2つの知識取得の結果を変数集約器でまとめることができました。
■ LLMブロックに変数集約器を接続する
違うところは、コンテキストです。ここは知識取得ブロックの出力変数ではなく、変数集約器の出力変数を選択します。これは、知識取得ブロックの出力を変数集約器で集約したからです。
■ 回答ブロックを作成する
■ フローを確認する
フロー全体を確認します。だいぶスッキリしましたね。
テキスト抽出ツール
テキスト抽出ツールは、WordやPDFからテキストを抽出するためのブロックです。例えば、PDF形式のファイルからテキストを抽出して、それをLLMブロックに渡して要約させたり、コンテキスト(LLMが回答を生成するための参照情報)にすることもできます。
以下のフローは、ユーザーがチャットの入力フォームからアップロードしたファイルを、テキスト抽出ツールブロックでテキストに変換し、LLMブロックでそれを要約させて、回答ブロックに渡して出力する簡単なチャットフローです。
テキスト抽出ツールブロックの入力変数に、ファイルを入力してあげるだけで、あとは特別な設定をせず、次のブロックに出力変数を渡せば、テキスト抽出が完了します。
変数代入
変数代入とは会話変数に値を代入できる機能を持つブロックのことです。ここで会話変数という新しい言葉が出てきたので説明します。
会話変数とは、一つの会話が始まってから終わるまで、ずっとその値が消えることなく保持され続ける変数のことです。ここでいう「会話」とは以下のように「New Chat」をクリックして新しい会話を開始するか、ブラウザを閉じたりするなどして、会話が終了するまでの間のことです。下図の様にAIと会話のキャッチボールを続けている限り、会話変数は保持され続けます。
会話変数は、例えばユーザーが最初に選択した言語や、ユーザーが最初に入力した名前など、会話の中で使いたい情報を保持するのに便利です。
ここでちょっと簡単なユースケースに基づいて会話変数を説明します。会話を開始して、AIが行う初めての回答のときには「初めてですね」と回答して、2回目以降のときには「2回目以降ですね」と回答するというフローを作成してみましょう。下図はそのフローのイメージ図です。
まず最初に会話変数is_first_messageを定義して0を代入します。この変数は、会話が始まったときに初期化され、その後の会話の中で保持され続けます。
そして、ユーザーが「こんにちは」と入力します。実際にはここはこんばんはでもこんばんみでも何でもいいです。
次に、IF/ELSEブロックで会話変数is_first_messageが0かどうかを判定します。もし0だったら「初めてですね」と回答する回答ブロックに移動します。そして、会話変数is_first_messageに1を代入します。この会話変数is_first_messageに1を代入するのを担当するのが、変数代入ブロックです。
IF/ELSEブロックで会話変数is_first_messageが1だったら「2回目以降ですね」と回答する回答ブロックに移動します。
そして、またユーザーの入力を待ちます。
これを実現するワークフローを作ってみましょう。
■ 会話変数を定義する
会話変数を定義します。画面上部の会話変数マークのアイコンをクリックして(①)、「+変数を追加」をクリックします(②)。
「名前」には、変数名is_first_messageを入力します(③)。「タイプ」は数字なのでnumberを選択します(④)。そして、初期値に0を入力します(⑤)。
最後に「保存」をクリックします(⑥)。
■ IF/ELSEブロックを定義する
IF/ELSEブロックを定義します。条件式は、会話変数is_first_messageが0かどうかを判定します。
■ はじめましての場合の回答ブロックを定義する
IF/ELSEブロックの条件が真だった場合の回答ブロックを定義します。ここでは「初めてですね」と回答します。
■ コードブロックを定義する
はじめましての場合の回答ブロックにコードブロックを接続して定義します。引数なしで、単純に1という数値だけを返すコードを入力します(①)。出力変数は、コードの戻り値と同じNumber型の変数を定義します(②)。
ここでコードブロックを定義する理由は、このあと定義する変数代入ブロックでは、固定値を代入できず、他のブロックの出力結果を代入する必要があるためです。
■ 変数代入ブロックを定義する
変数代入ブロックを定義します。変数代入ブロックは、コードブロックの出力結果を会話変数is_first_messageに代入します。
■ 2回目以降の場合の回答ブロックを定義する
2回目以降の回答ブロックを定義します。IF/ELSEブロックのELSEの部分に接続します。
■ フロー全体を確認する
フロー全体を確認します。この様になっていればOKです。
■ 動作確認をする
動作確認をします。たしかに初回は、「初めてですね」と回答し、それ以降は「2回目以降ですね」と回答しています。
HTTPリクエスト
HTTPリクエストブロックは、外部のAPIを呼び出すためのブロックです。例えば、外部のAPIを呼び出して、その結果をLLMブロックに渡して回答を生成するといったことができます。
■ HTTPリクエスト
①は、リクエストの種類を選択します。GET、POST、PUT、DELETEなどのHTTPリクエストメソッドを選択できます。
②は、リクエスト先のURLを入力します。
③は、リクエストヘッダを設定します。ヘッダは、リクエストに関する情報を含む部分です。例えば、リクエストの種類やリクエストの送信元などが含まれます。
④は、クエリパラメーターを設定します。クエリパラメーターは、リクエストに含めるパラメーターを指定します。例えば、検索クエリやページ番号などが含まれます。
⑤は、リクエストボディを設定します。リクエストボディは、リクエストに含めるデータを指定します。例えば、フォームに入力されたデータやJSON形式のデータが含まれます。
この図の例では、postmanが用意してくれているAPIを呼び出しています。GETメソッドで呼び出したリクエストをオウム返ししてくれるAPIです。api-keyというリクエストヘッダにhoge、searchというクエリパラメーターにパスタと指定しています。
■ HTTPレスポンス
以下がその実行結果です。確かにHTTPリクエストで指定した通りのレスポンスが返ってきています。
■ 出力変数
出力変数は以下の通りとなります。
- status_code: HTTPステータスコード
- headers: レスポンスヘッダ
- body: レスポンスボディ
- files: ファイル(multipart/form-dataの形式でバイナリデータを送信する場合に使用)

パラメーター抽出
パラメーター抽出ブロックは、非構造化データを構造化データに変換してくれます。例えば、任意のテキストを与えるとLLMがそれを解析して、あらかじめ指定した変数の中にデータを入れてくれます。
ユースケースとしては、ユーザーが入力したテキストを解析して、その中から特定の情報を抽出するといったことが考えられます。例えば、ユーザーが「今日はレストラン『タベルナ』でパスタを食べた」と入力したときに、「レストラン名」に「タベルナ」、「料理名」に「パスタ」という情報を抽出して、レストラン名をAPIに渡して場所を取得するといった事ができます。
今回は以下のようなチャットフローを作成してみましょう。
ユーザーは、「今日はレストラン『タベルナ』でパスタを食べた」と入力します。そのテキストをパラメーター抽出ブロックで解析して、restaurantという変数に食べたお店の名前を入力するようにパラメーター抽出ブロックに指示します。そして、それをHTTPリクエストブロックに渡して、レストラン名をAPIに渡して場所を取得します。今回は、HTTPリクエストのブロックで紹介したPostman Echo APIを使って、レストランの名前を渡して、オウム返しされてくるレストラン名を確認します。
■ パラメーター抽出ブロックを定義する
パラメーター抽出のブロックを定義します。
「モデル」には、パラメーター抽出対象のテキストや画像などを解析するためのモデルを指定します(①)。
「入力変数」には、パラメーター抽出対象のテキストや画像などを指定します(②)。ここでは、開始のブロックでユーザーが入力したテキストを指定します。
「パラメーターを抽出」の項目にある「+」をクリックして(③)します。
■ パラメーターを追加する
「restaurant」という変数を追加します(①)。
「タイプ」はパラメーターの型を指定します。ここでは、文字列(String)を指定します(②)。
「説明」は、パラメーターの説明を入力します(③)。ここで入力した説明と、先ほど入力変数に指定したテキストを解析して、その中からパラメーターを抽出します。なので、この説明は、パラメーターを抽出するためのヒントとなります。
最後に「追加」をクリックします(④)。
■ HTTPリクエストブロックを定義する
HTTPリクエストブロックを定義します。HTTPリクエストブロックの設定方法はHTTPリクエストのブロックとほぼ同じなので、詳細は割愛します。
異なる点は、クエリパラメーターsearchに、パラメーター抽出ブロックで抽出したrestaurantを指定しているところです。
■ フロー全体を確認する
以下のようになっていればOKです。
■ 動作確認をする
プレビューの画面で、「今日はレストラン『タベルナ』でパスタを食べた」と入力して、そのトレースを確認してみます。
HTTPリクエストのトレースの出力を確認してみたところ、確かにパラメーター抽出ブロックで抽出したrestaurantの値が、HTTPリクエストのレスポンスに含まれていることがわかります。
第15章: チャンキング
本章では、チャンキングについて解説します。チャンキングについては、「第9章:RAGを作ってみよう」の節で触れましたが、ここでは更にその設定の詳細について解説します。
Difyでのチャンキングの手法には、大きく分けて「汎用」と「親子」の2つがあります。
■ 汎用
汎用のチャンキングは、あらかじめ決められた固定長でドキュメントを分割する手法です。例えば、1000文字ごとに分割するといった設定ができます。「第9章:RAGを作ってみよう」の節で紹介したチャンキングは、この汎用のチャンキングに該当します。
■ 親子
親子のチャンキングは、固定長で分割したチャンク(これを親チャンクと呼びます)をさらに分割して、子チャンクを作ります。親チャンクと子チャンクは紐づけを行っており、検索を行う場合は、子チャンクに対してベクトル検索を行い、それに紐づく親チャンクをコンテキストとしてLLMに与えます。
では、以降では、これらのチャンキング手法について詳しく解説します。
汎用
これは非常に馴染み深い方法かと思います。先ほど説明したように、あらかじめ決められた固定長でドキュメントを分割する手法です。例えば、1000文字ごとに分割するといった設定ができます。「第9章:RAGを作ってみよう」の節でも紹介しましたが、そこでは説明しきれなかった項目について、ここで詳しく解説します。
設定画面をもとに、どのようにチャンキングを設定するかを見ていきましょう。
一番重要なのは、「チャンク識別子」「最大チャンク長」「チャンクのオーバーラップ」です。以下の図を例に説明します。
元のドキュメントをまず、チャンク識別子で分割します。この例では、チャンク識別子を「。」に設定しているので、文の終わりで分割されます。そして、最大チャンク長で設定した値に収まるまで、チャンクを結合します。よって、一つのチャンクは最大チャンク長に収まります。
前後のチャンクはある程度、重複していることがあります。この重複部分をオーバーラップと呼びます。このオーバーラップを設定することにより、チャンク間の関連性を保つことができます。オーバーラップのサイズは、「チャンクのオーバーラップ」で設定します。
「連続するスペース、改行、タブを置換する」は、チェックするとドキュメント内の連続するスペース、改行、タブを削除します。
「すべてのURLとメールアドレスを削除する」は、チェックするとドキュメント内のURLとメールアドレスを削除します。メールアドレスなどの個人情報を含むテキストをチャンクに含めたくない場合に便利です。
「チャンクをプレビュー」をクリックすると、以下のようにチャンクがどのように分割されるかを確認することができます。
親子
チャンクサイズは、検索や生成AIの精度に大きな影響を与えます。
チャンクサイズが大きすぎる場合は、不要な情報(ノイズ)が含まれ、意図した検索が難しくなります。一方、チャンクサイズが小さすぎる場合は、必要な文脈が失われ、適切な情報を取得できないことがあります。
一方で、チャンクサイズが小さすぎると、必要な文脈が失われ、適切な情報を取得できないことがあります。
実例を見てみましょう。例えば以下のような文章を考えてみます。
生成AIを活用することで、業務の効率化が図れる。
特にRAG(Retriever-Augmented Generation)を用いることで、文書検索がより精度よく行える。
Azure OpenAI Serviceを活用すると、簡単に導入できる。
企業のナレッジ管理に役立つ。
■ チャンクサイズが大きすぎる場合
極端な例ですが、この文章全体を一つのチャンクとして扱うと、以下のような問題が発生します。
【チャンク1】
生成AIを活用することで、業務の効率化が図れる。
特にRAG(Retriever-Augmented Generation)を用いることで、文書検索がより精度よく行える。
Azure OpenAI Serviceを活用すると、簡単に導入できる。
企業のナレッジ管理に役立つ。
「Azure OpenAI Service とは?」というクエリで検索した場合を考えてみます。
このチャンクには「Azure OpenAI Service」に関する記述はあるが、他の情報も多く含まれるため、検索時に関係のない「RAG」や「業務の効率化」などの情報が混ざってしまい、適切に検索されない可能性があります。
■ チャンクサイズが小さすぎる場合
こちらも極端な例ですが、以下のように小さく分割してしまうと、以下のような問題が発生します。
【チャンク1】 生成AIを活用することで
【チャンク2】 業務の効率化が図れる。
【チャンク3】 特にRAG(Retriever-Augmented
【チャンク4】 Generation)を用いることで
【チャンク5】 文書検索がより精度よく行える。
【チャンク6】 Azure OpenAI Serviceを
【チャンク7】 活用すると、簡単に導入できる。
【チャンク8】 企業のナレッジ管理に役立つ。
「Azure OpenAI Service の活用方法」というクエリで検索した場合を考えてみます。
「Azure OpenAI Serviceを活用すると、簡単に導入できる。」が チャンク6とチャンク7に分割されているため、検索時に文脈が途切れ、適切な回答が得られない可能性があります。
このようなジレンマを解決するのが親子のチャンキングです。これは以下の図のような流れで行われます。

まず元のドキュメントをチャンク化します(①)。このチャンクを親チャンクと呼びます。
親チャンクをさらに小さいサイズにチャンク化して、子チャンクを作ります(②)。この子チャンクは、親チャンクと紐づいています。
検索を行う場合は、まずクエリをベクトル化します(③)。
そして、子チャンクに対してベクトル検索を行います(④)。
④の結果、検索された最も関連性の高い子チャンクに紐づく親チャンクをコンテキストとして、LLMに与えます(⑤)。
そのコンテキストをもとに、LLMが回答を生成します(⑥)。
この方法であれば、必要十分な情報を持っている子チャンクに対して検索を行って正しい情報を取得しつつ、LLMに与えるコンテキストは、十分な情報を持っている親チャンクになるため、適切な回答を得ることができます。
親子のチャンキングを設定する方法については、以下の図を参考にしてください。
①は、親チャンクのチャンク識別子と最大チャンク長を設定します。これは汎用のチャンキングと同じです。もう一つ「全文」というのがあります。これは親チャンクを分割しないで一つの大きいチャンクとして扱う選択肢であります。ドキュメントのサイズが小さいときは有効に働く可能性があります。大きすぎると、10000トークンでぶった切られます。
②は、子チャンクのチャンク識別子と最大チャンク長を設定します。これは当然ながら親チャンクよりも小さくなります。
③は、テキストの前処理ルールであり、汎用のチャンキングと同じです。
④は、チャンクをプレビューすることができます。以下がプレビューの画面になります。この例では、Chunk9が、C1〜C5の5つの子チャンクに分割されていることがわかります。
第16章: ナレッジ
ナレッジには様々な機能があります。今まで紹介しきれなかった機能も含めて、ナレッジの機能を紹介します。
インデックス方法
インデックス方法には「高品質」「経済的」の2つのどちらかを選択できます。
経済的
こちらを選択すると、全文検索のみの検索方式になります。もともとDify自身が持っている検索エンジンやデータベースを使うのでコストはかかりません。だから「経済的」なんですね。
「検索設定」は全文検索の結果、スコアの高い順から、何件まで表示するかを設定します。
高品質
こちらは平たく言ってしまうと、お金はかかるけれど、より高精度な検索結果を出すことができる方法です。OpenAIなどが提供する「埋め込みモデル」によるベクトル検索や、Cohereなどが提供する「Rerankモデル」による検索結果の並べ替えを行うことにより、検索結果の精度を向上させます。
高品質のモードには「ベクトル検索」「全文検索」「ハイブリッド検索」の3つのモードがあります。以降では、それぞれを詳細に解説します。
■ ベクトル検索
ベクトル検索を行う場合には、こちらを選択します。
まず、ベクトル検索に利用する埋め込みモデルを選択します(①)。
Rerankモデルの利用可否、およびRerankモデルを利用する際のモデルを選択します(②)。Rerankモデルとは、取得した検索結果を再評価(Rerank)し、より関連性の高い順に並べ替えるためのモデルになります。
この例では、以下の図のようにベクトル検索した結果をRerankモデルについて並べ替えている例です。これはWikipediaから取得したスター・ウォーズの登場人物の情報に対して、「アナキン・スカイウォーカーによって作成されて、とてもたくさんの言葉が話せる登場人物は?」というプロンプトを投げている例です。正解と思われる結果が、ベクトル検索では10位だったのに、Rerankモデルによって並び替えすることで2位に浮上していることがわかります。
トップK(③)は、ベクトル検索もしくはベクトル検索の結果をRerankモデルによって並べ替えた結果から、スコアの高い順に何件まで表示するかを設定します。
スコア閾値は、ベクトル検索もしくはベクトル検索の結果をRerankモデルによって並べ替えた結果から、スコアがこの値以上のものだけを表示するようにします。
先の図の例では、ベクトル検索の結果をRerankモデルによって並べ替えた結果から上位10件かつスコアが0.8以上のものだけを表示するように設定されています。
■ 全文検索
全文検索を用いる場合はこちらを選択します。
Rerankモデル(①)はベクトル検索と同様、全文検索によって取得した検索結果を再評価(Rerank)し、より関連性の高い順に並べ替えるためのモデルになります。
トップK(②)は、全文検索もしくは全文検索の結果をRerankモデルによって並べ替えた結果から、スコアの高い順に何件まで表示するかを設定します。
スコア閾値は、全文検索もしくは全文検索の結果をRerankモデルによって並べ替えた結果から、スコアがこの値以上のものだけを表示するようにします。
■ ハイブリッド検索 (ウェイト設定)
ハイブリッド検索は、ベクトル検索と全文検索を組み合わせた検索方式です。ベクトル検索と全文検索の両方の利点を活かし、検索結果の精度を向上させることができます。前にご紹介したベクトル検索と全文検索を実施したうえで、その結果をこれから説明するハイブリッド検索の結果でさらに並べ替えるということを実施します。
なので、ハイブリッド検索をONにすると、(キーワード検索 + ベクトル検索) → ハイブリッド検索の順に実行されます。キーワード検索とハイブリッド検索はマルチスレッドで同時に実行され、そのスレッドの完了を待ったうえで、ハイブリッド検索がおこなれるという動きになっています。
この方式には「ウェイト設定」「Rerankモデル」という2つの機能があり、それぞれでスコアの算出方法が異なります。まずは「ウェイト設定」について説明します。
以下のクエリとドキュメントがあるとします。クエリからドキュメント1〜4を検索するとして、そのスコアをウェイト設定の場合で算出してみます。
- クエリ: 革新的なAI技術とは何でしょうか?
- ドキュメント1: 最新のAI技術により、データベースが大幅に改善されました。
- ドキュメント2: 当社はAI技術を活用し、データベースを統合、さらにPythonによる自動化を実現しています。
- ドキュメント3: RAGを用いたシステムは、バナナの柔軟性とAIの知見を融合しています。
- ドキュメント4: いちごとメロンとおまんじゅうの組み合わせが絶妙です。
まずは全文検索の場合のスコアを算出します。Difyの全文検索では、クエリとドキュメントのTF-IDFを求めて、その値のコサイン類似度を図ることでスコアの算出を行います。
クエリとドキュメント1〜4は、わかち書きを行った結果、以下の単語に分割されたとします。ちなみに、Difyで実際に以下のようになるかはわかりません。説明のための例になります。
- クエリ:”AI”, “技術”
- ドキュメント1: “AI”, “技術”, “データベース”
- ドキュメント2: “AI”, “技術”, “データベース”, “Python”
- ドキュメント3: “RAG”, “バナナ”, “AI”
- ドキュメント4: “いちご”, “メロン”, “おまんじゅう”
Difyのコード内で使われているIDFの式は以下のようになっています。
ドキュメントは4件あるので、各キーワードの出現数は下記のとおりです。
キーワード |
出現ドキュメント数 |
AI |
3(ドキュメント1,2,3) |
技術 |
2(ドキュメント1,2) |
データベース |
2(ドキュメント1,2) |
Python |
1(ドキュメント2) |
RAG |
1(ドキュメント3) |
バナナ |
1(ドキュメント3) |
いちご |
1(ドキュメント4) |
メロン |
1(ドキュメント4) |
おまんじゅう |
1(ドキュメント4) |
よってAIというキーワードのIDFは次のようになります。
同様に、技術、データベース、Python、RAG、バナナ、いちご、メロン、おまんじゅうのIDFも計算します。
- 技術: 1.5108
- データベース: 1.5108
- Python: 1.9163
- RAG: 1.9163
- バナナ: 1.9163
- いちご: 1.9163
- メロン: 1.9163
- おまんじゅう: 1.9163
TFはすべて1となります。TF-IDFは、TFとIDFの積になりますので、クエリのTF-IDFは以下のようになります。
{“AI”:1.2231,”技術”:1.5108}
これはクエリにおける「AI」という単語のTF-IDFと、「技術」という単語のTF-IDFの値をベクトルで表したものです。
同様に、ドキュメント1〜4のTF-IDFも計算します。
- ドキュメント1: {“AI”:1.2231,”技術”:1.5108,”データベース”:1.5108}
- ドキュメント2: {“AI”:1.2231,”技術”:1.5108,”データベース”:1.5108,”Python”:1.9163}
- ドキュメント3: {“RAG”:1.9163,”バナナ”:1.9163,”AI”:1.2231}
- ドキュメント4: {“いちご”:1.9163,”メロン”:1.9163,”おまんじゅう”:1.9163}
そして、クエリとドキュメント1〜4のコサイン類似度を計算します。コサイン類似度は、クエリとドキュメントのベクトルの内積を、クエリのベクトルの長さとドキュメントのベクトルの長さの積で割ったものになります。つまり以下の式で表せます。
では、まずクエリとドキュメント1のコサイン類似度を計算してみましょう。クエリとドキュメント1に含まれるキーワードは以下のとおりです。
- クエリ: {“AI”, “技術”}
- ドキュメント1: {“AI”, “技術”, “データベース”}
まず内積(分子)を計算します。共通の項は「AI」と「技術」の2つです。
numerator = (1.2231 × 1.2231) + (1.5108 × 1.5108) = 3.779
次に、各ベクトルのノルム(分母)を計算します。
クエリのノルム