〜 API Gateway+Lambda+HTML連携で「Could not process image」地獄を突破 〜
🎯 結論
✅ Claude 3.7 Sonnet(ap-northeast-1)でも Vision(画像入力)機能は動く!
ただし API Gateway のバイナリ設定が必須。
HTML → API Gateway → Lambda → Bedrock の構成で、
Claude が複数画像を正しく解析して日本語で説明できるようになった。
🧩 構成概要
[HTMLブラウザ]
↓ (multipart/form-data)
[API Gateway (binary対応)]
↓ (Base64 encoded)
[Lambda (Python3.12)]
↓ (invoke_model)
[AWS Bedrock: Claude 3.7 Sonnet]
⚙️ 問題点:「Could not process image」
最初、LambdaからClaudeへ送った画像で以下のエラーが出続けた👇
ValidationException: Could not process image
原因を掘ると、
API Gateway が画像データを文字列化して Lambda に渡していた ことが判明。
Claudeに届いたBase64が壊れており、デコード不能になっていた。
💡 対処の流れ
ステップ 内容
① Lambdaで multipart/form-data を正確にパースする処理を作成
② HTMLで fetch(FormData) 送信(Content-Type自動設定)
③ API Gateway の Binary Media Types に / を設定
④ Lambdaで isBase64Encoded=True を判定して base64.b64decode()
⑤ Claude呼び出しJSONを "type": "image"+Base64形式に統一
✅ Lambda コード(完成版)
import json
import boto3
import base64
from email.parser import BytesParser
from email.policy import default
def lambda_handler(event, context):
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)
if content_type and "multipart/form-data" in str(content_type).lower():
body_bytes = base64.b64decode(body_raw) if is_b64 else 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():
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":
data = part.get_payload(decode=True)
mime = part.get_content_type()
if mime in ["image/png", "image/jpeg"]:
b64 = base64.b64encode(data).decode().replace("\n", "")
images.append({"mime": mime, "data": b64})
except Exception as e:
return error_response(f"リクエスト解析エラー: {str(e)}")
if not user_input.strip():
user_input = "画像を解析してください。"
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}]
}
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)}
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"
}
🌐 HTML 側(複数画像対応版)
<input type="file" id="imageInput" multiple accept="image/*">
<textarea id="inputText"></textarea>
<button id="sendBtn">送信</button>
<div id="reply"></div>
<script>
const API_URL = "https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/●/●";
document.getElementById("sendBtn").onclick = async () => {
const msg = document.getElementById("inputText").value.trim();
const files = document.getElementById("imageInput").files;
const formData = new FormData();
formData.append("message", msg);
for (let f of files) formData.append("images", f, f.name);
const res = await fetch(API_URL, { method: "POST", body: formData });
const text = await res.text();
document.getElementById("reply").textContent = text;
};
</script>
🧱 API Gateway 設定ポイント
設定 → Binary Media Types → / を追加
→ デプロイ後、Lambdaに isBase64Encoded=True が渡るようになる。
📸 動作結果
複数の画像(Windows 10/11 のサポート情報)を送信した結果:
Claude が両方の画像を認識し、
各バージョンのサポート終了日を表にまとめて説明。
結果は自然な日本語で、画像間の比較も自動で行われた!
🏁 まとめ
要素 対応状況
Claude 3.7 Sonnet (ap-northeast-1) ✅ 画像解析OK
API Gateway (Binary設定) ✅ 有効
Lambda (multipart処理) ✅ 正常
HTML (fetch+FormData) ✅ 複数画像送信OK
🐘 感想
「Could not process image」エラーに何日も悩んだけど、
原因はAPI Gatewayが画像を文字列化してたことだった。
Binary Media Typeを / にした瞬間、
Claudeがちゃんと画像を読んで答えてくれた。
正直、成功したとき泣いた。
🧑💻 環境
AWS Lambda (Python 3.12)
Amazon Bedrock Claude 3.7 Sonnet
API Gateway HTTP API
東京リージョン (ap-northeast-1)
ブラウザ: Chrome 141
🎉 これで、
「AWS Bedrock × Claude 3.7 Sonnet × 画像解析」を東京リージョンで動かした最初の成功例のひとつ!