React で Amazon Bedrock ベースの簡易生成 AI チャットボットをつくる [2025年7月版] 実装編2 API作成

こんにちは、広野です。

生成 AI 界隈の技術の進化がすさまじく、以前開発したチャットボットのアーキテクチャも陳腐化が見えてきました。この記事を執筆している時点での最新のアーキテクチャで改めて作り直してみたので、いくつかの記事に分けて紹介します。

今回 (3回目) は実装編 その1 API 作成編です。

大変恐縮ですが、AWS CloudFormation によるデプロイをしているので YAML テンプレートベースでの説明となります。ご了承ください。

前回の記事

アーキテクチャ概要については前回記事で紹介しています。こちらをご覧ください。

 

今回の説明範囲

アーキテクチャ図中、赤枠の部分を説明いたします。

バックエンド側の根幹となる API 部分の説明になります。主に以下の 3 つのパートに分かれます。

  • ユーザーからのプロンプト (問い合わせ) を受け付ける Amazon API Gateway REST API
  • Amazon Bedrock にプロンプトを投げて、回答を AWS AppSync Events に返す AWS Lambda 関数
  • ユーザーに Amazon Bedrock からの回答を細切れに返す AWS AppSync Events API

この構成の面白いところは、ユーザーとのやり取りが行きと帰りで異なるところです。

各リソースの説明

若干アーキテクチャ概要編の記事と重複する内容がありますが、ご容赦ください。

AWS AppSync

まずこちらの説明から始めます。AWS Lambda 関数は AWS AppSync Events API ができていることが前提で動くのと、この部分のセキュリティ設計が肝になるため、これを最初に説明すべきと考えました。

AWS AppSync Events は Pub/Sub 機能を、従来の AWS AppSync GraphQL API で必要だった GraphQL 無しで簡単に作成できるようになりました。その利点を活用すべく、今回は Amazon Bedrock からの細切れの回答 (ストリームレスポンス) を順次アプリに送る目的で、アプリから AWS AppSync Events API をサブスクライブしてもらい、そこに AWS Lambda 関数からストリームレスポンスをパブリッシュする構成にしています。

アプリは複数人が使用しますが、Amazon Bedrock からの回答を受け取れるのは、問い合わせをしたユーザーのみです。いわゆる Pub/Sub は 1対1、1対多、多対多のメッセージ配信ができるわけですが、当然ここでは 1対1 の構成にします。そのセキュリティを、「チャンネル」という概念を使用して設計します。

アーキテクチャ図に書いていますが、今回の構成では以下のチャンネル名にしています。

bedrock-stream-response/<CognitoユーザーID>/<セッションID>

チャンネルはスラッシュ (/) 区切りの階層構造を持つことができ、階層構造を使用したセキュリティ設計ができます。仕様上、第一階層は固定文字列になります。この固定名は名前空間と呼ばれます。あらかじめ AWS AppSync Events API で設定をしておきます。それだけしておけば、パブリッシャー、サブスクライバー側で共通認識した階層構造で Pub/Sub することで、メッセージの受け渡しをすることができます。

今回は、自分の問い合わせの回答を自分だけが受信したいので、Amazon Cognito のユーザー ID を第二階層に指定しています。かつ、1件1件の問い合わせごとに生成した一意の ID、セッション ID を第三階層にしています。それにより、そのユーザーのその問い合わせだけを受信するチャンネルをつくることができます。

各 ID の生成、授受の流れを図にすると以下のようになります。

セッション ID は同じ一連の問い合わせかどうかを識別するための ID なので、セキュリティ上の意味は持ちません。

Cognito ユーザー ID はなりすましを防ぐために重要です。まず、ユーザーがアプリにログインした際に自分の Cognito ユーザー ID を取得します。それをバックエンド (Amazon API Gateway, AWS AppSync) に安全に送信する必要があります。

Amazon API Gateway には Cognito ユーザー ID をパラメータとして渡すのではなく、それぞれの Cognito 認証に使用した証明書、つまり検証済みの証明書から取得することで、取得した Cognito ユーザー ID が改ざんされていないことを担保できます。

AWS AppSync 側では、サブスクライブ開始時に AppSync 側で取得した証明書チェック済み Cognito ユーザー ID が、サブスクライバー (アプリ) がサブスクライブしようとしているチャンネル内に書かれている Amazon Cognito ユーザー ID と一致するかチェックし、サブスクライブ可否を判断しています。この部分は、以下の AWS 公式ドキュメントにある onSubscribe handler という機能を使用して実装しています。ご丁寧に Cognito ユーザー ID によるサブスクライブ拒否のサンプルコードが紹介されているので助かりました。

少々情報です。以下のようにイベントハンドラー欄にコードを書いて onSubscribe handler を設定しているのですが、よく見ると「動作」欄が「無効」になっています。検証したところ、この状態でもイベントハンドラーは機能していましたので気にするのをやめました。画面のバグだと思われます。入れ違いで修正されていたらすみません。

 

話を戻します。

Amazon Cognito ユーザー ID もセッション ID も UUID を使用しており、容易に推測はしづらいようになっていますが、セキュリティ対策をしていないとなりすましができる状況にはあるので、このような Cognito ユーザー ID 授受設計をしています。

AWS Lambda 関数が AWS AppSync Events API にパブリッシュするときのセキュリティはシンプルな IAM ロールベースの認証にしています。というのは、この Lambda 関数は不特定多数のユーザーが共用するので、パブリッシュ先のチャンネルは第一階層を除いて変動するためです。そのため、チャンネル名の第一階層が一致すればパブリッシュを許可する設計にしています。

AWS Lambda 関数

AWS Lambda 関数は、Amazon API Gateway REST API, Amazon Bedrock, AWS AppSync Events API を仲介する役割になります。

Amazon Bedrock への問い合わせはストリームレスポンス対応の converse_stream API を使用します。ストリームレスポンスを適切に AWS AppSync Events API に流す部分が肝になります。この部分を中心に、コードベースでインラインで説明をします。

import json
import boto3
# AppSync Events API への接続には、requests モジュールを使用します。
import requests
from requests_aws_sign import AWSV4Sign
bedrock = boto3.client('bedrock-runtime')
session = boto3.session.Session()
# 今回は Amazon Nova micro モデルを使用します。cross region inference 用のモデル ID を指定するので注意が必要です。
model_id = 'apac.amazon.nova-micro-v1:0'
# AppSync Events API の HTTPS エンドポイントです。リアルタイムエンドポイントではないので注意。
endpoint = 'https://xxxxxxxxxxxxxxxxxxxxxxxxxxx.appsync-api.ap-northeast-1.amazonaws.com/event'
headers = {'Content-Type': 'application/json'}
def lambda_handler(event, context):
  try:
    credentials = session.get_credentials()
    auth = AWSV4Sign(credentials, 'ap-northeast-1', 'appsync')
    # API Gateway からのインプットを取得
    prompt = event['body']['prompt']
    sessionid = event['body']['sessionid']
    sub = event['sub'] # Cognito ユーザー ID
    # Amazon Bedrock への問い合わせフォーマット作成
    conversation = [
      {
        "role": "user",
        "content": [{"text": prompt}]
      }
    ]
    # Amazon Bedrock に問い合わせ
    stream = bedrock.converse_stream(
      modelId=model_id,
      messages=conversation,
      inferenceConfig={"maxTokens": 10000, "temperature": 0.5, "topP": 0.9}
    )
    # ここで、チャンクと言われる細切れのレスポンス単位で AppSync にパブリッシュしています。
    for chunk in stream["stream"]:
      if "contentBlockDelta" in chunk:
        text = chunk['contentBlockDelta']['delta']['text']
        payload = {
          "channel": "bedrock-stream-response/" + sub + "/" + sessionid,
          "events": [
            json.dumps({"message": text})
          ]
        }
        requests.post(endpoint, auth=auth, json=payload, headers=headers).json()
  except Exception as e:
    print(str(e))
    exit(1)

一連のストリームレスポンスが終了すると、AWS Lambda 関数も自動的に終了します。

AWS Lambda 関数の IAM ロールは後述の AWS CloudFormation テンプレートをご覧ください。そこで定義された権限を、コード内の session.get_credentials() で取得し、証明書を作成、requests モジュールで AWS AppSync Events API に投げています。今時点、AWS AppSync には boto3 で簡単にリクエストを投げられないのが難点です。

requests および requests_aws_sign モジュールは Lambda レイヤーを使用しないと Lambda 関数内で import できないので、あらかじめ作成しておく必要があります。AWS CloudFormation テンプレート内では、Amazon S3 バケットにモジュールの ZIP ファイルを置いてある前提になっています。requests モジュール ZIP の作成方法は以下の記事を参考にしてください。

参考までに、私が実行した requests モジュール作成時のコマンド抜粋を載せておきます。

pip install requests
pip install requests_aws_sign

cd /home/ec2-user/.pyenv/versions/3.13.1/lib/python3.13/site-packages

cp -r certifi /home/ec2-user/environment/python/
cp -r certifi-2024.8.30.dist-info /home/ec2-user/environment/python/
cp -r charset_normalizer /home/ec2-user/environment/python/
cp -r charset_normalizer-3.4.0.dist-info /home/ec2-user/environment/python/
cp -r dateutil /home/ec2-user/environment/python/
cp -r idna /home/ec2-user/environment/python/
cp -r idna-3.10.dist-info /home/ec2-user/environment/python/
cp -r jmespath /home/ec2-user/environment/python/
cp -r jmespath-1.0.1.dist-info /home/ec2-user/environment/python/
cp -r python_dateutil-2.9.0.post0.dist-info /home/ec2-user/environment/python/
cp -r requests /home/ec2-user/environment/python/
cp -r requests-2.32.3.dist-info /home/ec2-user/environment/python/
cp -r requests_aws_sign /home/ec2-user/environment/python/
cp -r requests_aws_sign-0.1.6.dist-info /home/ec2-user/environment/python/
cp -r s3transfer /home/ec2-user/environment/python/
cp -r s3transfer-0.10.4.dist-info /home/ec2-user/environment/python/
cp -r six.py /home/ec2-user/environment/python/
cp -r six-1.17.0.dist-info /home/ec2-user/environment/python/
cp -r urllib3 /home/ec2-user/environment/python/
cp -r urllib3-2.2.3.dist-info /home/ec2-user/environment/python/

cd /home/ec2-user/environment

zip -r requests2323.zip python

aws s3 cp ./requests2323.zip s3://xxxx-xxxx-sdk-ap-northeast-1/sdk/Python3.13/

Amazon API Gateway REST API

さて、本記事最後のトピック、Amazon API Gateway です。この REST API は少々トリッキーで、以下の AWS 公式ドキュメントで紹介されているように、AWS Lamba 関数を非同期呼び出しします。

 

非同期呼出することにより、AWS Lambda 関数を呼び出した後 API Gateway はその応答を待つことなく処理を終了します。API Gateway の呼び出し元であるアプリには、非同期実行をした旨のステータスコード 202 を返すようにしています。

今回の設計では Amazon Bedrock からのレスポンスは AWS AppSync Events 経由でアプリに返すので、Amazon API Gateway が AWS Lambda 関数からの戻りを待つ必要がないためです。また、Amazon Bedrock からのレスポンスが長くなっても Amazon API Gateway のタイムアウトに制限されることがなくなるという利点があります。

気を付けるべきところは、統合リクエストで以下を設定することです。

  • Lambda プロキシ統合を False にすること
  • ヘッダーに X-Amz-Invocation-Type を登録すること

さらに、HTTP ヘッダーの欄に X-Amz-Invocation-Type に ‘Event’ (※ ‘ も入れること!) を登録します。

また、入力パススルーを不可にしているので、マッピングテンプレートを明記する必要があります。今回は body に input すべてを入れて、追加で sub という項目に Amazon Cognito 認証済みの Cognito ユーザー ID を取得して入れるようにしています。これが AWS Lambda 関数からの AWS AppSync Events API パブリッシュ時に使用されます。

 

AWS CloudFormation テンプレート

ここまでの一連の構成を構築するテンプレート例です。細かい設定はこちらをご覧いただけたらと思います。

AWSTemplateFormatVersion: 2010-09-09
Description: The CloudFormation template that creates an API Gateway REST API, an AppSync Events API, a Lambda function, and relevant IAM roles.

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  SystemName:
    Type: String
    Description: System name. use lower case only. (e.g. example)
    Default: example
    MaxLength: 10
    MinLength: 1

  SubName:
    Type: String
    Description: System sub name. use lower case only. (e.g. prod or dev)
    Default: dev
    MaxLength: 10
    MinLength: 1

  DomainName:
    Type: String
    Description: Domain name for URL. xxxxx.xxx (e.g. example.com)
    Default: example.com
    MaxLength: 40
    MinLength: 5

  SubDomainName:
    Type: String
    Description: Sub domain name for URL. (e.g. example-prod or example-dev)
    Default: example-dev
    MaxLength: 20
    MinLength: 1

  ModelId:
    Type: String
    Description: The Gen AI foundation model ID. (e.g. apac.amazon.nova-micro-v1:0)
    Default: apac.amazon.nova-micro-v1:0
    MaxLength: 100
    MinLength: 1

  S3BucketNameSdk:
    Type: String
    Description: S3 bucket name in which you uploaded sdks for Lambda Layers. (e.g. example-dev-materials-999999999999-ap-northeast-1)
    Default: example-dev-materials-999999999999-ap-northeast-1
    MaxLength: 100
    MinLength: 1

  S3KeyRequestsSdk:
    Type: String
    Description: S3 key of requests.zip. Fill the exact key name if you renamed. (e.g. sdk/Python3.13/requests2323.zip)
    Default: sdk/Python3.13/requests2323.zip
    MaxLength: 100
    MinLength: 1

Resources:
# ------------------------------------------------------------#
# AppSync Events
# ------------------------------------------------------------#
  AppSyncApi:
    Type: AWS::AppSync::Api
    Properties:
      Name: !Sub appsync-event-api-${SystemName}-${SubName}
      EventConfig:
        AuthProviders:
          - AuthType: AMAZON_COGNITO_USER_POOLS
            CognitoConfig:
              AwsRegion: !Ref AWS::Region
              UserPoolId:
                Fn::ImportValue:
                  !Sub CognitoUserPoolId-${SystemName}-${SubName}
          - AuthType: AWS_IAM
        ConnectionAuthModes:
          - AuthType: AMAZON_COGNITO_USER_POOLS
          - AuthType: AWS_IAM
        DefaultPublishAuthModes:
          - AuthType: AWS_IAM
        DefaultSubscribeAuthModes:
          - AuthType: AMAZON_COGNITO_USER_POOLS
        LogConfig:
          LogLevel: ALL
          CloudWatchLogsRoleArn: !GetAtt AppSyncCloudWatchLogsPushRole.Arn
      OwnerContact: !Sub ${SystemName}-${SubName}
      Tags:
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}

  AppSyncChannelNamespaceBedrockSR:
    Type: AWS::AppSync::ChannelNamespace
    Properties:
      Name: bedrock-stream-response
      ApiId: !GetAtt AppSyncApi.ApiId
      CodeHandlers: |
        import { util } from '@aws-appsync/utils';
        export function onSubscribe(ctx) {
          const requested = ctx.info.channel.path;
          if (!requested.startsWith(`/bedrock-stream-response/${ctx.identity.sub}`)) {
            util.unauthorized();
          }
        }
      Tags:
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}

# ------------------------------------------------------------#
# AppSync CloudWatch Logs Invocation Role (IAM)
# ------------------------------------------------------------#
  AppSyncCloudWatchLogsPushRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub AppSyncCloudWatchLogsPushRole-${SystemName}-${SubName}
      Description: This role allows AppSync to push logs to CloudWatch Logs.
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
              - appsync.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSAppSyncPushToCloudWatchLogs

# ------------------------------------------------------------#
# Lambda Bedrock Invocation Role (IAM)
# ------------------------------------------------------------#
  LambdaBedrockInvocationRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub LambdaBedrockSRRole-${SystemName}-${SubName}
      Description: This role allows Lambda functions to invoke Bedrock and AppSync Events API.
      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 LambdaBedrockSRPolicy-${SystemName}-${SubName}
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - "bedrock:InvokeModel"
                  - "bedrock:InvokeModelWithResponseStream"
                Resource:
                  - !Sub "arn:aws:bedrock:*::foundation-model/*"
                  - !Sub "arn:aws:bedrock:*:${AWS::AccountId}:inference-profile/*"
              - Effect: Allow
                Action:
                  - "appsync:connect"
                Resource:
                  - !GetAtt AppSyncApi.ApiArn
              - Effect: Allow
                Action:
                  - "appsync:publish"
                  - "appsync:EventPublish"
                Resource:
                  - !Sub ${AppSyncApi.ApiArn}/channelNamespace/bedrock-stream-response
    DependsOn:
      - AppSyncApi

# ------------------------------------------------------------#
# Lambda
# ------------------------------------------------------------#
  LambdaBedrockSR:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub bedrock-stream-response-${SystemName}-${SubName}
      Description: !Sub Lambda Function to invoke Bedrock for ${SystemName}-${SubName}
      Architectures:
        - x86_64
      Runtime: python3.13
      Timeout: 180
      MemorySize: 128
      Role: !GetAtt LambdaBedrockInvocationRole.Arn
      Handler: index.lambda_handler
      Layers:
        - !Ref LambdaLayerRequests2323
      Tags:
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}
      Code:
        ZipFile: !Sub |
          import json
          import boto3
          import requests
          from requests_aws_sign import AWSV4Sign
          bedrock = boto3.client('bedrock-runtime')
          session = boto3.session.Session()
          model_id = '${ModelId}'
          endpoint = 'https://${AppSyncApi.Dns.Http}/event'
          headers = {'Content-Type': 'application/json'}
          def lambda_handler(event, context):
            try:
              credentials = session.get_credentials()
              auth = AWSV4Sign(credentials, '${AWS::Region}', 'appsync')
              # API Gateway からのインプットを取得
              prompt = event['body']['prompt']
              sessionid = event['body']['sessionid']
              sub = event['sub']
              # Amazon Bedrock への問い合わせフォーマット作成
              conversation = [
                {
                  "role": "user",
                  "content": [{"text": prompt}]
                }
              ]
              # Amazon Bedrock に問い合わせ
              stream = bedrock.converse_stream(
                modelId=model_id,
                messages=conversation,
                inferenceConfig={"maxTokens": 10000, "temperature": 0.5, "topP": 0.9}
              )
              for chunk in stream["stream"]:
                if "contentBlockDelta" in chunk:
                  text = chunk['contentBlockDelta']['delta']['text']
                  payload = {
                    "channel": "bedrock-stream-response/" + sub + "/" + sessionid,
                    "events": [
                      json.dumps({"message": text})
                    ]
                  }
                  requests.post(endpoint, auth=auth, json=payload, headers=headers).json()
            except Exception as e:
              print(str(e))
              exit(1)
    DependsOn:
      - LambdaBedrockInvocationRole
      - LambdaLayerRequests2323

  LambdaBedrockSREventInvokeConfig:
    Type: AWS::Lambda::EventInvokeConfig
    Properties:
      FunctionName: !GetAtt LambdaBedrockSR.Arn
      Qualifier: $LATEST
      MaximumRetryAttempts: 0
      MaximumEventAgeInSeconds: 300

# ------------------------------------------------------------#
# API Gateway REST API
# ------------------------------------------------------------#
  RestApiBedrockSR:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: !Sub bedrock-stream-response-${SystemName}-${SubName}
      Description: !Sub REST API to call Lambda bedrock-stream-response-${SystemName}-${SubName}
      EndpointConfiguration:
        Types:
          - REGIONAL
        IpAddressType: dualstack
      Tags:
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}

  RestApiDeploymentBedrockSR:
    Type: AWS::ApiGateway::Deployment
    Properties:
      RestApiId: !Ref RestApiBedrockSR
    DependsOn:
      - RestApiMethodBedrockSRPost
      - RestApiMethodBedrockSROptions

  RestApiStageBedrockSR:
    Type: AWS::ApiGateway::Stage
    Properties:
      StageName: prod
      Description: production stage
      RestApiId: !Ref RestApiBedrockSR
      DeploymentId: !Ref RestApiDeploymentBedrockSR
      MethodSettings:
        - ResourcePath: "/*"
          HttpMethod: "*"
          LoggingLevel: INFO
          DataTraceEnabled : true
      TracingEnabled: false
      AccessLogSetting:
        DestinationArn: !GetAtt LogGroupRestApiBedrockSR.Arn
        Format: '{"requestId":"$context.requestId","status":"$context.status","sub":"$context.authorizer.claims.sub","email":"$context.authorizer.claims.email","resourcePath":"$context.resourcePath","requestTime":"$context.requestTime","sourceIp":"$context.identity.sourceIp","userAgent":"$context.identity.userAgent","apigatewayError":"$context.error.message","authorizerError":"$context.authorizer.error","integrationError":"$context.integration.error"}'
      Tags:
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}

  RestApiAuthorizerBedrockSR:
    Type: AWS::ApiGateway::Authorizer
    Properties:
      Name: !Sub restapi-authorizer-bedrocksr-${SystemName}-${SubName}
      RestApiId: !Ref RestApiBedrockSR
      Type: COGNITO_USER_POOLS
      ProviderARNs:
        - Fn::ImportValue:
            !Sub CognitoArn-${SystemName}-${SubName}
      AuthorizerResultTtlInSeconds: 300
      IdentitySource: method.request.header.Authorization

  RestApiResourceBedrockSR:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref RestApiBedrockSR
      ParentId: !GetAtt RestApiBedrockSR.RootResourceId
      PathPart: bedrocksr

  RestApiMethodBedrockSRPost:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref RestApiBedrockSR
      ResourceId: !Ref RestApiResourceBedrockSR
      HttpMethod: POST
      AuthorizationType: COGNITO_USER_POOLS
      AuthorizerId: !Ref RestApiAuthorizerBedrockSR
      Integration:
        Type: AWS
        IntegrationHttpMethod: POST
        Credentials: !GetAtt ApigLambdaInvocationRole.Arn
        Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaBedrockSR.Arn}/invocations"
        PassthroughBehavior: NEVER
        RequestTemplates:
          application/json: |
            {
              "body": $input.json('$'),
              "sub": "$context.authorizer.claims.sub"
            }
        RequestParameters:
          integration.request.header.X-Amz-Invocation-Type: "'Event'"
        IntegrationResponses:
          - ResponseParameters:
              method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,Cache-Control'"
              method.response.header.Access-Control-Allow-Methods: "'POST,OPTIONS'"
              method.response.header.Access-Control-Allow-Origin: !Sub "'https://${SubDomainName}.${DomainName}'"
            ResponseTemplates:
              application/json: ''
            StatusCode: '202'
      MethodResponses:
        - StatusCode: '202'
          ResponseModels:
            application/json: Empty
          ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: true
            method.response.header.Access-Control-Allow-Headers: true
            method.response.header.Access-Control-Allow-Methods: true
    DependsOn:
      - ApigLambdaInvocationRole

  RestApiMethodBedrockSROptions:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref RestApiBedrockSR
      ResourceId: !Ref RestApiResourceBedrockSR
      HttpMethod: OPTIONS
      AuthorizationType: NONE
      Integration:
        Type: MOCK
        Credentials: !GetAtt ApigLambdaInvocationRole.Arn
        IntegrationResponses:
          - ResponseParameters:
              method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,Cache-Control'"
              method.response.header.Access-Control-Allow-Methods: "'POST,OPTIONS'"
              method.response.header.Access-Control-Allow-Origin: !Sub "'https://${SubDomainName}.${DomainName}'"
            ResponseTemplates:
              application/json: ''
            StatusCode: '200'
        PassthroughBehavior: WHEN_NO_MATCH
        RequestTemplates:
          application/json: '{"statusCode": 200}'
      MethodResponses:
        - ResponseModels:
            application/json: Empty
          ResponseParameters:
            method.response.header.Access-Control-Allow-Headers: true
            method.response.header.Access-Control-Allow-Methods: true
            method.response.header.Access-Control-Allow-Origin: true
          StatusCode: '200'

# ------------------------------------------------------------#
# API Gateway Lambda Invocation Role (IAM)
# ------------------------------------------------------------#
  ApigLambdaInvocationRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ApigLambdaInvocationRole-${SystemName}-${SubName}
      Description: This role allows API Gateways to invoke Lambda.
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
              - apigateway.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaRole
        - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
        - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess

# ------------------------------------------------------------#
# API Gateway LogGroup (CloudWatch Logs)
# ------------------------------------------------------------#
  LogGroupRestApiBedrockSR:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/apigateway/${RestApiBedrockSR}
      RetentionInDays: 365
      Tags:
        - Key: Cost
          Value: !Sub Hirodemy-${SubName}

# ------------------------------------------------------------#
# Lambda Layer
# ------------------------------------------------------------#
  LambdaLayerRequests2323:
    Type: AWS::Lambda::LayerVersion
    Properties:
      LayerName: !Sub requests2323-${SystemName}-${SubName}
      Description: Requests 2.32.3 for Python
      CompatibleRuntimes:
        - python3.13
      Content:
        S3Bucket: !Ref S3BucketNameSdk
        S3Key: !Ref S3KeyRequestsSdk
      LicenseInfo: Apache-2.0

# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#
Outputs:
# API Gateway
  APIGatewayEndpointBedrockSR:
    Value: !Sub https://${RestApiBedrockSR}.execute-api.${AWS::Region}.${AWS::URLSuffix}/${RestApiStageBedrockSR}/bedrocksr
    Export:
      Name: !Sub RestApiEndpointBedrockSR-${SystemName}-${SubName}
# AppSync
  AppSyncEventsApiEndpointHttp:
    Value: !Sub https://${AppSyncApi.Dns.Http}/event
    Export:
      Name: !Sub AppSyncEventsEndpointHttp-${SystemName}-${SubName}
  AppSyncEventsApiEndpointRealtime:
    Value: !Sub wss://${AppSyncApi.Dns.Realtime}
    Export:
      Name: !Sub AppSyncEventsEndpointRealtime-${SystemName}-${SubName}

続編記事

続編記事が出来次第、この章を更新します。

 

まとめ

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

私が AWS AppSync Events と Amazon API Gateway REST API の Lambda 関数非同期呼出を使用するのが初めてだったのと、AWS Lambda 関数内で使用している converse_stream の Python 用 API の情報、それと Amazon Cognito 認証を使用した AWS AppSync Events の使用例が世の中になかったので、動く状態になるまでにとーっても苦労しました。w

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

著者について
広野 祐司

AWS サーバーレスアーキテクチャを駆使して社内クラウド人材育成アプリとコンテンツづくりに勤しんでいます。React で SPA を書き始めたら快適すぎて、他の言語には戻れなくなりました。サーバーレス & React 仲間を増やしたいです。AWS は好きですが、それよりもバックエンド構築を簡単にしてくれたことに対する感謝の気持ちの方が強いです。
取得資格:AWS 認定は15資格、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をコピーしました