こんにちは、広野です。
本記事はシリーズもので、以下の記事の続編です。


以前、以下の記事で 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 関数のつくりもなにげに参考になるかと思っております。
本記事が皆様のお役に立てれば幸いです。



