Amazon Bedrock を試す:Lambda から基盤モデルを呼び出すハンズオン

Tatsuroh Wakasugi

Tatsuroh Wakasugi
AWSAI
生成 AI をプロダクトに組み込みたいけれど、モデルのホスティングやインフラ管理が大変そう…。そんな悩みを解決してくれるのが Amazon Bedrock です。
本記事では、Bedrock を触ったことがないエンジニア向けに 概要の紹介 と Terraform を使った実践ハンズオン をお届けします。
Amazon Bedrock とは?
Amazon Bedrock1 は、AWS が提供するフルマネージドサービスで、AWS や各種モデルプロバイダーが提供する基盤モデルに、安全かつエンタープライズグレードでアクセスできるサービスです。
主な特徴
| 特徴 | 説明 |
|---|---|
| フルマネージド | サーバーのデプロイもモデルランタイムの管理もスケーリングも不要。モデルを選び、ペイロードを整形して、リクエストを送るだけです。 |
| 複数モデルを統一的に呼び出せる | Anthropic、Meta、Amazon などの複数のモデルを、Converse API などの共通インターフェース経由で扱える。 |
| カスタマイズ | プロンプト設計、Knowledge Bases、モデルカスタマイズなどを組み合わせて、自社ビジネスに最適化できます。 |
| エンタープライズセキュリティ | 業界最高水準のセキュリティ・プライバシー・コンプライアンスを提供。データがモデルの学習に使われることはありません。 |
| コスト最適化 | Prompt Caching や Intelligent Prompt Routing により、コストを削減しつつパフォーマンスを維持できます。(料金はモデルやリージョンによって異なるため、公式料金ページ2 をご確認ください。) |
ハンズオン構成
本ハンズオンでは以下の構成を Terraform でデプロイします。
(今回は Bedrock の全機能を網羅するのではなく、Lambda から基盤モデルを呼び出す最小構成に絞って試します。)
構成要素(何を作るか)
- ユーザー:AWS CLI などから Lambda を呼び出す
- AWS Lambda(Python):入力メッセージを受け取り、Bedrock を呼び出す実行基盤
- Amazon Bedrock(Nova モデル):Converse API 経由でテキスト生成を行う基盤モデル
- IAM Role / Policy:Lambda に Bedrock 呼び出し権限と CloudWatch Logs への書き込み権限を付与
- CloudWatch Logs:Lambda 実行ログの保存先
処理の流れ(どう動くか)
- ユーザーが Lambda 関数を
invokeする(ペイロードにmessageを指定) - Lambda が Bedrock の Converse API を呼び出し、モデル応答(生成テキスト)を取得する
- Lambda が応答テキストを整形して呼び出し元へ返す
- 実行ログは CloudWatch Logs に出力される(トラブルシュートに利用)
ゴール: Lambda 関数から Bedrock の基盤モデル(Nova)を呼び出して、テキスト生成を行うシンプルなパイプラインを構築します。
前提条件
ハンズオンを始める前に以下を準備してください。
- AWS アカウント(Bedrock が利用可能なリージョン:
us-east-1推奨) - AWS CLI がインストール・設定済み
- Terraform がインストール済み(v1.14 以降を推奨)
注意: 利用するモデルによっては、利用条件への同意やアクセス設定が必要です。必要な手順はモデルごとに異なるため、Bedrock コンソールや公式ドキュメントを確認してください。
ディレクトリ構成
今回のハンズオンでは以下のような構成を作ります。
bedrock-handson/
├── main.tf # メインリソース定義
├── variables.tf # 変数定義
├── outputs.tf # 出力定義
├── provider.tf # プロバイダー設定
├── iam.tf # IAM ロール・ポリシー
├── lambda_src/
│ └── index.py # Lambda ハンドラー(Python)
└── terraform.tfvars # 変数値(任意)
Terraform コード
- provider.tf
terraform {
required_version = ">= 1.14.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.49"
}
archive = {
source = "hashicorp/archive"
version = "~> 2.4"
}
null = {
source = "hashicorp/null"
version = "~> 3.2"
}
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Project = "bedrock-handson"
ManagedBy = "terraform"
}
}
}
- variables.tf
variable "aws_region" {
description = "AWS リージョン"
type = string
default = "us-east-1"
}
variable "bedrock_model_id" {
description = "Bedrock で使用する基盤モデルの ID"
type = string
default = "amazon.nova-lite-v1:0"
}
variable "lambda_function_name" {
description = "Lambda 関数名"
type = string
default = "bedrock-invoke-demo"
}
- iam.tf
# -----------------------------------------------
# Lambda 実行用 IAM ロール
# -----------------------------------------------
resource "aws_iam_role" "lambda_bedrock_role" {
name = "lambda-bedrock-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
# CloudWatch Logs への書き込み権限
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
role = aws_iam_role.lambda_bedrock_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
# Bedrock 推論 API 呼び出し権限
resource "aws_iam_role_policy" "bedrock_invoke_policy" {
name = "bedrock-invoke-policy"
role = aws_iam_role.lambda_bedrock_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
# Bedrock モデル呼び出し権限(既存)
{
Effect = "Allow"
Action = [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
]
Resource = "arn:aws:bedrock:${var.aws_region}::foundation-model/*"
},
# AWS Marketplace 権限(追加)
{
Effect = "Allow"
Action = [
"aws-marketplace:ViewSubscriptions",
"aws-marketplace:Subscribe",
"aws-marketplace:Unsubscribe"
]
Resource = "*"
}
]
})
}
- main.tf
# -----------------------------------------------
# Lambda 関数用ビルドディレクトリの作成
# -----------------------------------------------
resource "null_resource" "create_build_dir" {
provisioner "local-exec" {
command = "mkdir -p ${path.module}/.build"
}
}
# -----------------------------------------------
# Lambda 関数用 ZIP パッケージ
# -----------------------------------------------
data "archive_file" "lambda_zip" {
type = "zip"
source_dir = "${path.module}/lambda_src"
output_path = "${path.module}/.build/lambda.zip"
depends_on = [null_resource.create_build_dir]
}
# -----------------------------------------------
# CloudWatch Logs ロググループ
# -----------------------------------------------
resource "aws_cloudwatch_log_group" "lambda_log" {
name = "/aws/lambda/${var.lambda_function_name}"
retention_in_days = 14
}
# -----------------------------------------------
# Lambda 関数
# -----------------------------------------------
resource "aws_lambda_function" "bedrock_demo" {
function_name = var.lambda_function_name
role = aws_iam_role.lambda_bedrock_role.arn
handler = "index.handler"
runtime = "python3.12"
timeout = 60
memory_size = 256
filename = data.archive_file.lambda_zip.output_path
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
environment {
variables = {
BEDROCK_MODEL_ID = var.bedrock_model_id
}
}
depends_on = [
aws_iam_role_policy_attachment.lambda_basic_execution,
aws_cloudwatch_log_group.lambda_log,
]
}
- outputs.tf
output "lambda_function_name" {
description = "デプロイされた Lambda 関数名"
value = aws_lambda_function.bedrock_demo.function_name
}
output "lambda_function_arn" {
description = "Lambda 関数の ARN"
value = aws_lambda_function.bedrock_demo.arn
}
output "bedrock_model_id" {
description = "使用している Bedrock モデル ID"
value = var.bedrock_model_id
}
- lambda_src/index.py
import json
import boto3
import os
bedrock = boto3.client("bedrock-runtime", region_name=os.environ["AWS_REGION"])
def handler(event, context):
user_message = event.get("message", "こんにちは。AWS Lambda から Bedrock を呼んでいます。")
model_id = os.environ["BEDROCK_MODEL_ID"]
response = bedrock.converse(
modelId=model_id,
messages=[
{
"role": "user",
"content": [{"text": user_message}]
}
],
inferenceConfig={
"maxTokens": 300,
"temperature": 0.7
}
)
output_text = ""
for item in response["output"]["message"]["content"]:
if "text" in item:
output_text += item["text"]
return {
"statusCode": 200,
"body": json.dumps({
"reply": output_text
}, ensure_ascii=False)
}
デプロイ手順
- 初期化 & プラン
cd bedrock-handson
# Terraform 初期化
terraform init
# 実行計画の確認
terraform plan
- デプロイ
terraform apply
yes を入力して適用します。
- 動作確認
AWS CLI で Lambda 関数を呼び出します。
# デフォルトプロンプトで実行
aws lambda invoke \
--function-name bedrock-invoke-demo \
--cli-binary-format raw-in-base64-out \
--payload '{}' \
response.json
cat response.json | jq .
カスタムプロンプトを送る場合:
aws lambda invoke \
--function-name bedrock-invoke-demo \
--cli-binary-format raw-in-base64-out \
--payload '{"message": "Pythonでクイックソートを実装してください"}' \
response.json
cat response.json | jq .body -r | jq .
期待される出力例:
{
"reply": "こんにちは。Amazon Bedrock から応答しています。AWS Lambda から正常にモデルを呼び出せています。"
}
{
"reply": "クイックソート(QuickSort)は、分割と統合の考え方に基づく分割統治法のアルゴリズムです。以下のPythonコードは、クイックソートの基本的な実装例を示しています。\n\n```python\ndef quicksort(arr):\n if len(arr) <= 1:\n return arr\n else:\n pivot = arr[len(arr) // 2] # ピボット要素を選択\n left = [x for x in arr if x < pivot] # ピボットより小さい要素\n middle = [x for x in arr if x == pivot] # ピボットと等しい要素\n right = [x for x in arr if x > pivot] # ピボットより大きい要素\n return quicksort(left) + middle + quicksort(right) # 再帰的にソート\n\n# クイックソートの実行例\nunsorted_array = [3, 6, 8, 10, 1, 2, 1]\nsorted_array = quicksort(unsorted_array)\nprint(\"Sorted array:\", sorted_array)\n```\n\nこのコードは、配列を再帰的に分割し、ピボット要素を基準に小さい要素と大きい要素"
}
- クリーンアップ
terraform destroy
今回は簡単な例でお出ししましたが、応用例として以下のようなものもあるので実践してみてください。
| ステップ | 内容 |
|---|---|
| RAG 構築 | Bedrock Knowledge Bases + OpenSearch Serverless で社内ドキュメント検索 |
| エージェント構築 | Bedrock Agents で外部 API を呼び出すマルチステップエージェント |
| コスト管理 | Provisioned Throughput / Intelligent Prompt Routing の活用 |
| CI/CD 統合 | GitHub Actions + Terraform Cloud で自動デプロイパイプライン構築 |