はじめに
生成AIとても流行っていますね。
私もかねてから生成AI、特に画像生成AIに興味があったため Amazon Bedrock を利用して画像生成アプリを作ってみました。
本内容は2024年11月時点での情報を元に記載しています。
最新の情報はAWSの公式ドキュメントを参照してください。
Amazon Bedrock とは
Amazon Bedrock とは、主要な基盤モデルを選択して利用できるサービスで、セキュリティやプライバシーに配慮された生成AIアプリケーションを構築できます。
開発者はAPIを通じて生成AIを呼び出すことができ、アプリに簡単にAI機能を組み込むことができます。
さらに、サーバーレスであるためインフラストラクチャの管理が不要です。
構成図
実装
ソースコードの全体像は こちら になります。
AWSリソースはCDKで作成していきます。
モデルの有効化
Amazon Bedrockで利用する基盤モデルを有効化します。
今回画像生成に利用するTitanモデルは東京リージョンだと利用できないため、バージニア北部リージョンを利用します。
-
AWSコンソールにログインしAmazon Bedrockに移動
-
リージョンを「バージニア北部(us-east-1)」に変更
S3バケットの作成
生成した画像を保存するS3バケットを作成します。
const accountId = cdk.Stack.of(this).account;
const region = cdk.Stack.of(this).region;
const bucket = new s3.Bucket(this, `BedrockBucket`, {
bucketName: `bedrock-bucket-${accountId}-${region}`,
accessControl: s3.BucketAccessControl.PRIVATE,
objectOwnership: s3.ObjectOwnership.BUCKET_OWNER_PREFERRED,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
encryption: s3.BucketEncryption.KMS_MANAGED,
});
Lambdaの作成
LambdaからBedrockのモデルの呼び出しとS3のアップロード/ダウンロードができるようにRoleを作成します。
Model ARNはAmazon Bedrockのプロバイダーから確認できます。
const bedrockLambdaFunctionRole = new iam.Role(this, 'BedrockLambdaFunctionRole', {
roleName: 'bedrock-lambda-function-role',
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
],
inlinePolicies: {
BedrockInvokeModel: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['bedrock:InvokeModel'],
resources: [
'arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-image-generator-v2:0',
],
}),
],
}),
S3PutObject: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['s3:GetObject', 's3:PutObject'],
resources: [bucket.arnForObjects('*')],
}),
],
}),
},
});
続いてLambdaでライブラリを利用したいためレイヤーを作成します。
今回はAWSが提供しているLambdaレイヤーのAWS SDK for Pandasを利用することにしました。
Layer Arnはこちらから確認することができます。
const lambdaLayer = lambda.LayerVersion.fromLayerVersionArn(
this,
'BedrockLambdaLayer',
'arn:aws:lambda:us-east-1:336392948345:layer:AWSSDKPandas-Python312-Arm64:6'
);
続いてLambda関数を作成します。
Lambdaの処理で利用する値を環境変数に渡しています。
const bedrockLambdaFunction = new lambda.Function(this, 'BedrockLambdaFunction', {
functionName: `bedrock-function`,
code: lambda.Code.fromAsset(path.join(__dirname, `../src/lambda/bedrock`)),
handler: 'index.lambda_handler',
runtime: lambda.Runtime.PYTHON_3_12,
timeout: cdk.Duration.seconds(60),
architecture: lambda.Architecture.ARM_64, //X86_64,
environment: {
S3_BUCKET_NAME: bucket.bucketName,
LOG_LEVEL: 'INFO',
LINE_CHANNEL_SECRET: process.env.LINE_CHANNEL_SECRET || '',
LINE_CHANNEL_ACCESS_TOKEN: process.env.LINE_CHANNEL_ACCESS_TOKEN || '',
},
role: bedrockLambdaFunctionRole,
layers: [lambdaLayer],
});
続いてLambda関数の実装です。
Amazon Bedrockの基盤モデルへAPIリクエストし画像を生成します。
APIリクエストの例はAmazon Bedrockのプロバイダーから確認できます。
画質、画像サイズなどは自分好みに調整してください。
seed値は固定にしておくと同じ入力で同じ画像を生成することができます。
私は色々な画像生成を楽しみたかったので、ランダム値にしています。
def invoke_titan_image(prompt):
try:
request = json.dumps(
{
"taskType": "TEXT_IMAGE",
"textToImageParams": {"text": prompt},
"imageGenerationConfig": {
"numberOfImages": 1,
"quality": "standard",
"cfgScale": 8.0,
"height": 512,
"width": 512,
"seed": random.randint(0, 2147483646),
},
}
)
response = bedrock_runtime.invoke_model(
modelId="amazon.titan-image-generator-v2:0", body=request
)
response_body = json.loads(response["body"].read())
base64_image_data = response_body["images"][0]
return base64_image_data
except ClientError as e:
logger.error(f"Couldn't invoke Titan Image generator: {e}")
raise
生成した画像をS3にアップロードします。
image_data = base64.b64decode(base64_image_data)
object_key = (
f"generated_images/{datetime.now().strftime("%Y%m%d%H%M%S")}_{random_uuid}/image_{input_text.replace(' ', '_')}.jpg"
)
s3.put_object(
Bucket=bucket_name,
Key=object_key,
Body=image_data,
ContentType="image/jpeg",
)
API Gatewayの作成
LINE botからLabmda関数を実行できるようにするためにAPI Gatewayを作成します。
const api = new apigwv2.HttpApi(this, 'BedrockLambdaApi', {
apiName: 'BedrockExecuteApi',
corsPreflight: {
allowHeaders: ['*'],
allowOrigins: ['*'],
allowMethods: [apigwv2.CorsHttpMethod.ANY],
},
});
api.addRoutes({
path: '/',
methods: [apigwv2.HttpMethod.POST],
integration: new HttpLambdaIntegration('BedrockLambdaIntegration', bedrockLambdaFunction),
});
LINE botの作成
以前はLINE DevelopersコンソールからMessageing APIチャネルを作成できたのですが、2024/09/04以降できなくなったようです。
公式の手順を参考にMessage APIの利用を有効にします。
Message APIを有効にしたら、LINE Developersコンソールからbotを作成します。
こちらを参考にチャネルアクセストークンの作成とWebhook URLを設定します。
チャネルアクセストークンには少し試したいだけなら短期のチャネルアクセストークンを発行しておけば良いかと思います。
Webhook URLにはAPI Gatewayで作成したエンドポイントを指定しましょう。
LINE Developersコンソールで確認できるチャネルシークレットとチャネルアクセストークンを.envファイルに記載しておきます。
LINE botからの署名検証
LINE botから送信されたメッセージの送信者の確認を行うため、署名の検証をLambdaに実装します。
def verify_signature(event):
try:
header_signature = event["headers"]["x-line-signature"]
body = event["body"]
channel_secret = os.environ.get("LINE_CHANNEL_SECRET")
hash = hmac.new(
channel_secret.encode("utf-8"), body.encode("utf-8"), hashlib.sha256
).digest()
signature = base64.b64encode(hash)
return header_signature == signature.decode('utf-8')
except ClientError as e:
logger.error(f"Error happnend when verified signature: {e}")
raise
LINE botへ生成した画像の送信
こちらを参考に、botに対して生成した画像を送信します。
# S3から署名付きURLを取得
presigned_url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": bucket_name, "Key": object_key},
ExpiresIn=3600,
)
# LINEに返信する
data = {
"replyToken": event.get("replyToken"),
"messages": [
{
"type": "image",
"originalContentUrl": presigned_url,
"previewImageUrl": presigned_url
}
]
}
try:
channel_access_token = os.environ.get("LINE_CHANNEL_ACCESS_TOKEN")
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer {}'.format(channel_access_token)
}
response = requests.post(REPLY_ENDPOINT, json=data, headers=headers)
logger.info(f"response: {response}")
except ClientError as e:
logger.error(f"Error happnend when reply: {e}")
raise
以上で一通りの実装は終了です。
動作確認
LINE DevelopersコンソールからQAコードを読み取り、botを友だち登録します。
botにメッセージを送ると、生成した画像が返ってくるか確認してみましょう。
うまく実装できていれば以下のようになります。
注意点として、Titanモデルは日本語対応できていません。
送るメッセージは英語で送信しましょう。
実装時にハマったポイント
当初LINE botからの署名検証にはLambda Authorizerを利用しようとしました。
署名検証にはrequest bodyが必要なのですが、Lambda Authorizerではrequest bodyを受け取ることができないようでしたので断念し、全ての処理を1つのLambdaにまとめました。
応用例
こちらの記事 を参考にすることで、Amazon Bedrock のモデルをファインチューニングすることもできるとのことです。
自分好みの画像を生成したい場合はチャレンジしてみてはいかがでしょうか。
最後に
このようにAWSが提供している基盤モデルを利用して簡単に画像生成アプリを作ることができました。
この記事が生成AIを触るきっかけになれれば嬉しいです。