こんにちは、広野です。
生成 AI 界隈の技術の進化がすさまじく、以前開発したチャットボットのアーキテクチャも陳腐化が見えてきました。この記事を執筆している時点での最新のアーキテクチャで改めて作り直してみたので、いくつかの記事に分けて紹介します。
今回 (3回目) は実装編 その2 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
本記事が皆様のお役に立てれば幸いです。