Dify導入 個人メモ
急に RAG チャットボットを導入することになったので…
参考になれば幸いです
定期的に更新します
※2024/10/03 時点での情報です
Install Docker Compose
※以下の v2.29.7 をインストール
sudo -s
cd
dnf update
dnf install -y docker
systemctl start docker
gpasswd -a $(whoami) docker
chgrp docker /var/run/docker.sock
systemctl restart docker
systemctl enable docker
curl -L "https://github.com/docker/compose/releases/download/v2.29.7/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
「uname -s」は OS 名、例えば「Linux」
「uname -m」は CPU アーキテクチャ、例えば「x86_64」
docker -v
docker-compose -v
Install Git
dnf install -y git
Install Dify
※以下のように記載があり、profiles で、qdrant をベクトルデータベースとして起動可能
現在は、Qdrant が推奨されている
# Qdrant vector store.
# (if used, you need to set VECTOR_STORE to qdrant in the api & worker service.)
qdrant:
image: langgenius/qdrant:v1.7.3
profiles:
- qdrant
restart: always
volumes:
- ./volumes/qdrant:/qdrant/storage
environment:
QDRANT_API_KEY: ${QDRANT_API_KEY:-difyai123456}
git clone https://github.com/langgenius/dify.git
cd dify/docker
cp .env.example .env
docker-compose --profile qdrant up -d # qdrantをベクトルデータベースとして起動
docker-compose ps
以下でアクセスする
http://localhost/
http://グローバル IP アドレス/
スペックについて
EC2 の IAMRole は、最低限 Bedrock へのアクセス権限を付与しておくように
EC2 インスタンスタイプは、t3.medium で起動可能
EBS は、最小で 7.3GB 使用した
Knowledge Base を使用してデータを蓄積する場合は、50 ~ 100GB くらいあっても良いと思う
ログ・トレーシング
Langfuse が無料で使用可能
Dify 設定エビデンス
サインイン
適当なメールアドレスとパスワードでサインインする
※メールアドレスのチェックは行われないため、存在しないメールアドレスでも問題ないです
Dify 設定
設定画面に移動
時刻設定
LLM (Bedrock) 設定
※モデルへのアクセスは有効であること
※適当に 1 つモデル ID をコピーします
※IAM Role が割り当てられているため、アクセスキー設定は不要です
各モデル設定
シンプルなチャットボットを作成 (動作確認)
※モデルを選択
※更新する (何か設定変更したら更新して保存するように!)
チャットボットを起動
チャットしてみる
Claude 3 Haiku は、ChatGPT をベースに作られていることが分かりました…
Langfuse 設定エビデンス
Langfuse 起動
Dify と同様に Docker Compose で起動する
sudo -s
cd
git clone https://github.com/langfuse/langfuse.git
cd langfuse
vi docker-compose.yml
以下に修正
- ポート 3000 は、Dify が使用しているため、3002 に変更する
- network を追加して、Dify と Langfuse を同じネットワークに配置する
services:
langfuse-server:
image: langfuse/langfuse:2
depends_on:
db:
condition: service_healthy
ports:
- "3002:3000" # 変更
environment:
- DATABASE_URL=postgresql://postgres:postgres@db:5432/postgres
- NEXTAUTH_SECRET=mysecret
- SALT=mysalt
- ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
- NEXTAUTH_URL=http://localhost:3002 # 変更
- TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-true}
- LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false}
networks: # 追加
- langfuse_network # 追加
db:
image: postgres
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 3s
timeout: 3s
retries: 10
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=postgres
ports:
- 5432:5432
volumes:
- database_data:/var/lib/postgresql/data
networks: # 追加
- langfuse_network # 追加
volumes:
database_data:
driver: local
networks: # 追加
langfuse_network: # 追加
name: langfuse_network # 追加
driver: bridge # 追加
docker-compose up -d
docker-compose ps
docker network ls
以下でアクセスする
http://グローバル IP アドレス:3002/
Dify の docker-compose.yaml を修正し、Langfuse と同じネットワークに配置する
cd /root/dify/docker
vi docker-compose.yaml
以下、2 つの api と worker サービスに langfuse_network を追加する
※vi エディタで、/api と/worker で検索
services:
api:
image: langgenius/dify-api:0.9.1
restart: always
environment:
<<: *shared-api-worker-env
MODE: api
depends_on:
- db
- redis
volumes:
- ./volumes/app/storage:/app/api/storage
networks:
- ssrf_proxy_network
- default
- langfuse_network # 追加
worker:
image: langgenius/dify-api:0.9.1
restart: always
environment:
<<: *shared-api-worker-env
MODE: worker
depends_on:
- db
- redis
volumes:
- ./volumes/app/storage:/app/api/storage
networks:
- ssrf_proxy_network
- default
- langfuse_network # 追加
ネットワーク設定にも追加
※/networks で検索
networks:
ssrf_proxy_network:
driver: bridge
internal: true
milvus:
driver: bridge
opensearch-net:
driver: bridge
internal: true
langfuse_network: # 追加
name: langfuse_network # 追加
external: true # 追加
Dify を再起動
docker-compose down
docker-compose up -d
docker-compose ps
Langfuse と Dify の連携
アカウントを作成する
※なぜか localhost にリダイレクトされたので、アクセスできないと思ったら URL を確認してグローバル IP アドレスに戻して再接続すること
初期設定
API Key を作成する
Dify の設定画面に移動
※Host は、以下を設定する
http://langfuse-server:3000
トレーシングの動作確認
※Dify のチャットボットを起動して、何か質問する
Langfuse の画面に移動し、Trace を確認する
チャット内容や Token 数が確認できる
Dify と Bedrock Knowledge Base の連携エビデンス
- Dify には、Knowledge Base の機能はありますが、Confluence や SharePoint をソースにはできません
- そのため、カスタムツールの機能で、Bedrock Knowledge Base と連携します
- カスタムツールで、REST API (POST メソッド) を使用し、API Gateway (Lambda) を経由して Bedrock Knowledge Base にアクセスします
- Bedrock Knowledge Base は、Confluence や SharePoint をソースにできます
Bedrock Knowledge Base を構築
以下の CloudFormation テンプレートを使用して、Bedrock Knowledge Base を構築します
- S3 のファイルをデータソースにする
- Aurora Serverless をベクトルデータベースにする
※Bedrock Knowledge Base への接続テストをしたいため、ここでは SharePoint ではなく S3 をデータソースにします
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Network
Parameters:
- VPCName
- PrivateSubnetName1A
- PrivateSubnetName1C
- PrivateSubnetRegion1A
- PrivateSubnetRegion1C
- PrivateSubnetRouteTableName
- Label:
default: Aurora
Parameters:
- AuroraSecurityGroupName
- AuroraSubnetGroupName
- AuroraName
- DBUserName
- Label:
default: SecretsManager
Parameters:
- SecretsManagerName
- DBBedrockUser
- DBBedrockPassword
- Label:
default: S3
Parameters:
- S3Name
- S3LambdaName
- S3IAMRoleName
- S3IAMPolicyName
- S3CloudWatchLogsName
- Label:
default: Lambda
Parameters:
- ExecSQLLambdaRoleName
- ExecSQLLambdaeName
- Label:
default: KnowledgeBase
Parameters:
- KnowledgeBaseIAMRoleName
- KnowledgeBaseIAMPolicyName
- KnowledgeBaseName
- KnowledgeBaseEmbeddingModel
- KnowledgeBaseDataSourceName
Parameters:
VPCName:
Type: String
Default: "KnowledgeBase-VPC"
PrivateSubnetName1A:
Type: String
Default: "KnowledgeBase-PrivateSubnet1A"
PrivateSubnetName1C:
Type: String
Default: "KnowledgeBase-PrivateSubnet1C"
PrivateSubnetRegion1A:
Type: String
Default: "ap-northeast-1a"
PrivateSubnetRegion1C:
Type: String
Default: "ap-northeast-1c"
PrivateSubnetRouteTableName:
Type: String
Default: "KnowledgeBase-PrivateSubnetRouteTable"
AuroraSubnetGroupName:
Type: String
Default: "KnowledgeBase-AuroraSubnetGroup"
AuroraSecurityGroupName:
Type: String
Default: "KnowledgeBase-AuroraSecurityGroup"
AuroraName:
Type: String
Default: "knowledgebase"
DBUserName:
Type: String
Default: "postgresql"
SecretsManagerName:
Type: String
Default: "knowledge-base"
DBBedrockUser:
Type: String
Default: "bedrock_user"
DBBedrockPassword:
Type: String
Default: "bedrock_password"
S3Name:
Type: String
Default: "knowledgebase-datasource"
S3LambdaName:
Type: String
Default: "delete-knowledgebase-datasource"
S3IAMRoleName:
Type: String
Default: "delete-knowledgebase-datasource"
S3IAMPolicyName:
Type: String
Default: "delete-knowledgebase-datasource"
S3CloudWatchLogsName:
Type: String
Default: "delete-knowledgebase-datasource"
ExecSQLLambdaRoleName:
Type: String
Default: "KnowledgeBase-ExecSQLLambda-Role"
ExecSQLLambdaeName:
Type: String
Default: "KnowledgeBase-ExecSQLLambda"
KnowledgeBaseIAMRoleName:
Type: String
Default: "KnowledgeBase-IAMRole"
KnowledgeBaseIAMPolicyName:
Type: String
Default: "KnowledgeBase-IAMPolicy"
KnowledgeBaseName:
Type: String
Default: "KnowledgeBase"
KnowledgeBaseEmbeddingModel:
Type: String
Default: "amazon.titan-embed-text-v1"
KnowledgeBaseDataSourceName:
Type: String
Default: "KnowledgeBaseDataSource"
Resources:
VPC:
Type: "AWS::EC2::VPC"
Properties:
CidrBlock: "10.0.0.0/16"
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: "Name"
Value: !Ref VPCName
PrivateSubnet1A:
Type: "AWS::EC2::Subnet"
Properties:
VpcId: !Ref VPC
CidrBlock: "10.0.1.0/24"
AvailabilityZone: !Ref PrivateSubnetRegion1A
MapPublicIpOnLaunch: false
Tags:
- Key: "Name"
Value: !Ref PrivateSubnetName1A
PrivateSubnet1C:
Type: "AWS::EC2::Subnet"
Properties:
VpcId: !Ref VPC
CidrBlock: "10.0.2.0/24"
AvailabilityZone: !Ref PrivateSubnetRegion1C
MapPublicIpOnLaunch: false
Tags:
- Key: "Name"
Value: !Ref PrivateSubnetName1C
PrivateSubnetRouteTable:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref VPC
Tags:
- Key: "Name"
Value: !Ref PrivateSubnetRouteTableName
PrivateSubnetRouteTableAssociation1A:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PrivateSubnet1A
RouteTableId: !Ref PrivateSubnetRouteTable
PrivateSubnetRouteTableAssociation1C:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PrivateSubnet1C
RouteTableId: !Ref PrivateSubnetRouteTable
DBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Ref AuroraSecurityGroupName
GroupDescription: Allow all traffic from self
VpcId: !GetAtt VPC.VpcId
DBSubnetGroup:
Type: "AWS::RDS::DBSubnetGroup"
Properties:
DBSubnetGroupName: !Ref AuroraSubnetGroupName
DBSubnetGroupDescription: Bedrock Aurora Subnet Group # required description
SubnetIds:
- !GetAtt PrivateSubnet1A.SubnetId
- !GetAtt PrivateSubnet1C.SubnetId
AuroraCluster:
Type: "AWS::RDS::DBCluster"
DeletionPolicy: Delete
Properties:
Engine: "aurora-postgresql"
EngineVersion: "15.6" # https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.requirements.html#aurora-serverless-v2-Availability
DBClusterIdentifier: "aurora-cluster"
DatabaseName: !Ref AuroraName
MasterUsername: !Ref DBUserName
ManageMasterUserPassword: true
StorageType: "aurora"
ServerlessV2ScalingConfiguration:
MinCapacity: 0.5
MaxCapacity: 1.0
VpcSecurityGroupIds:
- !GetAtt DBSecurityGroup.GroupId
DBSubnetGroupName: !Ref DBSubnetGroup
AvailabilityZones:
- !Ref PrivateSubnetRegion1A
- !Ref PrivateSubnetRegion1C
BackupRetentionPeriod: "7"
DeletionProtection: False
StorageEncrypted: False
EnableHttpEndpoint: true # boto3.client('rds-data') を使用する際に必要
AuroraInstance:
Type: "AWS::RDS::DBInstance"
Properties:
Engine: "aurora-postgresql"
DBClusterIdentifier: !Ref AuroraCluster
DBInstanceIdentifier: "aurora-instance"
DBInstanceClass: "db.serverless"
PubliclyAccessible: False
EnablePerformanceInsights: False
MonitoringInterval: "0"
SecretsManager:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Ref SecretsManagerName
Description: "Secret for the database user for Bedrock"
SecretString: !Sub '{ "username":"${DBBedrockUser}", "password":"${DBBedrockPassword}"}'
S3:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "${S3Name}-${AWS::AccountId}"
BucketEncryption:
ServerSideEncryptionConfiguration:
- BucketKeyEnabled: FALSE
VersioningConfiguration:
Status: "Suspended"
LifecycleConfiguration:
Rules:
- Id: "rule1"
NoncurrentVersionExpiration:
NoncurrentDays: 1
Status: "Enabled"
CorsConfiguration:
CorsRules:
- Id: corsRule1
MaxAge: 0
AllowedHeaders:
- "*"
AllowedMethods:
- PUT
- POST
AllowedOrigins:
- "https://www.metalmental.net"
S3Lambda:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Ref S3LambdaName
Handler: index.lambda_handler
Role: !GetAtt S3IAMRole.Arn
Runtime: python3.12
Timeout: 300
LoggingConfig:
LogGroup: !Ref S3CloudWatchLogs
Code:
ZipFile: |
import boto3
import cfnresponse
def lambda_handler(event, context):
s3 = boto3.resource('s3')
try:
if event['RequestType'] == 'Delete':
bucket = s3.Bucket(event['ResourceProperties']['BucketName'])
bucket.objects.all().delete()
bucket.object_versions.all().delete()
s3.Bucket(event['ResourceProperties']['BucketName']).delete()
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
except Exception as e:
print("Error: ", e)
cfnresponse.send(event, context, cfnresponse.FAILED, {})
S3LambdaInvoke:
Type: Custom::EmptyS3Bucket
Properties:
ServiceToken: !GetAtt S3Lambda.Arn
BucketName: !Ref S3
S3IAMRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Ref S3IAMRoleName
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/AmazonRDSDataFullAccess"
Policies:
- PolicyName: !Ref S3IAMPolicyName
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:*
Resource:
- !Sub ${S3.Arn}
- !Sub ${S3.Arn}/*
- Effect: Allow
Action:
- logs:*
Resource:
- !Sub ${S3CloudWatchLogs.Arn}
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource:
- !Sub ${AuroraCluster.MasterUserSecret.SecretArn}
S3CloudWatchLogs:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Ref S3CloudWatchLogsName
RetentionInDays: 1
SetupAuroraData:
Type: "Custom::SetupAuroraData"
DependsOn: AuroraInstance
Properties:
ServiceToken: !GetAtt ExecSQLLambda.Arn
ClusterArn: !GetAtt AuroraCluster.DBClusterArn
SecretArn: !GetAtt AuroraCluster.MasterUserSecret.SecretArn
DatabaseName: !Ref AuroraName
DatabasePassword: !Ref DBBedrockPassword
UserName: !Ref DBBedrockUser
ExecSQLLambda:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Ref ExecSQLLambdaeName
Handler: index.lambda_handler
Role: !GetAtt S3IAMRole.Arn
Runtime: python3.12
Timeout: 900
LoggingConfig:
LogGroup: !Ref S3CloudWatchLogs
Code:
ZipFile: |
import boto3
import cfnresponse
rds_data = boto3.client('rds-data')
def execute_statement(cluster_arn, database_name, secret_arn, sql):
response = rds_data.execute_statement(
resourceArn=cluster_arn,
database=database_name,
secretArn=secret_arn,
sql=sql
)
return response
def lambda_handler(event, context):
try:
cluster_arn = event['ResourceProperties']['ClusterArn']
secret_arn = event['ResourceProperties']['SecretArn']
database_name = event['ResourceProperties']['DatabaseName']
database_password = event['ResourceProperties']['DatabasePassword']
user_name = event['ResourceProperties']['UserName']
print(f"cluster_arn: {cluster_arn}")
print(f"secret_arn: {secret_arn}")
print(f"database_name: {database_name}")
print(f"database_password: {database_password}")
print(f"user_name: {user_name}")
if event['RequestType'] == 'Create':
print("Setup Aurora")
# https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/AuroraPostgreSQL.VectorDB.html#AuroraPostgreSQL.VectorDB.PreparingKB
# 4
create_extension = f"CREATE EXTENSION IF NOT EXISTS vector;"
execute_statement(cluster_arn, database_name, secret_arn, create_extension)
check_pg_vector = f"SELECT extversion FROM pg_extension WHERE extname='vector';"
execute_statement(cluster_arn, database_name, secret_arn, check_pg_vector)
# 5
create_schema = f"CREATE SCHEMA bedrock_integration;"
execute_statement(cluster_arn, database_name, secret_arn, create_schema)
# 6
create_role = f"CREATE ROLE {user_name} WITH PASSWORD '{database_password}' LOGIN;"
execute_statement(cluster_arn, database_name, secret_arn, create_role)
# 7
grant_schema = f"GRANT ALL ON SCHEMA bedrock_integration to {user_name};"
execute_statement(cluster_arn, database_name, secret_arn, grant_schema)
# 8
create_table = f"CREATE TABLE bedrock_integration.bedrock_kb (id uuid PRIMARY KEY, embedding vector(1536), chunks text, metadata json)"
execute_statement(cluster_arn, database_name, secret_arn, create_table)
# require
grant_table = f"GRANT ALL ON TABLE bedrock_integration.bedrock_kb TO {user_name};"
execute_statement(cluster_arn, database_name, secret_arn, grant_table)
# 9
create_index = f"CREATE INDEX on bedrock_integration.bedrock_kb USING hnsw (embedding vector_cosine_ops);"
execute_statement(cluster_arn, database_name, secret_arn, create_index)
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
except Exception as e:
cfnresponse.send(event, context, cfnresponse.FAILED, {'Message': str(e)})
KnowledgeBaseIAMRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Ref KnowledgeBaseIAMRoleName
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "sts:AssumeRole"
Principal:
Service:
- bedrock.amazonaws.com
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/AmazonRDSDataFullAccess"
Policies:
- PolicyName: !Ref KnowledgeBaseIAMPolicyName
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "bedrock:InvokeModel"
Resource:
- "*"
- Effect: Allow
Action:
- s3:*
Resource:
- !Sub ${S3.Arn}
- !Sub ${S3.Arn}/*
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource:
- !Ref SecretsManager
- Effect: Allow
Action:
- rds:*
Resource:
- !Sub ${AuroraCluster.DBClusterArn}
BedrockKnowledgeBase:
Type: AWS::Bedrock::KnowledgeBase
Properties:
Name: !Ref KnowledgeBaseName
RoleArn: !GetAtt KnowledgeBaseIAMRole.Arn
KnowledgeBaseConfiguration:
Type: VECTOR
VectorKnowledgeBaseConfiguration:
EmbeddingModelArn: !Sub
- "arn:${AWS::Partition}:bedrock:${AWS::Region}::foundation-model/${Model}"
- Model: !Ref KnowledgeBaseEmbeddingModel
StorageConfiguration:
Type: RDS
RdsConfiguration:
CredentialsSecretArn: !Ref SecretsManager
DatabaseName: !Ref AuroraName
FieldMapping:
PrimaryKeyField: "id"
VectorField: "embedding"
TextField: "chunks"
MetadataField: "metadata"
ResourceArn: !GetAtt AuroraCluster.DBClusterArn
TableName: "bedrock_integration.bedrock_kb"
DependsOn: SetupAuroraData
KnowledgeBaseDataSource:
Type: AWS::Bedrock::DataSource
Properties:
KnowledgeBaseId: !Ref BedrockKnowledgeBase
Name: !Ref KnowledgeBaseDataSourceName
DataSourceConfiguration:
Type: S3
S3Configuration:
BucketArn: !GetAtt S3.Arn
S3 にデータを格納
Knowledge Base として読み込ませたい適当なファイルを S3 に格納します
※対応しているファイルは以下からご確認ください
私は、最新の以下 3 つの記事を html ファイルとしてダウンロードし、S3 に格納しました
AWS Chatbot と Bedrock Knowledge Base (Agent) が連携できるようになった、という内容で、生成 AI が知らない情報です
S3 にファイルをアップロードします
Bedrock Knowledge Base で Sync します
テストします
「bedrock と teams は連携できますか」と質問します
問題なく Knowledge Base が機能していることが分かりました
Lambda を作成
- Lambda を作成
先ほどコンソールでテストしたことと同様なことを、コードでできるようにします
Python Boto3 を使用して、Bedrock Knowledge Base に質問できるようにします
- API Gateway を作成
REST API (POST メソッド) を作成し、1 で作成した Lambda を呼び出せるようにします
- Dify でカスタムツールを作成し、2 で作成した API Gateway を呼び出せるようにします
Lambda を作成します
タイムアウトを 30 秒にします
※API Gateway のタイムアウトがデフォルトで 29 秒のためです
環境変数を設定します
以下 2 つを設定します
キー | 値 |
---|---|
KNOWLEDGE_BASE_ID | xxxxxxxx |
MODEL_ARN | arn:aws:bedrock:ap-northeast-1::foundation-model/anthropic.claude-3-haiku-20240307-v1:0 |
※KNOWLEDGE_BASE_ID
の確認方法
※MODEL_ARN
の確認方法 (テキストモデルの Claude 3 Haiku を使用します)
Lambda の IAM Role に、Bedrock へのアクセス権限を追加します
以下の IAM Policy を追加します
AmazonBedrockFullAccess
Lambda のコードを以下のようにして、デプロイします
import os
import boto3
from botocore.config import Config
### Lambda環境変数の取得
try:
KNOWLEDGE_BASE_ID = os.environ["KNOWLEDGE_BASE_ID"]
MODEL_ARN = os.environ["MODEL_ARN"]
# "arn:aws:bedrock:{region}::foundation-model/" + MODEL_ID
except KeyError:
raise Exception("Environment variable is not defined.")
### Bedrock Agent Runtime クライアントの取得
try:
config = Config(
retries={"max_attempts": 30, "mode": "standard"},
read_timeout=900,
connect_timeout=900,
)
bedrock_agent_runtime_client = boto3.client("bedrock-agent-runtime", config=config)
except Exception as error:
raise Exception("Boto3 client error" + str(error))
### Bedrock Knowledge Base に質問する
def ask_knowledge_base(event):
try:
query = event["query"]
print(query)
# 「numberOfResults」 で、Knowledge Base からいくつのドキュメントを取得するかを指定できる
response = bedrock_agent_runtime_client.retrieve_and_generate(
input={
"text": query,
},
retrieveAndGenerateConfiguration={
"type": "KNOWLEDGE_BASE",
"knowledgeBaseConfiguration": {
"knowledgeBaseId": KNOWLEDGE_BASE_ID,
"modelArn": MODEL_ARN,
"retrievalConfiguration": {
"vectorSearchConfiguration": {
"numberOfResults": 3,
},
},
},
},
)
print(response)
# 取得したドキュメントを結合して、1 つの文字列にする
combined_text = '\n'.join(ref['content']['text'] for citation in response['citations'] for ref in citation['retrievedReferences'])
print(combined_text)
return combined_text
except Exception as e:
print(f"An error occurred: {e}")
raise
def lambda_handler(event, context):
try:
print(event)
combined_text = ask_knowledge_base(event)
return combined_text
except Exception as e:
print(f"An error occurred: {e}")
raise
テストします
「bedrock と teams は連携できますか」と質問します
{
"query": "bedrock と teams は連携できますか"
}
これで Lambda の設定は完了です
API Gateway を作成
POST メソッドを作成します
※Lambda の ARN は以下で確認できます
テストします
{
"query": "bedrock と teams は連携できますか"
}
POST リクエストにおいて、API キーの設定を必須にします
デプロイします
API キーを作成します
使用量プランを作成します
使用量プランとステージを関連付けます
使用量プランと API キーを関連付けます
API をエクスポートします
これで API Gateway の設定は完了です
Postman でテストします
※スキップしても問題ないです
Headers に以下を設定します
Key | Value |
---|---|
x-api-key | xxxxxxxx |
Body に以下を設定して、Send します
{
"query": "bedrock と teams は連携できますか"
}
※API キーの確認方法
※リクエスト URL の確認方法
Dify でカスタムツールを作成
エクスポートした OpenAPI スキーマを修正して入力します
openapi: "3.0.1"
info:
title: "DifyAPI"
version: "2024-10-04T04:37:52Z"
servers:
- url: "https://idocl2ul73.execute-api.ap-northeast-1.amazonaws.com/prod" # ここを正しいリクエストURLに変更する
variables:
basePath:
default: "prod"
paths:
/:
post:
summary: QueryBedrockKnowledgeBase # 追加
operationId: queryBedrockKnowledgeBase # 追加
requestBody: # 以下の requestBody を追加
description: "Request body containing the query"
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/query"
responses:
"200":
description: "200 response"
content:
application/json:
schema:
$ref: "#/components/schemas/Empty"
security:
- api_key: []
components:
schemas:
query: # 以下の query を追加
title: "query"
type: "object"
properties:
query:
type: "string"
required:
- query
Empty:
title: "Empty Schema"
type: "object"
securitySchemes:
api_key:
type: "apiKey"
name: "x-api-key"
in: "header"
認証方法に API Key を設定します
Key | Value |
---|---|
x-api-key | xxxxxxxx |
テストします
bedrock と teams は連携できますか
保存します
Knowledge Base を使用したチャットボットを作成
以下のようなフローにします
前提知識
/
スラッシュを入力することで、使用できる変数が表示されます
右クリックすることでブロック
を追加できます
開始ブロック
デフォルトで問題ないです
sys.query
変数にユーザの質問内容が入ります
変数は、全てのブロックから利用できます
質問分類器ブロック
入力変数は、sys.query
にします
クラス
で、条件を日本語で記載します
ユーザの質問に対して、自然言語で IF 文を定義すると考えてください
回答 2 ブロック
回答ブロックは、ユーザの質問に対して回答するアシスタントのメッセージ文を記載します
ここでは、Knowledge Base から情報が取得できなかった場合のブロックになるので、以下の日本語を直接返します
Knowledge Baseに情報はありませんでした
カスタムツールブロック
カスタムツールブロックは、カスタムツールを呼び出すブロックになります
入力変数は、sys.query
にします
出力変数に、API Gateway からのレスポンスが入ります
▷
からテストをすると、text
に Knowledge Base の回答が入っていることが分かります
LLM ブロック
生成 AI に質問して回答を得ます
コンテキスト
には、前のカスタムツールブロックで取得した Knowledge Base の回答を入力します
SYSTEM
とUSER
の 2 つの内容を元に生成 AI が回答を生成します
回答結果は、出力変数text
に入ります
回答ブロック
前の LLM ブロックで得た回答を出力します
/
で変数は入力できます
プレビュー
からテスト実行します
bedrock と teams は連携できますか
Entra ID ドメイン認証 設定エビデンス
Dify にドメイン認証を実装して欲しいと言われたので…
- Microsoft Entra ID は、Azure(クラウド)の AD サーバ
- AWS Cognito で、SAML を使用して、Microsoft Entra ID と連携
- ALB に Cognito を関連付け、リスナーに EC2 (Dify) を設定
Azure 設定
Azure にサインインし、エンタープライズアプリケーション
のページに遷移します
適当な名前でアプリケーションを作成します
SSO (SAML) 設定をします
アプリのフェデレーション メタデータ URL
をメモしておきます
認証させたいユーザを追加します
ALB 設定
ALB を作成します (具体的な作成方法は割愛)
ターゲットグループは、Dify の EC2 を設定し、Security Group で、Dify の EC2 から ALB への通信を許可します
※Cognito は、HTTPS しか対応していないため、ACM でSSL/TLS 証明書
を発行し、Route53
でレコードを登録して、HTTPS で通信するようにしてください
※ALB と EC2 の Security Group 設定例
※Route 53 設定例
AWS Cognito 設定
SES の構築方法は割愛します
※Cognito で E メールを送信
を選択しても問題ないです
※SAML 属性に以下を設定します
これは、Entra ID から提供されるユーザ情報(email)を Cognito のユーザ情報にマッピングし、Cognito がユーザ情報を正しく解釈するための設定です
以下を設定すると、Entra ID に対して Email を要求し、取得することが可能です
その他に要求できる属性は、以下のページを参考にしてください
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress
※メタデータドキュメントのエンドポイント URL を入力
には、Azure でメモしておいたアプリのフェデレーション メタデータ URL
を入力します
許可されているコールバック URL には、以下を設定します
https://ALBのDNS名/oauth2/idpresponse
以上で作成します
Azure と Cognito の連携
識別子 (エンティティ ID)
と応答 URL (Assertion Consumer Service URL)
を設定します
識別子
urn:amazon:cognito:sp:Cognito ユーザープール ID
応答 URL
https://Cognito ドメイン/saml2/idpresponse
テストして、取得できる属性を確認します
ALB のリスナーで、Cognito を関連付けます
ALB にアクセスして、Cognito のサインイン画面にリダイレクトされることを確認します
以上です
お疲れ様でした (o_ _)o
Discussion
これってEntraIdでログインした後に、さらにDifyのログイン必要な仕組みでしょうか?