🗂

Dify導入 個人メモ

2024/10/03に公開
1

急に RAG チャットボットを導入することになったので…
参考になれば幸いです
定期的に更新します
※2024/10/03 時点での情報です

Install Docker Compose

https://zenn.dev/rock_penguin/articles/28875c7b0a5e30

https://github.com/docker/compose/releases/

※以下の v2.29.7 をインストール
https://github.com/docker/compose/releases/download/v2.29.7/docker-compose-linux-x86_64

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

https://docs.dify.ai/ja-jp/getting-started/install-self-hosted/docker-compose#difyno

https://github.com/langgenius/dify/blob/main/docker/docker-compose.yaml

https://docs.docker.jp/compose/profiles.html

※以下のように記載があり、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}

https://docs.dify.ai/ja-jp/getting-started/readme/features-and-specifications

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 くらいあっても良いと思う

ログ・トレーシング

https://zenn.dev/aidemy/articles/13ceca39b0ea6c

https://langfuse.com/pricing

Langfuse が無料で使用可能

Dify 設定エビデンス

サインイン

適当なメールアドレスとパスワードでサインインする
※メールアドレスのチェックは行われないため、存在しないメールアドレスでも問題ないです



Dify 設定

設定画面に移動

時刻設定

LLM (Bedrock) 設定
※モデルへのアクセスは有効であること

※適当に 1 つモデル ID をコピーします

※IAM Role が割り当てられているため、アクセスキー設定は不要です

各モデル設定

シンプルなチャットボットを作成 (動作確認)



※モデルを選択

※更新する (何か設定変更したら更新して保存するように!)

チャットボットを起動

チャットしてみる


Claude 3 Haiku は、ChatGPT をベースに作られていることが分かりました…

Langfuse 設定エビデンス

Langfuse 起動

Dify と同様に Docker Compose で起動する

https://langfuse.com/docs/deployment/local

https://note.com/jolly_dahlia842/n/n406dec2c11d9

sudo -s
cd
git clone https://github.com/langfuse/langfuse.git
cd langfuse
vi docker-compose.yml

以下に修正

  1. ポート 3000 は、Dify が使用しているため、3002 に変更する
  2. 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 をソースにできます

https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/data-source-connectors.html

Bedrock Knowledge Base を構築

以下の CloudFormation テンプレートを使用して、Bedrock Knowledge Base を構築します

  • S3 のファイルをデータソースにする
  • Aurora Serverless をベクトルデータベースにする

※Bedrock Knowledge Base への接続テストをしたいため、ここでは SharePoint ではなく S3 をデータソースにします

https://dev.classmethod.jp/articles/cloudformation-knowledge-base-for-bedrock-with-aurora/

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 に格納します

※対応しているファイルは以下からご確認ください
https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/knowledge-base-ds.html

私は、最新の以下 3 つの記事を html ファイルとしてダウンロードし、S3 に格納しました
AWS Chatbot と Bedrock Knowledge Base (Agent) が連携できるようになった、という内容で、生成 AI が知らない情報です

https://aws.amazon.com/jp/about-aws/whats-new/2024/09/aws-chatbot-amazon-bedrock-agent-microsoft-teams-slack/
https://qiita.com/moritalous/items/b63d976c2c40af1c39e5
https://qiita.com/minorun365/items/3d6bb63a59fb10712948

S3 にファイルをアップロードします


Bedrock Knowledge Base で Sync します


テストします
「bedrock と teams は連携できますか」と質問します

問題なく Knowledge Base が機能していることが分かりました

Lambda を作成

https://acro-engineer.hatenablog.com/entry/2024/07/22/120000

  1. Lambda を作成
    先ほどコンソールでテストしたことと同様なことを、コードでできるようにします
    Python Boto3 を使用して、Bedrock Knowledge Base に質問できるようにします

  2. API Gateway を作成
    REST API (POST メソッド) を作成し、1 で作成した Lambda を呼び出せるようにします

  3. 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 を作成

https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-create-usage-plans.html
https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-key-usage-plan-oas.html



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 の回答を入力します
SYSTEMUSERの 2 つの内容を元に生成 AI が回答を生成します
回答結果は、出力変数textに入ります

回答ブロック

前の LLM ブロックで得た回答を出力します
/で変数は入力できます

プレビューからテスト実行します

bedrock と teams は連携できますか

Entra ID ドメイン認証 設定エビデンス

Dify にドメイン認証を実装して欲しいと言われたので…

  • Microsoft Entra ID は、Azure(クラウド)の AD サーバ
  • AWS Cognito で、SAML を使用して、Microsoft Entra ID と連携
  • ALB に Cognito を関連付け、リスナーに EC2 (Dify) を設定

https://www.beex-inc.com/blog/AzureAD_Cognito_20230915
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pools-saml-idp.html

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 を要求し、取得することが可能です
 その他に要求できる属性は、以下のページを参考にしてください
https://learn.microsoft.com/ja-jp/windows-server/identity/ad-fs/technical-reference/the-role-of-claims

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

GitHubで編集を提案

Discussion

ちょこおもちちょこおもち

これってEntraIdでログインした後に、さらにDifyのログイン必要な仕組みでしょうか?