Dify × Bedrock ナレッジベース(S3 Vectors)でマルチモーダルなRAGを作る

記事タイトルとURLをコピーする

はじめに

こんにちは、アプリケーションサービス本部 ディベロップメントサービス3課の北出です。
今回は、DifyとAWSを利用して、PDF内のテキストだけでなく、画像の情報も含めたマルチモーダルなRAGを作成してみましたのでざっくりと方法を紹介させていただきます。
DifyとAWSの統合 ではこちらの記事 をだいぶ参考にさせていただきました。
ですので手順が重なる部分は省略させていただきます。

背景

そもそも今回このようなマルチモーダルなRAGを作ろうと思った背景になります。
現在参加しているプロジェクトで設計書などのドキュメントが多く、全体像をつかみきれなかったり、レビューでの観点の漏れが起こる可能性を懸念しており、設計書のRAGを作成して少しでもプロジェクトの生産性を挙げられないかと思いました。
プロジェクトでは設計書などをGitlabで管理しているのですが、設計書はパワーポイントやエクセルなども多く、PDF化できたとしても構成図やシーケンス図などの画像からわかる情報もないと回答としては信頼できません。 そのため、今回はテキストだけでなく、画像の情報も含めたRAGの作成をしようと思いました。

目標

プロジェクトに導入する際の目標は

  • .pptxや.xlsxを自動でPDFに変換する
  • GitLabのブランチがマージされたら自動でナレッジベースのデータソースに追加して同期する
  • GitLabのマージリクエストをトリガーに生成AIからレビューをする

などもあるのですが、今回はシンプルに、 データソースのPDFから画像の情報に関する回答を得る というところを目標にします。
具体的なところですが、さすがにプロジェクトの設計書などは出せないので、今回は自分の過去の自己紹介のスライドを例で使ってみます。

これは自分の自己紹介のスライドの内の1ページですが、PDFをデータソースにして以下のようなやり取りができることを目指します。

【質問】 北出宏紀がおすすめする家族でやるのにおすすめなゲームは?

【AIからの回答】 「OVERCOOKED! 2」です

オーバークック2についてはPDF中ではテキストでは一切なく、この画像だけにある情報になります。 AIが確実に画像からテキストを読み取れるように大きくしています。

構成

構成図は上のようになります。
当初はこちらの記事 にならって、Dify のAWS Tools プラグインを使おうとしていたのですが、うまくいかず、API GatewayにHTTPリクエストを送る形にしました。

作成手順

Dify導入手順

こちらの記事 を参考にしてください。

ナレッジベースの作成

データソース用バケットの作成

米国(オレゴン)リージョンに任意の名前でS3バケットを作成します。特別な設定は不要でデフォルトのままで問題ありません。ここにナレッジベースのソースとなるPDFを入れることになります。
バケット作成後にPDFもアップロードしましょう

マルチモーダルストレージの作成

米国(オレゴン)リージョンに任意の名前でS3バケットを作成します。特別な設定は不要でデフォルトのままで問題ありません。このバケットの用途は以下のようで、確認したときはPDFが1ページずつ.pngの画像になって格納されていました。ユーザー自身がこのバケットに何かをアップロードする必要はありません。

Amazon Bedrock ナレッジベースは、この S3 の場所にフォルダを作成します。これには、ドキュメントから抽出されたものやデータソースから抽出されたものも含まれます。これらの画像はユーザークエリに基づいて取得され、応答の生成に使用され、応答で引用されます。

Bedrock ナレッジベースの作成

おおよその手順はこちらの記事 と同様です。 Bedrockの画面にて、「ナレッジベース」→「作成」→「ベクトルストアを含むナレッジベース」をクリックします。
ナレッジベース名、サービスロール名はデフォルトで値が入っていますが、そのままでOKです。
データソースはS3とし、「次へ」をクリックします。
「S3のURI」の箇所で、「参照」をクリックするとS3バケットが選択できます。 先ほど作成したデータソース用S3バケットを選択します。
【重要】
解析戦略パーサーとしての基盤モデル を選択します。
選択すると、 解析用の基盤モデルを選択 の欄が出てきますので、Nova Pro を選択してから「次へ」をクリックします。

埋め込みモデルは Titan Embeddings G1 を選択します。 Titan Text Embeddings V2 でも可能と思いますが未検証です。
ベクトルデータベースを選択する箇所で、「Amazon S3 Vectors」を選択します。
マルチモーダルストレージの保存先では、作成したマルチモーダルストレージを選択し、「次へ」をクリックします。
確認画面が表示されるので内容を確認し、「ナレッジベースを作成」をクリックします。
作成後、作成したデータソースを選択肢、「同期」をクリックします。
次の手順で必要なため、英数10桁のナレッジベースIDはコピーしておきます。

AWSリソースの作成

次に、API GatewayやLambdaなどのAWSリソースを作成します。

必要なリソースはAWS SAM でIaC化しております。
動作優先のため、最小権限になっていなかったり、冗長な部分もありますが目をつぶっていただきたいです。

ディレクトリ構成は以下です。

├── src/
│   ├── app.py
│   └── requirements.txt
├── samconfig.toml
└── template.yaml

template.yaml

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Bedrock Knowledge Base API for Dify

Parameters:
  KnowledgeBaseId:
    Type: String
    Description: Bedrock Knowledge Base ID
  ModelId:
    Type: String
    Description: Bedrock Foundation Model ID

Globals:
  Function:
    Timeout: 60
    Runtime: python3.13
    Environment:
      Variables:
        KNOWLEDGE_BASE_ID: !Ref KnowledgeBaseId
        MODEL_ID: !Ref ModelId

Resources:
  BedrockKnowledgeBaseFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/
      Handler: app.lambda_handler
      Policies:
        - AmazonBedrockFullAccess
        - AmazonS3ReadOnlyAccess
      Events:
        QueryApi:
          Type: Api
          Properties:
            Path: /query
            Method: post

Outputs:
  BedrockKnowledgeBaseApi:
    Description: API Gateway endpoint URL
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/query"

samconfig.toml

# More information about the configuration file can be found here:
# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
version = 0.1

[default]
[default.global.parameters]
stack_name = "bedrock-knowledge-base-api"

[default.build.parameters]
cached = true
parallel = true

[default.validate.parameters]
lint = true

[default.deploy.parameters]
capabilities = "CAPABILITY_IAM"
confirm_changeset = true
resolve_s3 = true
region = "us-west-2"
s3_prefix = "bedrock-knowledge-base-api"
disable_rollback = true
parameter_overrides = "KnowledgeBaseId=\"XXXXXXXXXX\" ModelId=\"global.anthropic.claude-sonnet-4-20250514-v1:0\""
image_repositories = []

[default.package.parameters]
resolve_s3 = true

[default.sync.parameters]
watch = true

[default.local_start_api.parameters]
warm_containers = "EAGER"

[default.local_start_lambda.parameters]
warm_containers = "EAGER"

src/app.py

import json
import os
import boto3
from botocore.exceptions import ClientError


def lambda_handler(event, context):
    try:
        # Parse request body
        body = json.loads(event["body"])
        query_text = body.get("query")

        if not query_text:
            return {
                "statusCode": 400,
                "headers": {"Content-Type": "application/json"},
                "body": json.dumps({"error": "Query text is required"}),
            }

        # Initialize Bedrock clients
        bedrock_agent_client = boto3.client(
            "bedrock-agent-runtime", region_name="us-west-2"
        )
        bedrock_runtime_client = boto3.client(
            "bedrock-runtime", region_name="us-west-2"
        )

        # Get configuration from environment
        knowledge_base_id = os.environ["KNOWLEDGE_BASE_ID"]
        model_id = os.environ["MODEL_ID"]

        # Query knowledge base
        kb_response = bedrock_agent_client.retrieve(
            knowledgeBaseId=knowledge_base_id, retrievalQuery={"text": query_text}
        )

        # Extract context from knowledge base results
        context_text = ""
        for result in kb_response.get("retrievalResults", []):
            context_text += result["content"]["text"] + "\n\n"

        # Generate AI response using Claude
        ai_response = ""
        if context_text.strip():
            prompt = f"""以下の情報を参考にして、ユーザーの質問に日本語で回答してください。

参考情報:
{context_text}

ユーザーの質問: {query_text}

回答:"""

            claude_request = {
                "anthropic_version": "bedrock-2023-05-31",
                "max_tokens": 1000,
                "messages": [{"role": "user", "content": prompt}],
            }

            claude_response = bedrock_runtime_client.invoke_model(
                modelId=model_id, body=json.dumps(claude_request)
            )

            response_body = json.loads(claude_response["body"].read())
            ai_response = response_body["content"][0]["text"]

        return {
            "statusCode": 200,
            "headers": {"Content-Type": "application/json"},
            "body": json.dumps({"answer": ai_response}, ensure_ascii=False),
        }

    except ClientError as e:
        return {
            "statusCode": 500,
            "headers": {"Content-Type": "application/json"},
            "body": json.dumps({"error": f"AWS error: {str(e)}"}),
        }
    except Exception as e:
        return {
            "statusCode": 500,
            "headers": {"Content-Type": "application/json"},
            "body": json.dumps({"error": str(e)}),
        }

src/requirements.txt

boto3>=1.34.0

コードコピー後、 samconfig.tomlparameter_overrides = "KnowledgeBaseId=\"XXXXXXXXXX\ を作成したナレッジベースIDに変更してください。

sam buildsam deploy --region us-west-2 でデプロイしましょう。

デプロイ後はAPIGatewayでテストしてみることを推奨します。例えば図のようにテストできます。

IAMユーザーの作成

今回のハンズオンではDify側から直接Bedrockを実行する部分もあるので、実行用のIAMユーザーを作成します。 手順は こちら を参考にしてください。

Dify側の設定

ここまででAWS側の準備は整ったので、Dify側の設定を行います。

DifyでのBedrockのセットアップもこちら を参考にしてください。 システム推論モデルとして、Amazon Nova が使える状態ならば問題ありません。

チャットフローの作成開始

ヘッダの「スタジオ」→「最初から作成」をクリックします。 任意のアプリ名を指定し、「作成する」をクリックします。

チャットフローの作成

チャットフローの作成手順はこれから説明しますが、最終的には以下のようになります。

「開始」の次にHTTPリクエストを追加します。図のように設定します。URLはSAMの出力にもあるので参照してください。ヘッダーとボディも図のように作成します。ボディは / を入力すると、sys.queryが選択肢として出てくるはずです。

次に回答を整形するためのAmazon Nova のLLMのステップを追加します。APIGatewayから帰ってくる値はエスケープ文字列などもあって読みづらかったので、Amazon Novaに頼りました。本来はAWS内で整形して返すのがベストですが、今回はそこには力は入れませんでした。図のようにLLMを設定してください。

最後に「回答」ステップを追加します。

ここまでできたらプレビューで試してみましょう

無事に「Overcooked! 2(オーバークック2)」です。との回答が来たので目標達成です。

おわりに

長くなりましたが、ここまでの手順で画像の情報も読み取ったマルチモーダルなRAGが作れたかと思います。
Difyで作ったものをどうやってプロジェクトに組み込むのかや、回答精度などの課題もありますが、それはこれからやっていこうかと思います。
今回作成したAPIなどは忘れずに削除するようにしてください。

北出 宏紀(執筆記事の一覧)

アプリケーションサービス本部ディベロップメントサービス3課

2024年9月中途入社です。