はじめに
こんにちは、アプリケーションサービス本部 ディベロップメントサービス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.toml のparameter_overrides = "KnowledgeBaseId=\"XXXXXXXXXX\ を作成したナレッジベースIDに変更してください。
sam build 、sam 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などは忘れずに削除するようにしてください。