AWSのBedrockを、API Gateway経由でLambdaから呼び出す手順を整理しました。
Claude 3.7 Sonnetモデルを使い、日本語応答を得るまでの構成を実際に構築したときのメモです。
💡 構成概要
- HTML → API Gateway → Lambda → Bedrock(Claude 3.7 Sonnet)
- リージョン:ap-northeast-1(東京)
- モデルID:apac.anthropic.claude-3-7-sonnet-20250219-v1:0
🧾 手順一覧
前提条件:東京リージョンでBedrockが使用できるようになっていること
1 Lambda関数作成
2 IAMロール変更(Bedrockアクセス許可)
3 タイムアウト延長(3秒→30秒)
4 Lambdaコード実装・デプロイ
5 Lambdaテスト
6 API Gateway作成(REST API)
7 リソース 作成
8 ANYメソッド追加
9 CORSを有効化
10 画像送信設定
11 デプロイ&ステージ作成
12 実行URL確認
13 curlでOPTIONS確認
14 HTMLフォームで実行テスト
15 画像送信対応(multipart/form-data対応)
16 CloudWatchでログ確認
手順
1 Lambda関数作成
2 IAMロール変更(Bedrockアクセス許可)
3 タイムアウト延長(3秒→30秒)
★画像は15分だけど30秒でいいかも
4 Lambdaコード実装・デプロイ
Lambdaコードを張り付けてデプロイする
🐍 Lambda関数(Python)
import json
import boto3
import base64
from email.parser import BytesParser
from email.policy import default
def lambda_handler(event, context):
# --- OPTIONS プリフライト対応 ---
if event.get("requestContext", {}).get("http", {}).get("method") == "OPTIONS":
return {
"statusCode": 200,
"headers": cors_headers(),
"body": json.dumps({"message": "CORS OK"})
}
bedrock = boto3.client("bedrock-runtime", region_name="ap-northeast-1")
model_id = "apac.anthropic.claude-3-7-sonnet-20250219-v1:0"
system_prompt = "必ず日本語で答えてください"
max_tokens = 1000
user_input = ""
images = []
try:
headers = event.get("headers", {}) or {}
content_type = headers.get("content-type") or headers.get("Content-Type")
body_raw = event.get("body", "")
is_b64 = event.get("isBase64Encoded", False)
# --- multipart/form-data の場合 ---
if content_type and "multipart/form-data" in str(content_type).lower():
if isinstance(body_raw, bytes):
body_bytes = body_raw
elif is_b64:
body_bytes = base64.b64decode(body_raw)
else:
body_bytes = bytes(body_raw, "latin-1", errors="ignore")
parser = BytesParser(policy=default)
msg = parser.parsebytes(b"Content-Type: " + content_type.encode() + b"\n\n" + body_bytes)
for part in msg.iter_parts():
disp = part.get("Content-Disposition", "")
if "form-data" not in disp:
continue
name = part.get_param("name", header="content-disposition")
if name == "message":
raw = part.get_payload(decode=True)
if raw:
user_input = raw.decode(errors="ignore").strip()
elif name == "images":
mime = part.get_content_type()
if mime not in ["image/png", "image/jpeg"]:
continue
data = part.get_payload(decode=True)
b64 = base64.b64encode(data).decode().replace("\n", "")
images.append({"mime": mime, "data": b64})
# --- JSON 形式の場合 ---
elif body_raw:
body_data = json.loads(body_raw)
user_input = body_data.get("message", "")
if "images" in body_data:
for img in body_data["images"]:
images.append({"mime": "image/png", "data": img})
except Exception as e:
return error_response(f"リクエスト解析エラー: {str(e)}")
# --- テキスト未入力対策 ---
if not user_input.strip():
user_input = "画像を解析してください。"
# --- Claude 入力構築 ---
user_content = [{"type": "text", "text": user_input}]
for img in images:
user_content.append({
"type": "image",
"source": {
"type": "base64",
"media_type": img["mime"],
"data": img["data"]
}
})
payload = {
"anthropic_version": "bedrock-2023-05-31",
"system": system_prompt,
"max_tokens": max_tokens,
"messages": [{"role": "user", "content": user_content}]
}
# --- デバッグ出力 ---
print("=== PAYLOAD HEAD ===", json.dumps(payload)[:500])
try:
response = bedrock.invoke_model(modelId=model_id, body=json.dumps(payload))
body = response.get("body")
if hasattr(body, "read"):
body = body.read()
res_json = json.loads(body)
output_text = res_json["content"][0]["text"]
return {
"statusCode": 200,
"headers": cors_headers(),
"body": json.dumps({"reply": output_text}, ensure_ascii=False)
}
except Exception as e:
print("Error:", e)
return {
"statusCode": 500,
"headers": cors_headers(),
"body": json.dumps({"error": str(e)}, ensure_ascii=False)
}
def error_response(msg):
return {
"statusCode": 400,
"headers": cors_headers(),
"body": json.dumps({"error": msg}, ensure_ascii=False)
}
def cors_headers():
return {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET",
"Access-Control-Allow-Headers": "Content-Type,x-amz-date,authorization,x-api-key,x-amz-security-token"
}
5 Lambdaテスト
LamdaからBedrockに通信できることを確認
この時点で画像添付はできないので、後で確認する
"message": "これはテストです。"
6 API Gateway作成(REST API)
7 リソース 作成
8 POSTメソッド追加
ai階層で作成する
9 CORSを有効化
10 画像送信設定
11 デプロイ&ステージ作成
ここで初めてステージを作成している
12 実行URL確認
13 curlでOPTIONS確認
curl -i -X OPTIONS https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/ai
14 HTMLフォームで実行テスト
curlでの確認で「できた」と思ったが
htmlで実行すると、CORS関連エラーと画像添付できない問題が発生していた。
🌐 HTMLテストページ
★const API_URL に自身のBedrockAPIアドレスを入れる。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Bedrock API テスト(複数画像対応)</title>
<style>
body { font-family: sans-serif; padding: 20px; }
textarea { width: 100%; height: 100px; }
button { padding: 8px 16px; margin-top: 10px; }
#reply { margin-top: 15px; white-space: pre-wrap; border: 1px solid #ccc; padding: 10px; }
#preview img { max-width: 150px; margin: 5px; border: 1px solid #ccc; }
</style>
</head>
<body>
<h2>Bedrock API テスト(複数画像対応)</h2>
<textarea id="inputText" placeholder="メッセージを入力..."></textarea><br>
<!-- ✅ 複数画像選択対応 -->
<input type="file" id="imageInput" multiple accept="image/*"><br>
<!-- ✅ 選択済み画像のプレビュー -->
<div id="preview"></div>
<button id="sendBtn">送信</button>
<div id="reply"></div>
<script>
const API_URL = "https://xxxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/ステージ/リソース";
const btn = document.getElementById("sendBtn");
const input = document.getElementById("inputText");
const imageInput = document.getElementById("imageInput");
const reply = document.getElementById("reply");
const preview = document.getElementById("preview");
// --- 画像プレビュー ---
imageInput.onchange = () => {
preview.innerHTML = "";
for (const file of imageInput.files) {
const img = document.createElement("img");
img.src = URL.createObjectURL(file);
preview.appendChild(img);
}
};
// --- 送信処理 ---
btn.onclick = async () => {
const msg = input.value.trim();
const files = imageInput.files;
if (!msg && files.length === 0) {
reply.textContent = "⚠️ テキストか画像を入力してください。";
return;
}
reply.textContent = "送信中...";
btn.disabled = true; // 二重送信防止
const formData = new FormData();
formData.append("message", msg);
// ✅ 複数画像を1つずつ追加
for (let i = 0; i < files.length; i++) {
formData.append("images", files[i], files[i].name);
}
try {
const res = await fetch(API_URL, {
method: "POST",
body: formData // Content-Typeは自動設定(boundary含む)
});
const text = await res.text();
let message = "";
try {
const data = JSON.parse(text);
message = data.reply || data.error || text;
} catch {
message = text;
}
reply.textContent = message;
} catch (e) {
reply.textContent = "通信エラー: " + e.message;
} finally {
btn.disabled = false;
}
};
</script>
</body>
</html>
15 画像送信対応(multipart/form-data対応)
通常ローカルでの送信はCORSでlocalhostを認識できないため、
ブラウザ経由ではエラーになるが、これまでの設定で回避している。
16 CloudWatchでログ確認
エラーが出た場合の確認はClowdWatchでの確認を行う。
(Lamda上で[$LATEST]を記載し、検索しやすくしている)
- START RequestId 〜 END RequestId の間に、Bedrock応答テキストが出力されているか確認
🧩 注意点
- Bedrockは応答がやや遅いので、Lambdaのタイムアウトを30秒以上に設定すること。
- API GatewayでCORSを有効化しないと、ブラウザからPOSTできない。
- multipart/form-dataを扱う場合は、Lambdaをboto3+base64対応に改修する必要あり。
(今回のLambdaは改修済み)
✅ まとめ
この構成を使えば、HTMLフォームから画像添付し、claude3.7の東京リージョンに
送付が可能となります。
- CORS・タイムアウト・IAMロールの3点を調整することで安定動作します。
























