Difyは。自身のプラットフォーム内に検索用データベースを持っています。デフォルトでは、Weaviateを使いますが、PostgreSQLやChromaなど多彩なデータベースに対応しています。そして、自プラットフォーム内に検索データベースを持つことで、別途検索データベースを用意することなく、LLMアプリの導入コストを抑えることができます。
しかしながら、Azure AI SearchやElastic Search、Vertex AI Searchなど自プラットフォーム外の検索サービスを使いたい場合もあると思います。既にこういったサービスで検索データベースを提供している場合はなおさらでしょう。これらのサービスは、お値段も少々高いですが、高機能ですし、エンタープライズ向けの検索データベースとしては広く使われていますし、こういったものを導入したいというニーズもあると思います。
Difyはこういったニーズにも対応しています。この自プラットフォーム外にある検索データベースのことをDifyでは「外部ナレッジベース」と呼びます。
システム構成
外部ナレッジベースを利用するためのシステム構成は以下のとおりです。
Difyが外部ナレッジベースにアクセスするためには、Difyの準拠するAPIの仕様に従わなければなりません。しかしながら、外部ナレッジベースは、そのサービスによって様々なAPIを持つので、この差分を吸収するために、Difyと外部ナレッジベースの間にプロキシのような役割を持つAPIを設ける必要があります。それが上図の「API」と書かれているコンポーネントになります。
このプロキシのような役割を持つAPIは独自に開発する必要があります。これをDifyでは「外部ナレッジベースAPI」と呼びます。
外部ナレッジベースAPI
外部ナレッジベースのAPIの仕様は以下のとおりです。
リクエスト
■ エンドポイント
/retrieval
■ メソッド
Post
■ ヘッダー
Authorization: Bearer {APIキー}
■ ボディ
{
"knowledge_id": "{ナレッジを一意に識別するID}",
"query": "{検索クエリ}",
"retrieval_setting":{
"top_k": {上位何件を取得するか},
"score_threshold": {スコアの閾値}
}
}
{APIキー}は外部ナレッジベースAPIの認証に使われるキーです。このキーは外部ナレッジベースAPIの管理者が発行します。
{ナレッジを一意に識別するID}は外部ナレッジベース内のナレッジを一意に識別するためのIDです。Difyで外部ナレッジベースを追加する際に任意の値を設定します。
{検索クエリ}は検索するためのクエリです。
{上位何件を取得するか}は検索結果の上位何件を取得するかを指定します。Difyで外部ナレッジベースを追加する際にこの値を設定します。
{スコアの閾値}は検索結果のスコアの閾値を指定します。スコアがこの閾値を超えるナレッジのみを取得します。Difyで外部ナレッジベースを追加する際にこの値を設定します。
レスポンス
Difyは以下のようなレスポンスを受け取ることを期待します。
■ ステータスコード
200
■ ヘッダー
Content-Type: application/json
■ ボディ
{
"records": [
{
"metadata": {
"path": "{ドキュメントへのパス}",
"description": "{ドキュメントの説明}"
},
"score": {検索スコア},
"title": "{ドキュメントのタイトル}",
"content": "{ドキュメントの内容}"
},
...略...
]
}
{ドキュメントへのパス}は検索結果のナレッジが存在するドキュメントへのパスです。例えばAzure Blob Storageにドキュメントを格納する場合は、`https://example.blob.core.windows.net/{コンテナ名}/{ファイル名}`のような形式になります。
{ドキュメントのタイトル}は検索結果のナレッジが存在するドキュメントの説明です。
{検索スコア}は検索結果のスコアです。
{ドキュメントのタイトル}は検索結果のナレッジが存在するドキュメントのタイトルです。
{ドキュメントの内容}は検索結果のナレッジが存在するドキュメントの内容です。
外部ナレッジベースを利用する
外部ナレッジベースにAzure AI Searchを利用する場合の実装や設定を紹介します。先の仕様に準拠すればElasticsearchでもなんでも大丈夫です。
APIの実装
外部ナレッジベースを利用するためのAPIは以下のような実装になります。Python + Flaskですが、APIの仕様を満たしていればフレームワーク、開発言語は問いません。
import os
from flask import Flask, request, jsonify, abort
from azure.search.documents import SearchClient
from openai import AzureOpenAI
from azure.core.credentials import AzureKeyCredential
from azure.search.documents.models import VectorizedQuery
from dotenv import load_dotenv
# .envファイルから環境変数を読み込む。
load_dotenv(verbose=True)
# 環境変数から各種Azureリソースへの接続情報を取得する。
SEARCH_SERVICE_ENDPOINT = os.environ.get("SEARCH_SERVICE_ENDPOINT") # Azure AI Searchのエンドポイント
SEARCH_SERVICE_API_KEY = os.environ.get("SEARCH_SERVICE_API_KEY") # Azure AI SearchのAPIキー
SEARCH_SERVICE_INDEX_NAME = os.environ.get("SEARCH_SERVICE_INDEX_NAME") # Azure AI Searchのインデックス名
AOAI_ENDPOINT = os.environ.get("AOAI_ENDPOINT") # Azure OpenAI Serviceのエンドポイント
AOAI_API_VERSION = os.environ.get("AOAI_API_VERSION") # Azure OpenAI ServiceのAPIバージョン
AOAI_API_KEY = os.environ.get("AOAI_API_KEY") # Azure OpenAI ServiceのAPIキー
AOAI_EMBEDDING_MODEL_NAME = os.environ.get("AOAI_EMBEDDING_MODEL_NAME") # Azure OpenAI Serviceの埋め込みモデル名
# ユーザーの質問に対してドキュメントをAzure AI Searchから検索する関数
def search(question):
# Azure AI SearchのAPIに接続するためのクライアントを生成する
search_client = SearchClient(
endpoint=SEARCH_SERVICE_ENDPOINT,
index_name=SEARCH_SERVICE_INDEX_NAME,
credential=AzureKeyCredential(SEARCH_SERVICE_API_KEY)
)
# Azure OpenAI ServiceのAPIに接続するためのクライアントを生成する
openai_client = AzureOpenAI(
azure_endpoint=AOAI_ENDPOINT,
api_key=AOAI_API_KEY,
api_version=AOAI_API_VERSION
)
# Azure OpenAI Serviceの埋め込み用APIを用いて、ユーザーからの質問をベクトル化する。
response = openai_client.embeddings.create(
input=question,
model=AOAI_EMBEDDING_MODEL_NAME,
dimensions=1536
)
# ベクトル化された質問をAzure AI Searchに対して検索するためのクエリを生成する。
vector_query = VectorizedQuery(
vector=response.data[0].embedding,
k_nearest_neighbors=3, # 初期値。後続の処理でretrieval_settingのtop_kで絞る。
fields="contentVector"
)
# ベクトル化された質問を用いて、Azure AI Searchに対してベクトル検索を行う。
results = search_client.search(
vector_queries=[vector_query],
select=['id', 'content']
)
return results
# Flaskアプリケーションの定義
app = Flask(__name__)
# 外部ナレッジベースの検索APIを提供するエンドポイント
@app.route('/retrieval', methods=['POST'])
def retrieval():
# ヘッダーのAuthorizationをチェックする
# Difyからの外部ナレッジベースのHTTPリクエストAPIキーが正しいかどうかを確認する
# ここではとりあえず、APIキーが"your-api-key"であることをチェックする
auth = request.headers.get("Authorization", "")
if auth != "Bearer your-api-key":
abort(401)
body = request.get_json()
if not body:
return jsonify({"error": "JSON body required"}), 400
# リクエストボディから必要なパラメータを取得する。
knowledge_id = body.get("knowledge_id")
query = body.get("query")
retrieval_setting = body.get("retrieval_setting", {})
top_k = retrieval_setting.get("top_k", 3)
score_threshold = retrieval_setting.get("score_threshold", 0.0)
if not query or not knowledge_id:
return jsonify({"error": "knowledge_id and query are required."}), 400
# search関数を用いて検索を実施する。questionはqueryに対応する。
results = search(query)
records = []
count = 0
# 検索結果を変換して、score_threshold以上の結果をtop_k件まで抽出する。
for result in results:
score = result.get('@search.score', 0)
if score < score_threshold:
continue
if count >= top_k:
break
# 変換例: idをファイル名として利用(仮実装)
record = {
"metadata": {
"path": f"https://example.blob.core.windows.net/knowledge/{result.get('id')}.txt",
"description": "dify知識ドキュメント"
},
"score": score,
"title": f"{result.get('id')}",
"content": result.get('content')
}
records.append(record)
count += 1
return jsonify({"records": records}), 200
if __name__ == '__main__':
# Flaskアプリを起動する
app.run(host='0.0.0.0', port=5001)
Difyの設定
それではDify側の設定を行います。
■ 外部ナレッジAPIの追加画面を表示する
「ナレッジ」をクリックし(①)、「外部ナレッジAPI」をクリックします(②)。そして、「+外部ナレッジAPIを追加する」をクリックします(③)。
■ 外部ナレッジAPIを追加する
「Name」には外部ナレッジAPIを識別する任意の名称を入力します(①)。「API Endpoint」には外部ナレッジAPIのエンドポイントを入力します(②)。「/retrieval」を除いたURLを入力します。「API Key」には外部ナレッジAPIのAPIキーを入力します(③)。最後に「セーブ」をクリックします(④)。
■ 外部ナレッジベースを追加する
「外部ナレッジ名」には外部ナレッジベースを識別する任意の名称を入力します(①)。「ナレッジの説明」には外部ナレッジベースの説明を入力します(②)。「外部ナレッジAPI」には、先ほど追加した外部ナレッジAPIを選択します(③)。「ナレッジID」には外部ナレッジベース内のナレッジを一意に識別するIDを入力します(④)。「トップK」には検索結果の上位何件を取得するかを入力します(⑤)。「スコアの閾値」には検索結果のスコアの閾値を入力します(⑥)。最後に「繋ぐ」をクリックします(⑦)。
■ チャットボットに外部ナレッジベースを接続する
外部ナレッジベースに接続するチャットボットを作成してみます。チャットボットの設定画面にて、コンテキストの「+追加」をクリックします。
■ 外部ナレッジベースを選択する
先ほど作成した外部ナレッジベースを選択します(①)。そして、「追加」をクリックします(②)。
チャットフローの場合には、「知識取得」のブロックにて、同様の方法で外部ナレッジベースを選択します。
■ テストする
チャットボットをテストしてみます。チャットボットに質問を投げて、外部ナレッジベースからの回答を確認します。
まとめ
外部の検索システムを利用することで、さらにDifyをパワーアップすることができます。