Amazon Bedrock RAG 環境用 AWS CloudFormation テンプレート series 3 Bedrock 編

こんにちは、広野です。

本記事はシリーズもので、以下の記事の続編です。

以前、以下の記事で Amazon Bedrock や Agents for Amazon Bedrock を使用した最小構成 RAG 環境構築を紹介しておりました。当時はAmazon Bedrock 関連のリソースを一部 AWS CloudFormation ではデプロイできなかったのですが、今はサポートされたためにできるようになりました。

当時の構成を現在は変更しており、Knowledge Base に使用するデータベースを Amazon OpenSearch Serverless から Aurora Serverless v2 Postgresql に変更したり、モデルを Claude 3.5 Sonnet に変更したりしています。

本シリーズ記事では、環境構築用の AWS CloudFormation のサンプルテンプレートを 3 記事に分けて紹介します。説明を分割するため、テンプレートを3つに分けていますのでご了承ください。

3回目は Amazon Bedrock 編です。

本記事で取り扱う構成

RAG 環境全体の構成

以下のアーキテクチャで RAG アプリケーションを構築しています。このうち、赤枠の部分が本シリーズ記事で取り扱う箇所です。すみません、当初 Amazon SQS まで含んでおりましたが、コード量が多くなってしまうため今回から割愛いたしました。

series 3 Bedrock 編では、series 2 で構築した Amazon Aurora Serverless v2 Postgresql を Agents for Amazon Bedrock に Knowledge Base として登録し、RAG を使用しない問い合わせ (一般検索) 用の AWS Lambda 関数、および Agents for Amazon Bedrock に問い合わせるための AWS Lambda 関数 URL をデプロイします。

Agents for Amazon Bedrock の構成

Agents for Amazon Bedrock は、ユーザーからの問い合わせの内容から、裏にあるどのナレッジから回答を得るべきか仕分けの判断をしてくれます。ただし、判断基準となる情報は提供してあげないといけないので、それを自然言語で設定します。

  • 「SCSK」に関する問い合わせであった場合は、Knowledge Base を確認するようにします。(RAG 検索ルート)
  • それ以外の問い合わせであれば、AWS Lambda 関数を呼び出します。この関数は Amazon Bedrock Claude モデルに問い合わせます。(一般検索ルート)
  • アプリから Agents for Amazon Bedrock に問い合わせるためには、それ用の AWS Lambda 関数が必要です。ただし関数だけでは API から問い合わせを受け付けられないので、Lambda 関数 URL で公開しています。代わりに API Gateway でもかまいませんが、ストリームレスポンスに対応していなかったため Node.js の関数 URL にしました。
  • AWS Lambda 関数 URL には、CORS 設定のためドメイン情報を設定しています。ドメイン名などの情報は、AWS CloudFormation テンプレートのパラメータとして入力するようにしています。
  • AWS Lambda 関数 URL の認証については本記事では割愛します。以下の参考記事をご覧ください。

AWS CloudFormation テンプレート

図に掲載している赤字部分を今回のテンプレートで作成しています。一部、前回のテンプレートで作成したスタックからの情報をインポートしている箇所があります。

使用する Amazon Bedrock (一般検索用) のリージョンや Anthropic Claude モデルのバージョンは簡単に変更可能にするため、パラメータ化しています。技術の進歩が著しいので。

AWSTemplateFormatVersion: 2010-09-09
Description: The CloudFormation template that creates an Agent for Amazon Bedrock, a Bedrock Knowledge base, and a Lambda function URL.

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  SubName:
    Type: String
    Description: System sub name of sample. (e.g. test)
    Default: test
    MaxLength: 10
    MinLength: 1

  DomainName:
    Type: String
    Description: Domain name for URL.
    Default: scskexample.com
    MaxLength: 40
    MinLength: 5
    AllowedPattern: "[^\\s@]+\\.[^\\s@]+"

  SubDomainName:
    Type: String
    Description: Sub domain name for URL. (e.g. xxx of xxx.scskexample.com)
    Default: xxx
    MaxLength: 20
    MinLength: 1

  BedrockAgentAliasName:
    Type: String
    Description: The Alias name of Agents for Amazon Bedrock.
    Default: Default
    MaxLength: 20
    MinLength: 1

  BedrockRegion:
    Type: String
    Description: The region name you use for Amazon Bedrock Claude 3 model. (e.g. ap-northeast-1)
    Default: ap-northeast-1
    MaxLength: 50
    MinLength: 1

  ClaudeModelId:
    Type: String
    Description: The Claude 3 model ID. (e.g. anthropic.claude-3-5-sonnet-20240620-v1:0)
    Default: anthropic.claude-3-5-sonnet-20240620-v1:0
    MaxLength: 100
    MinLength: 1

Resources:
# ------------------------------------------------------------#
# Bedrock Knowledge Base
# ------------------------------------------------------------#
  BedrockKnowledgeBase:
    Type: AWS::Bedrock::KnowledgeBase
    Properties:
      Name: !Sub sample-${SubName}-kb
      Description: !Sub RAG Knowledge Base for sample-${SubName}
      KnowledgeBaseConfiguration:
        Type: VECTOR
        VectorKnowledgeBaseConfiguration:
          EmbeddingModelArn: !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/amazon.titan-embed-text-v1
      RoleArn:
        Fn::ImportValue:
          !Sub sample-${SubName}-IAMRoleBedrockKbArn
      StorageConfiguration:
        Type: RDS
        RdsConfiguration:
          CredentialsSecretArn:
            Fn::ImportValue:
              !Sub sample-${SubName}-SecretAurora
          DatabaseName: bedrockragkb
          FieldMapping:
            MetadataField: metadata
            PrimaryKeyField: id
            TextField: chunks
            VectorField: embedding
          ResourceArn:
            Fn::ImportValue:
              !Sub sample-${SubName}-AuroraDBClusterArn
          TableName: bedrock_integration.bedrock_kb
      Tags:
        Cost: !Sub sample-${SubName}

  BedrockKnowledgeBaseDataSource:
    Type: AWS::Bedrock::DataSource
    Properties:
      Name: !Sub sample-${SubName}-kb-datasource
      Description: !Sub RAG Knowledge Base Data Source for sample-${SubName}
      KnowledgeBaseId: !Ref BedrockKnowledgeBase
      DataDeletionPolicy: RETAIN
      DataSourceConfiguration:
        Type: S3
        S3Configuration:
          BucketArn:
            Fn::ImportValue:
              !Sub sample-${SubName}-S3BucketKbDatasourceArn

# ------------------------------------------------------------#
# Agents for Amazon Bedrock
# ------------------------------------------------------------#
  BedrockAgent:
    Type: AWS::Bedrock::Agent
    Properties:
      AgentName: !Sub sample-${SubName}
      Description : !Sub The agent for sample-${SubName} to assign the appropriate knowledge base or action group.
      AgentResourceRoleArn: !GetAtt BedrockAgentRole.Arn
      FoundationModel: "anthropic.claude-v2:1"
      Instruction: |
        あなたは優秀なAIアシスタントです。ユーザーの指示には日本語で回答してください。SCSKに関する情報が必要な場合はナレッジベースから情報を取得してください。それ以外の問い合わせには、Action Group に設定している Claude foundation model を使用して回答してください。
      KnowledgeBases:
        - KnowledgeBaseId: !Ref BedrockKnowledgeBase
          KnowledgeBaseState: ENABLED
          Description: Knowledge Base for the information related to SCSK
      ActionGroups:
        - ActionGroupName: UserInputAction
          ActionGroupState: ENABLED
          ParentActionGroupSignature: AMAZON.UserInput
        - ActionGroupName: LambdaBedrockAgentAgClaude
          ActionGroupState: ENABLED
          ActionGroupExecutor:
            Lambda: !GetAtt LambdaBedrockAgentAgClaude.Arn
          FunctionSchema:
            Functions:
              - Name: LambdaBedrockAgentAgClaude
                Description: "Lambda Function to invoke Bedrock Claude foundation model triggered from Bedrock Agent"
                Parameters:
                  url:
                    Description: "Invoke the Claude foundation model to answer for a general query except for the related to SCSK."
                    Required: false
                    Type: string
      Tags:
        Cost: !Sub sample-${SubName}
    DependsOn:
      - LambdaBedrockAgentAgClaude
      - BedrockAgentRole

  BedrockAgentAlias:
    Type: AWS::Bedrock::AgentAlias
    Properties:
      AgentAliasName: !Ref BedrockAgentAliasName
      AgentId: !Ref BedrockAgent
      Description: Default alias 2024-07-13 1
      Tags:
        Cost: !Sub sample-${SubName}
    DependsOn:
      - BedrockAgent

# ------------------------------------------------------------#
# Bedrock Agent Role (IAM)
# ------------------------------------------------------------#
  BedrockAgentRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub AmazonBedrockExecutionRoleForAgents_sample-${SubName}
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: "sts:AssumeRole"
            Principal:
              Service: bedrock.amazonaws.com
            Condition:
              StringEquals:
                "aws:SourceAccount": !Sub ${AWS::AccountId}
              ArnLike:
                "aws:SourceArn": !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      Policies:
        - PolicyName: !Sub AmazonBedrockExecutionPolicyForAgents_sample-${SubName}
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - "bedrock:InvokeModel"
                Resource:
                  - !Sub "arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude*"
              - Effect: Allow
                Action:
                  - "bedrock:Retrieve"
                  - "bedrock:RetrieveAndGenerate"
                Resource:
                  - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:knowledge-base/${BedrockKnowledgeBase}"
    DependsOn:
      - BedrockKnowledgeBase

# ------------------------------------------------------------#
# Lambda Execution Role (IAM)
# ------------------------------------------------------------#
  LambdaBedrockInvocationRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub sample-LambdaBedrockInvocationRole-${SubName}
      Description: This role allows Lambda functions to invoke Bedrock.
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
              - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
      Policies:
        - PolicyName: !Sub sample-LambdaBedrockInvocationPolicy-${SubName}
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - "bedrock:InvokeModel"
                  - "bedrock:InvokeModelWithResponseStream"
                Resource:
                  - !Sub "arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude*"

  LambdaBedrockAgentInvocationRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub sample-LambdaBedrockAgentInvocationRole-${SubName}
      Description: This role allows Lambda functions to invoke Bedrock Agent.
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
              - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
      Policies:
        - PolicyName: !Sub sample-LambdaBedrockAgentInvocationPolicy-${SubName}
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - "bedrock:InvokeAgent"
                Resource:
                  - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent-alias/${BedrockAgent}/*"
    DependsOn:
      - BedrockAgent

  LambdaBedrockAgentAgClaudeInvocationRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub sample-LambdaBedrockAgentAgClaudeInvocationRole-${SubName}
      Description: This role allows Lambda functions to invoke Bedrock Claude FM.
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
              - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
      Policies:
        - PolicyName: !Sub sample-LambdaBedrockAgentAgClaudeInvocationPolicy-${SubName}
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - "bedrock:InvokeModel"
                  - "bedrock:InvokeModelWithResponseStream"
                Resource:
                  - !Sub "arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude*"

# ------------------------------------------------------------#
# Lambda
# ------------------------------------------------------------#
  LambdaBedrock:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub sample-Bedrock-${SubName}
      Description: !Sub Lambda Function to invoke Bedrock for sample-${SubName}
      Architectures:
        - x86_64
      Runtime: nodejs20.x
      Timeout: 180
      MemorySize: 128
      Role: !GetAtt LambdaBedrockInvocationRole.Arn
      Handler: index.handler
      Tags:
        - Key: Cost
          Value: !Sub sample-${SubName}
      Code:
        ZipFile: !Sub |
          const { BedrockRuntimeClient, InvokeModelWithResponseStreamCommand } = require("@aws-sdk/client-bedrock-runtime");
          const bedrock = new BedrockRuntimeClient({region: "${BedrockRegion}"});
          exports.handler = awslambda.streamifyResponse(async (event, responseStream, _context) => {
            try {
              const args = JSON.parse(event.body);
              if (args.prompt == '') {
                responseStream.write("No prompt provided.");
                responseStream.end();
              }
              const body = {
                "max_tokens": 3000,
                "temperature": 0.5,
                "top_k": 250,
                "top_p": 1,
                "anthropic_version": "bedrock-2023-05-31",
                "system": "質問文に対して適切な回答をしてください。",
                "messages": [
                  {
                    "role": "user",
                    "content": [
                      {
                        "type": "text",
                        "text": args.prompt
                      }
                    ]
                  }
                ]
              };
              const input = {
                modelId: '${ClaudeModelId}',
                accept: 'application/json',
                contentType: 'application/json',
                body: JSON.stringify(body)
              };
              const command = new InvokeModelWithResponseStreamCommand(input);
              const apiResponse = await bedrock.send(command);
              let completeMessage = "";
              for await (const item of apiResponse.body) {
                const chunk = JSON.parse(new TextDecoder().decode(item.chunk.bytes));
                const chunk_type = chunk.type;
                if (chunk_type === "content_block_delta") {
                  const text = chunk.delta.text;
                  completeMessage = completeMessage + text;
                  responseStream.write(text);
                }
              }
              responseStream.end();
            } catch (error) {
              console.error(error);
              responseStream.write('error');
              responseStream.end();
            }
          });
    DependsOn:
      - LambdaBedrockInvocationRole

  LambdaUrlBedrock:
    Type: AWS::Lambda::Url
    Properties:
      AuthType: AWS_IAM
      Cors:
        AllowCredentials: false
        AllowHeaders:
          - "*"
        AllowMethods:
          - POST
        AllowOrigins:
          - !Sub https://${SubDomainName}.${DomainName}
        ExposeHeaders:
          - "*"
        MaxAge: 0
      InvokeMode: RESPONSE_STREAM
      TargetFunctionArn: !GetAtt LambdaBedrock.Arn
    DependsOn:
      - LambdaBedrock

  LambdaBedrockAgent:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub sample-BedrockAgent-${SubName}
      Description: !Sub Lambda Function to invoke Bedrock Agent for sample-${SubName}
      Architectures:
        - x86_64
      Runtime: nodejs20.x
      Timeout: 600
      MemorySize: 128
      Role: !GetAtt LambdaBedrockAgentInvocationRole.Arn
      Handler: index.handler
      Tags:
        - Key: Cost
          Value: !Sub sample-${SubName}
      Code:
        ZipFile: !Sub |
          const { BedrockAgentRuntimeClient, InvokeAgentCommand } = require("@aws-sdk/client-bedrock-agent-runtime");
          const bedrockagent = new BedrockAgentRuntimeClient({region: "${AWS::Region}"});
          exports.handler = awslambda.streamifyResponse(async (event, responseStream, _context) => {
            try {
              // Query Bedrock Agent
              const args = JSON.parse(event.body);
              if (args.prompt == '') {
                responseStream.write("No prompt provided.");
                responseStream.end();
              }
              const agentInput = {
                "agentId": "${BedrockAgent}",
                "agentAliasId": "${BedrockAgentAlias.AgentAliasId}",
                "sessionId": args.jobid,
                "enableTrace": false,
                "endSession": false,
                "inputText": args.prompt,
                "sessionState": {
                  "promptSessionAttributes": {
                    "serviceid": args.serviceid,
                    "user": args.username,
                    "datetime": args.datetime
                  }
                }
              };
              const command = new InvokeAgentCommand(agentInput);
              const res = await bedrockagent.send(command);
              const actualStream = res.completion.options.messageStream;
              const chunks = [];
              for await (const value of actualStream) {
                const jsonString = new TextDecoder().decode(value.body);
                const base64encoded = JSON.parse(jsonString).bytes;
                const decodedString = Buffer.from(base64encoded,'base64').toString();
                try {
                  chunks.push(decodedString);
                  responseStream.write(decodedString);
                } catch (error) {
                  console.error(error);
                  responseStream.write(null);
                  responseStream.end();
                }
              }
              responseStream.end();
            } catch (error) {
              console.error(error);
              responseStream.write('error');
              responseStream.end();
            }
          });
    DependsOn:
      - LambdaBedrockAgentInvocationRole
      - BedrockAgentAlias

  LambdaUrlBedrockAgent:
    Type: AWS::Lambda::Url
    Properties:
      AuthType: AWS_IAM
      Cors:
        AllowCredentials: false
        AllowHeaders:
          - "*"
        AllowMethods:
          - POST
        AllowOrigins:
          - !Sub https://${SubDomainName}.${DomainName}
        ExposeHeaders:
          - "*"
        MaxAge: 0
      InvokeMode: RESPONSE_STREAM
      TargetFunctionArn: !GetAtt LambdaBedrockAgent.Arn
    DependsOn:
      - LambdaBedrockAgent

  LambdaBedrockAgentAgClaude:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub sample-BedrockAgentAgClaude-${SubName}
      Description: !Sub Lambda Function to invoke Bedrock Claude model triggered from Bedrock Agent action group for sample-${SubName}
      Architectures:
        - x86_64
      Runtime: python3.12
      Timeout: 300
      MemorySize: 128
      Role: !GetAtt LambdaBedrockAgentAgClaudeInvocationRole.Arn
      Handler: index.lambda_handler
      Tags:
        - Key: Cost
          Value: !Sub sample-${SubName}
      Code:
        ZipFile: !Sub |
          import boto3
          import json
          bedrock = boto3.client('bedrock-runtime', region_name='${BedrockRegion}')
          def lambda_handler(event, context):
            print(event)
            # Invoke Bedrock
            body = {
              "max_tokens": 3000,
              "temperature": 0.5,
              "top_k": 250,
              "top_p": 1,
              "anthropic_version": "bedrock-2023-05-31",
              "system": "質問文に対して適切な回答をしてください。",
              "messages": [
                {
                  "role": "user",
                  "content": [
                    {
                      "type": "text",
                      "text": event['inputText']
                    }
                  ]
                }
              ]
            }
            res = bedrock.invoke_model(
              body=json.dumps(body),
              contentType='application/json',
              accept='application/json',
              modelId='${ClaudeModelId}'
            )
            resbody = json.loads(res['body'].read())['content'][0].get('text', '適切な回答が見つかりませんでした。')
            return {
              "messageVersion": "1.0",
              "response": {
                "actionGroup": event["actionGroup"],
                "function": event["function"],
                "functionResponse": {
                  "responseBody": {
                    "TEXT": {
                      "body": resbody
                    }
                  }
                }
              },
              "sessionAttributes": event["sessionAttributes"],
              "promptSessionAttributes": event["promptSessionAttributes"]
            }
    DependsOn:
      - LambdaBedrockAgentAgClaudeInvocationRole

  LambdaBedrockAgentAgClaudePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt LambdaBedrockAgentAgClaude.Arn
      Action: lambda:InvokeFunction
      Principal: bedrock.amazonaws.com
      SourceAccount: !Sub ${AWS::AccountId}
      SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/${BedrockAgent}"
    DependsOn:
      - BedrockAgent

# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#
Outputs:
# Lambda
  LambdaBedrockArn:
    Value: !GetAtt LambdaBedrock.Arn
    Export:
      Name: !Sub sample-${SubName}-LambdaBedrockArn
  LambdaBedrockUrl:
    Value: !GetAtt LambdaUrlBedrock.FunctionUrl
    Export:
      Name: !Sub sample-${SubName}-LambdaBedrockUrl
  LambdaBedrockAgentArn:
    Value: !GetAtt LambdaBedrockAgent.Arn
    Export:
      Name: !Sub sample-${SubName}-LambdaBedrockAgentArn
  LambdaBedrockAgentUrl:
    Value: !GetAtt LambdaUrlBedrockAgent.FunctionUrl
    Export:
      Name: !Sub sample-${SubName}-LambdaBedrockAgentUrl

Agents for Amazon Bedrock 変更時の作業

上述の AWS CloudFormation テンプレートを流しただけで一旦 Agents for Amazon Bedrock の一連の構成が出来上がりますが、何か構成を変更したときには、バージョン管理の機能があるためにエイリアスの情報も更新しておく必要があります。以下の Description の部分を、何か記述ルールを決めて同時に変えていきましょう。

  BedrockAgentAlias:
    Type: AWS::Bedrock::AgentAlias
    Properties:
      AgentAliasName: !Ref BedrockAgentAliasName
      AgentId: !Ref BedrockAgent
      Description: Default alias 2024-07-13 1

本記事の範囲はこれで終了です。

まとめ

いかがでしたでしょうか?

あまり説明はなく AWS CloudFormation テンプレートを読んで下さい的な内容になっていますが、そもそもテンプレート化したい人でないとこの記事は読まないと思いますので、ある程度読める方がいらっしゃっているのかな、と思います。その他、AWS Lambda 関数のつくりもなにげに参考になるかと思っております。

本記事が皆様のお役に立てれば幸いです。

著者について
広野 祐司

AWS サーバーレスアーキテクチャを駆使して社内クラウド人材育成アプリとコンテンツづくりに勤しんでいます。React で SPA を書き始めたら快適すぎて、他の言語には戻れなくなりました。サーバーレス & React 仲間を増やしたいです。AWSは好きですが、それよりもフロントエンド開発の方が好きでして、バックエンド構築を簡単にしてくれたAWSには感謝の気持ちの方が強いです。
取得資格:AWS 認定は13資格、ITサービスマネージャ、ITIL v3 Expert 等
2020 - 2024 Japan AWS Top Engineer 受賞
2022 - 2024 AWS Ambassador 受賞
2023 当社初代フルスタックエンジニア認定
好きなAWSサービス:AWS Amplify / AWS AppSync / Amazon Cognito / AWS Step Functions / AWS CloudFormation

広野 祐司をフォローする

クラウドに強いによるエンジニアブログです。

SCSKクラウドサービス(AWS)は、企業価値の向上につながるAWS 導入を全面支援するオールインワンサービスです。AWS最上位パートナーとして、多種多様な業界のシステム構築実績を持つSCSKが、お客様のDX推進を強力にサポートします。

AI・MLAWSクラウドソリューション
シェアする
タイトルとURLをコピーしました