Difyで外部の検索データベースを使う

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」には検索結果の上位何件を取得するかを入力します(⑤)。「スコアの閾値」には検索結果のスコアの閾値を入力します(⑥)。最後に「繋ぐ」をクリックします(⑦)。
■ チャットボットに外部ナレッジベースを接続する
外部ナレッジベースに接続するチャットボットを作成してみます。チャットボットの設定画面にて、コンテキストの「+追加」をクリックします。
■ 外部ナレッジベースを選択する
先ほど作成した外部ナレッジベースを選択します(①)。そして、「追加」をクリックします(②)。
チャットフローの場合には、「知識取得」のブロックにて、同様の方法で外部ナレッジベースを選択します。
■ 詳細な設定を行う
外部ナレッジベースを選択すると、詳細な設定が表示されます。Rerankモデルの利用や、検索結果の上位何件を取得するかやスコアの閾値を設定することができます。
■ テストする
チャットボットをテストしてみます。チャットボットに質問を投げて、外部ナレッジベースからの回答を確認します。

まとめ

外部の検索システムを利用することで、さらにDifyをパワーアップすることができます。
ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です