30
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DifyAdvent Calendar 2024

Day 1

Dify でも図表入り資料の RAG がしたい!Amazon Bedrock Knowledge Bases と組み合わせて高度な RAG を実現する

Posted at

本記事は、Dify Advent Calendar の 1 日目記事となります。

tl;dr

  • Dify のビルトインのナレッジは手軽で便利なものの、資料中の図表を適切にパースできない
  • Amazon Bedrock Knowledge Bases を併用すると、LLM で図表を適切にパースしたり、階層型チャンキングで RAG 精度を向上することができる
  • カスタムツールとして Amazon Bedrock Knowledge Bases を統合している (Amazon API Gateway + AWS Lambda を経由) 記事はあるが、ツールだと少し取り回しが面倒
  • Dify の 外部ナレッジベースAPI 機能を使って、より手軽かつ便利に利用できる
  • その構築方法を Step by Step でやってみた

この手順で Amazon Bedrock Knowledge Bases を統合すると、以下のように図表入り資料 RAG が改善できます。

image.png
image.png

はじめに

mabuchs と申します。Dify アドベントカレンダー発起人として、一発目の記事を書かせていただきます!
4-5 月ごろに Dify がバズったタイミングで触って衝撃を受けて以来、 Dify を布教したい!という思いで色々やってきています。

これからも Dify が盛り上がってほしいと思い、アドベントカレンダーを立てた次第です。皆様の記事を楽しみにしています!

今回の記事のモチベーション

Dify の RAG の課題

Dify では、非常に簡単に PDF などのファイルを取り込んでナレッジ化し、 RAG チャットを実装することができます。全てが Dify の GUI 上で完結するため非常に手軽なものの、文書から取り込めるのはテキストに限定されており、図表をパースすることができません。また、チャンキング方法は改行等のセグメント識別子での分割のみに限られており、高度なチャンキング戦略をサポートしていません。

例として、架空の部署の戦略説明会資料 PDF を題材にします。この資料には体制の図や業績推移のグラフ、施策結果の表が含まれています。

image.png

この PDF を Dify のナレッジとして投入すると、以下のようにナレッジに保存されます。 (チャンク設定:自動)
image.png

体制図のページでは、図中のテキスト同士の連関が抽出できず、部門と案件の紐付けなどが見えなくなっています。業績推移は画像しか貼られていないため、タイトル以外抽出できていません。施策結果の表では、パワポ職人芸が仇となり、どの部がどの施策でのような評価だったのかを確認することができません。評価に対する 〇 / △ / ‐ も、パワポの図形で表現されており読み取れていません。

実際にこの資料をナレッジに利用してチャットで質問してみても、上手く回答させることはできません。
image.png

社内の資料を使った RAG において、同様の課題に当たったことがある方も多いのではないでしょうか。

Amazon Bedrock Knowledge Bases での解決

Amazon Bedrock の機能として、ビルトインされている RAG 用ナレッジベースである Amazon Bedrock Knowledge Bases が存在します。高度な RAG を実現するための様々な機能が組み込まれており、高精度な RAG アプリケーションを簡単に構築できるようになっています。

特に注目すべきは、RAG の精度を高めるために重要なパースとチャンキングの機能です。

  • LLM によるパース: 文書を取り込む際、 LLM に図表を読み取ってテキスト化させることができ (Advanced parsing)、図表を正しく解釈した RAG チャットが可能
  • 複数のチャンキングのオプション: 階層型チャンキングやセマンティックチャンキングを選択でき、RAG の精度を高めることができる (詳細は AWS Black Belt Online Seminar 資料 を参照)

これを使うことで、前述の問題を解決することができます。本記事のゴールとして、以下のような回答が返ってくることを目指していきます。

image.png

Amazon Bedrock Knowledge Bases と Dify の統合

Amazon Bedrock Knowledge Bases と Dify の連携方法については、すでにいくつか書かれている記事があります。が、後述の外部ナレッジベース API 機能登場前の記事ということもあり、

  • Amazon Bedrock Knowledge Bases を叩くための Amazon API Gateway と AWS Lambda を構築する
  • Dify のカスタムツールとして API Gateway を登録する

という手順を踏むものとなっており、やや構築するリソースが多いものになっています。また、ツールとしての外部ナレッジは、ビルトインのナレッジに比べてやや扱いにくいところがあります。

別の方法として、Dify 0.9.0 から登場した、外部ナレッジベース API を利用する方法があります。カスタムツールを構築するのに比べて扱いやすく (例:エージェントでないチャットボットから利用できる、ワークフロー内での Retrieve も標準のナレッジと同様に扱える)、 構築するリソースも少ない方法となっています。

Dify の公式ドキュメント内には、外部ナレッジ API として Amazon Bedrock Knowledge Bases を統合する方法 のドキュメントがあります。しかし、具体的な API サービスの立て方がガイドされていなかったり、サンプルコード内で使われている flask_restful のメンテが活発でなく Python/Flask のバージョンとの互換性に課題があったりします。

そこで本記事では、EC2 上にセルフホストした Dify に、外部ナレッジ API として Amazon Bedrock Knowledge Bases を統合する方法をステップバイステップで紹介していきます。

やってみる

本記事は、 Amazon EC2 上にセルフホストした Dify への組み込みを前提としています。 SaaS 版の Dify.ai や、 Amazon EC2 以外の環境にホストしている Dify の環境では適用できません。(その場合向けの記事も追々書くかもしれません)

手順の概要

image.png

本記事では、上図の構成を以下の手順で構築していきます。

  1. Dify を Amazon EC2 上に構築
  2. Amazon Bedrock のモデル有効化
  3. Dify 上での Amazon Bedrock モデルの有効化
  4. Amazon S3 バケットの作成と、対象ドキュメントの投入
  5. Amazon Bedrock Knowledge Bases の作成
  6. EC2 上の docker compose に、Amazon Bedrock Knowledge Bases を実行するための API サービス追加
  7. Dify 上で外部ナレッジ API として Amazon Bedrock Knowledge Bases を統合
  8. 動作確認

1. Dify を Amazon EC2 上に構築

既存の環境がない場合、まずは Dify を Amazon EC2 上に構築していきます。前述のワークショップの手順「2.1. AWS マネジメントコンソールへログインし、Dify を AWS にデプロイする - (a) ご自身の AWS アカウントで実施する場合」をそのまま実施することをオススメします。VPC や IAM ロール、EC2 インスタンスが CloudFormation で自動作成され、さらに Dify のインストール・起動まで自動で行われます。
注意点として、同資料内の CloudFormation テンプレートでは、手順の固定のために Dify のバージョンを 0.9.1-fix1 に固定しています。より新しいバージョンを利用したい場合、CloudFormation テンプレートをダウンロードしたのち、テンプレート内に記載されているバージョンを変更したうえでデプロイするようにしてください。
(本記事では、11/30 時点の最新版である 0.12.1 で実施しています)

dify-self-deployment.yml
# (前略)
          sudo git clone https://github.com/langgenius/dify.git
          cd /opt/dify
-         sudo git checkout 0.9.1-fix1
-         sudo git pull origin 0.9.1-fix1
+         sudo git checkout 0.12.1
+         sudo git pull origin 0.12.1
          cd /opt/dify/docker
          sudo cp .env.example .env
# (後略)

既存の環境がある場合は、その環境をそのまま利用しても構いません。ただし、EC2 に付与している IAM ロールに、以下を実行できるようなポリシーを付与するようにしてください。

  • Amazon Bedrock のモデル実行、Amazon Bedrock Agent での Retrieve 実行
  • AWS Systems Manager の Session Manager でのログイン
    • 直接 SSH できる場合は不要。SSM での手順を SSH でのログインに読み替えてください

2. Amazon Bedrock のモデル有効化

こちらも、ワークショップ手順「2.2. Amazon Bedrock 基盤モデルの有効化」と同様に実施します。本手順では、us-west-2 (オレゴン) リージョンで Amazon Bedrock を利用します。
ただ、今回は以下のモデルを有効化してください。

  • Amazon - Titan Text Embeddings V2
  • Anthropic - Claude 3.5 Haiku
  • Anthropic - Claude 3.5 Sonnet
  • Anthropic - Claude 3.5 Sonnet v2
  • Anthropic - Claude 3 Haiku

3. Dify 上での Amazon Bedrock モデルの有効化

こちらも、ワークショップ手順「2.3. Dify の初期設定」と同様に実施します。有効化するモデルも、前手順と同様にしてください。

4. Amazon S3 バケットの作成と、対象ドキュメントの投入

Amazon Bedrock Knowledge Bases を利用するリージョンに、データソースとなる S3 バケットを作成します。特別な設定は不要です。作成したバケット名は手元に控えておきます。

image.png

バケットを作成したら、検索対象としたいドキュメントをアップロードします。この手順では、先ほどの 架空の部署の戦略説明会資料 PDF を利用して進めていきます。
今回は単一のナレッジベース・データソースのみ作成しますが、拡張することを想定してフォルダ (プレフィックス) を作成し、その配下に検索対象とするファイルをアップロードします。

image.png

image.png

これで、検索対象のデータが準備できました。

5. Amazon Bedrock Knowledge Bases の作成

次に、 Amazon Bedrock Knowledge Bases を作成していきます。S3 のときと同様に Amazon Bedrock の画面に遷移したら、ナレッジベースの作成を開始し、まずはナレッジベースの基本的な設定をしていきます。

image.png

すると、次はデータソースの設定と、ベクトル DB に関する設定の画面に遷移します。

image.png

データソースの場所としては、先ほど作成した S3 のバケットとフォルダ名を利用して、 s3://{バケット名}/{フォルダ名}/ を指定します。
そして、重要なパラメータとして、[Parsing strategy] を設定します。ここで、[Foundation models as a parser] を選択すると、データ取込時に LLM が図表を読んでテキスト化し、ナレッジとして抽出するようになります。現時点では [Claude 3.5 Sonnet v1] が有効なモデル内で最も高精度なため、これを利用します。
※ なお、Parsing strategy の直下の「Instructions for the parser」を開くと、 LLM に図表を読ませる際に利用するプロンプトを確認・編集できます。図表の読み込み方法に細かい指定をしたい場合はここを編集すると良いでしょう。
また、[チャンキング戦略] も精度向上には重要です。今回は、[Hierarchical chunking] を選択します。

次の画面ではベクトル DB の設定を行います。今回は埋め込みモデルとして Amazon Titan Embeddings v2 を利用します。必要に応じて、 Cohere のモデルを選択しても構いません。ご自身の利用する言語等に合わせて選択しましょう。
ベクトルデータベースは、新規作成にすると OpenSearch Serverless のコレクションが作成され、ベクトル DB として利用されます。今回は新規作成にしますが、コストを抑えたい場合は Pinecone Serverless を別途構築して利用したりしてもよいでしょう。

その次の画面は確認画面ですので、設定項目を確認して、ナレッジベースを作成します。

image.png

数分待つとナレッジベースの準備ができます。この画面で、ナレッジベースの ID を控えておくようにします。また、データソースのデータ同期(データの取得・パース・ベクトル化)が未実行なので、同期を開始させましょう。同期完了までしばらく時間がかかるので、先に次のステップに進んでいきます。

6. EC2 上の docker compose に、Amazon Bedrock Knowledge Bases を実行するための API サービス追加

Dify を EC2 などの仮想マシンにホストする際には、 docker-compose が利用されます。この docker-compose に、Amazon Bedrock Knowledge Bases へ検索リクエストを送る API サービスを追加します。
API サービスのアプリケーションとして、 Dify の公式ドキュメント「外部ナレッジ API として Amazon Bedrock Knowledge Bases を統合する方法 」のコードを、flask ベースから FastAPI ベースに変更したものを利用します。

6.1. EC2 に SSM セッションマネージャーでログイン

EC2 インスタンスのシェルにログインします。 SSM が有効に設定されていれば、SSM セッションマネージャーでログインできるので、今回はそちらで作業を実施していきます。EC2 の管理画面から接続に進んでいきます。なお、シェルにログインできればどういったツールを利用しても問題ありません。

image.png

6.2. API サービスの関連ファイルを作成する

Dify の docker compose のディレクトリ配下に bedrock-kb-api フォルダを作成し、その配下に各種ファイルを配置します。今回のセットアップでは、 /opt/ 配下に Dify をインストールしているので、以下のようなフォルダ構成になるようにフォルダ・ファイルを作成してください。また、今回 root ユーザで Dify をインストールしているので、一度 sudo su - で root ユーザに切り替えたうえでファイル・フォルダを作成する必要があります。

/opt/ ※ dify のインストールディレクトリ
 dify/
  docker/
+  bedrock-kb-api/  <-- 本ディレクトリと配下ファイルを新規追加
+    Dockerfile     <-- コンテナ定義
+    app.py         <-- FastAPI のハンドラ
+    knowledge_service.py  <-- Amazon Bedrock Knowledge Bases へのリクエストを処理
+    requirements.txt       <-- 依存関係の定義

各ファイルの中身は以下の通りです。

Dockerfile
FROM python:3.12

# app ディレクトリ作成、移動
WORKDIR /app

# プロジェクトディレクトリにコピー
COPY requirements.txt /app

# 必要モジュールのインストール
RUN pip install --upgrade pip
RUN pip install -r requirements.txt

COPY app.py /app
COPY knowledge_service.py /app
EXPOSE 8000

CMD ["python", "-m", "uvicorn", "app:app", "--host", "0.0.0.0"]
app.py
# Dify 公式ドキュメントのサンプルを FastAPI ベースに書き換え
from fastapi import FastAPI, Header, HTTPException, Depends
from pydantic import BaseModel
from knowledge_service import ExternalDatasetService
from typing import Optional
import os
import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[logging.StreamHandler()]
)

logger = logging.getLogger(__name__)


valid_token = os.environ.get('BEARER_TOKEN')
app = FastAPI()

class RetrievalRequest(BaseModel):
    query: str
    retrieval_setting: dict
    knowledge_id: str

async def validate_token(authorization: Optional[str] = Header(None)):
    if authorization is None:
        logger.error("Authorization header is missing")
        raise HTTPException(status_code=401, detail="Authorization header is missing")
    
    parts = authorization.split()
    if len(parts) != 2 or parts[0].lower() != "bearer":
        logger.error("Invalid authorization header format: not bearer")
        raise HTTPException(status_code=401, detail="Invalid authorization header format")
    
    token = parts[1]
    if not token:
        logger.error("Invalid token: empty")
        raise HTTPException(status_code=401, detail="Invalid token")
    if token != valid_token:
        logger.error("Invalid token: not match")
        raise HTTPException(status_code=401, detail="Invalid token")
    
    return token

@app.post("/retrieval")
async def retrieve(req: RetrievalRequest, token: str = Depends(validate_token)):
    logger.info(f"Received request: {req}")
    result = ExternalDatasetService.knowledge_retrieval(
        req.retrieval_setting, req.query, req.knowledge_id
    )
    logger.info(f"Returning result: {result}")
    return result
knowledge_service.py
# ほぼ全て Dify 公式ドキュメントの通り、一部改変
import boto3
import os

# set aws related consts from environment variables
AWS_REGION = os.environ.get("AWS_REGION")
AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY")

class ExternalDatasetService:
    @staticmethod
    def knowledge_retrieval(retrieval_setting: dict, query: str, knowledge_id: str):
        # get bedrock client
        client = boto3.client(
            "bedrock-agent-runtime",
            region_name=AWS_REGION,
            aws_access_key_id=AWS_ACCESS_KEY_ID,
            aws_secret_access_key=AWS_SECRET_ACCESS_KEY
        )
        # fetch external knowledge retrieval
        response = client.retrieve(
            knowledgeBaseId=knowledge_id,
            retrievalConfiguration={
                "vectorSearchConfiguration": {"numberOfResults": retrieval_setting.get("top_k"), "overrideSearchType": "HYBRID"}
            },
            retrievalQuery={"text": query},
        )
        # parse response
        results = []
        if response.get("ResponseMetadata") and response.get("ResponseMetadata").get("HTTPStatusCode") == 200:
            if response.get("retrievalResults"):
                retrieval_results = response.get("retrievalResults")
                for retrieval_result in retrieval_results:
                    # filter out results with score less than threshold
                    if retrieval_result.get("score") < retrieval_setting.get("score_threshold", .0):
                        continue
                    result = {
                        "metadata": retrieval_result.get("metadata"),
                        "score": retrieval_result.get("score"),
                        "title": retrieval_result.get("metadata").get("x-amz-bedrock-kb-source-uri"),
                        "content": retrieval_result.get("content").get("text"),
                    }
                    results.append(result)
        return {
            "records": results
        }
requirements.txt
fastapi
uvicorn[standard]
boto3

6.3. docker-compose.yaml を編集・保存し、起動

/opt/dify/docker/docker-compose.yaml を編集し、作成した API サービスを docker compose で起動させるようにします。docker-compose.yaml の services: セクションの末尾に、以下を追加していきます。

services:
# 中略
  unstructured:
    image: downloads.unstructured.io/unstructured-io/unstructured-api:latest
    profiles:
      - unstructured
    restart: always
    volumes:
      - ./volumes/unstructured:/app/data

+ bedrock-kb-api:
+   container_name: bedrock-kb-api
+   build:
+     context: ./bedrock-kb-api
+     dockerfile: Dockerfile
+   environment:
+     - BEARER_TOKEN=anybearertokenstring # 必要に応じて変更
+     - AWS_REGION=us-west-2              # 必要に応じて変更
+   command: [ "python", "-m", "uvicorn", "app:app", "--host", "0.0.0.0" ]
+ 
networks:
# 後略

environment: 配下の環境変数は必要に応じて変更してください。

  • BEARER_TOKEN : この API サービスがリクエストを受け付ける際の Bearer トークンの値。今回の設定では NW 構成上 Dify を構成するコンテナ以外からは接続できないため、そのまま利用してもリスクは低いが、必要に応じて変更する。 5 文字以上である必要がある。後に Dify 上での設定時に利用するので、控えておく
  • AWS_REGION : Amazon Bedrock Knowledge Bases を作成した AWS リージョンを指定

これが完了したら、docker-compose を再び起動します。

# 以下 root ユーザで実行。root ユーザ以外で作業している場合は sudo su - で切替
$ cd /opt/dify/docker/
$ docker-compose up -d

各種設定に問題がなければ、既存の Dify のコンテナに加えて、新しく bedrock-kb-api という名前のコンテナが立ち上がります。

7. Dify 上で外部ナレッジ API として Amazon Bedrock Knowledge Bases を統合

あと少しです! Dify の UI に戻って作業を実施していきます。

image.png

ナレッジの管理画面の右上の [外部ナレッジ API] から、API 接続先を作成します。ここでは以下の値を設定して [セーブ] をクリックします。

  • Name : 任意の名称
  • API Endpoint: http://bedrock-kb-api:8000
  • API Key: 6.3. で環境変数に指定したトークン
    • サンプルの例では、 anybearertokenstring

先ほど作成したサービスは docker-compose 上で起動しているため、 bedrock-kb-api のサービス名で名前解決できます。トークンは、 API サービス作成時に環境変数で指定したトークンを利用します (サンプルままの例では anybearertokenstring)。なお、外部ナレッジ API 設定時に「API キーは必須かつ 5 文字以上の長さが必要」という制約があるため、これをそのまま 6.3. での BEARER_TOKEN 環境変数の制約にしていました。

image.png

API との接続ができたら、5. で控えておいたナレッジベース ID を指定してナレッジベースへの接続を行います。これで、Dify のナレッジとして Amazon Bedrock Knowledge Bases を利用する準備が完了しました!
なお、もしナレッジベースを複数作成して接続する場合も、外部ナレッジ API は再作成する必要がありません。もう一度上図のように[外部ナレッジベーへの接続] を行い、別のナレッジ ID を追加するだけで OK です。

8. 動作確認

8.1. ナレッジの検索確認

ナレッジが作成できたら、検索の動作を確認してみましょう。Dify の UI のバグなのか、一度リロードしないと外部ナレッジが表示されないようなので、リロードしてナレッジの画面に遷移し、検索クエリを実行してみます。

image.png

検索結果を確認してみると、Dify のビルトインのナレッジでは断片的な文の羅列になっていた検索結果が、整然とした Markdown のテーブルになっていることに気付きます。さらに、PowerPoint の図形で作成されていた 〇 / △ / - の文字列も OCR されて文字として抽出できていることがわかります。

8.2. アプリに組み込んで RAG チャットで確認

これで、Dify で構築した各種アプリで、ナレッジとして Amazon Bedrock Knowledge Bases を利用できるようになりました!チャットボットのコンテキストとして簡単に追加できるようになっており、RAG チャットが容易に実現できるようになっていることがわかります。

image.png

サンプル PDF の場合の例をいくつか見てみましょう。元資料と、Dify のビルトインのナレッジを利用した場合の回答、Amazon Bedrock Knowledge Bases を併用した場合の回答をあわせて見ていきます。

例1) グラフからの数値の読み取り

image.png

グラフが添付されているページを元に回答してほしい質問です。Dify ビルトインのナレッジでは図表の情報が欠落していましたが、 Amazon Bedrock Knowledge Bases はグラフ内の数値を正しく読み取って回答してくれています。

例2) 図形ベースの表の読み取り

image.png

PowerPoint の図形で表が表現されているケースでは、PDF の文字をただ抽出するだけでは表の行・列のラベルの対応関係を抽出することが難しいです。ビルトインのナレッジでは、列ラベルの情報が欠落した回答になってしまっています。また、〇 / △ / - が図形として作成されているため、これも抽出できていませんでした。Amazon Bedrock Knowledge Bases ではこれが解決できていることがわかります。

おわりに

本記事では、 Dify の RAG の弱みである「図表入りの資料の RAG ナレッジ化」を、Amazon Bedrock Knowledge Bases + Dify の外部ナレッジ API 機能により解決してみました。皆さんの Dify アプリ構築がよりよくなることを願っています!

Dify Advent Calendar、 明日は @har1101 さんによる記事です。お楽しみに!

30
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?