こんにちは、広野です。
生成 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
本記事が皆様のお役に立てれば幸いです。







