「知っている」を「できる」に変えるには
勉強会のLTで「『知っている』を『できる』に変えるには」というテーマで登壇した。題材は、普段からやっている食事管理。テキストで食事内容を送るとAIがカロリーとPFC(タンパク質・脂質・炭水化物)を推定し、記録・週次集計までしてくれる小さなPoCをAWSで作ってみた話だ。
この記事では、LTで話した内容に加えて、スライドに載せきれなかった構成図や、実際にハマった3つのエラーの中身まで書いておく。
記録して、改善するのが好き
好きなものはそば、コーヒー、ビール。趣味は「記録し、改善すること」。
原体験がある。幼い頃かなり太っていて、学校でおかわりを禁止された生徒だった。そこから食事を記録して可視化し、減量に成功した。それ以来、食事も学習も、記録して見える化して少しずつ改善するのが癖になっている。
AWSの資格勉強も、この「コツコツ記録して改善する」やり方でなんとか進められた。Lambda、DynamoDB、S3、API Gateway、Bedrock……単語カードみたいに、一つひとつは説明できる。
「知っている」と「できる」のギャップ
知っていること:AWS単体のサービス概要。
できないこと:複数サービスを繋いで機能させること。
資格試験は「What(何を知っているか)」を問うものが多く、「How(どう繋ぐか)」は実際に手を動かさないと身につかない。特にAIを使っていると、この2つが混在してしまう感覚があった。AIが答えをすぐ出してくれるので、自分ができるような気になってしまう。
見分ける方法は何だろう、と考えたときに出た仮説はシンプルだった。自分の手で作って、言葉にしてみることではないか。
何を作ったか
題材には、普段からやっている食事管理を選んだ。今は食事記録アプリを使っているが、データを自分で活用したりAIで分析したりしにくい、という不満があった。
作ったのは、食事をテキストで入力すると、Bedrockがカロリー・PFCを推定してDynamoDBに記録し、1週間分をまとめてS3にMarkdownで出力する仕組み。
全体構成(二つの流れで動く)
【記録(同期)】
API Gateway → Lambda → Bedrock(PFC推定)
↕
DynamoDB(記録保存)
【週次レポート(非同期)】
EventBridge → Lambda(集計処理) → S3(レポート保存)
↑
DynamoDBから集計対象を読み込み
記録は「API Gateway → Lambda → Bedrock → DynamoDB」の同期処理。週次レポートは「EventBridge → Lambda → S3」の非同期バッチで、DynamoDBから1週間分を読み込んで集計する。
実際に動かすとこうなる
① 「鶏胸肉200gと白米150g」とテキストで送る。
curl -i -s --max-time 40 --aws-sigv4 "aws:amz:ap-northeast-1:execute-api" \
--user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
-H "x-amz-security-token: $AWS_SESSION_TOKEN" \
-H 'Content-Type: application/json' \
'https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/meals' \
-d '{"text":"鶏胸肉200gと白米150g"}'
カロリー472、タンパク質49.8、脂質3.5、炭水化物55.7、という推定結果が返ってくる。
{"food": "鶏胸肉200gと白米150g", "calories": 472, "protein": 49.8, "fat": 3.5, "carbs": 55.7}
② 1食ずつDynamoDBに記録が貯まっていく(おでん5個・ご飯100g、紅生姜天ぷら・チャーハン200g、など食べたものがそのまま蓄積される)。
③ 週に1回、EventBridgeが起動して1週間分をまとめ、S3にMarkdownのレポートとして出力する。ある週は記録12件、カロリー合計8,072kcal、1日平均1,153.1kcal、といった形で集計される。
作って気づいた3つのこと
① 権限が広すぎた
AIを呼ぶだけのLambdaに、管理者権限(AdministratorAccess)を付けたまま動かしていた。
原因:最初にAccessDeniedExceptionが出て、とりあえず広い権限を付けて動かした。動いた後、権限を見直さないままになっていた。
気付いたきっかけ:IAMロールを棚卸ししたら、実際に使っていたのはBedrock・DynamoDB・CloudWatch Logsだけだった。API GatewayのPOST /mealsも、当初は認証なし(AuthorizationType: NONE)で、URLを知っていれば誰でも呼べる状態だったことも同時に見つかった。
対策:Lambdaごとに必要な操作だけを許可するポリシーに置き換え、API GatewayはAWS_IAM認証に変更。署名なしのアクセスが403で拒否されることを確認した。
{
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow", "Action": "bedrock:InvokeModel", "Resource": "arn:aws:bedrock:ap-northeast-1::foundation-model/【モデルID】"},
{"Effect": "Allow", "Action": "dynamodb:PutItem", "Resource": "arn:aws:dynamodb:ap-northeast-1:【アカウントID】:table/【テーブル名】"}
]
}
学び:最小権限の原則は資格勉強で「知っていた」。でも、エラーを消したい場面では権限を広げて通してしまった。足りない権限はエラーメッセージに出るので、それを読んで1つずつ足すのが本来の進め方。
② AIの返事の形が崩れる
Bedrockの返事が、指定した形で返ってこないことがある。項目が抜ける、途中で切れる、余計な文章が混ざる、という3パターン。
原因:AIは毎回確率的に文章を作るので、プロンプトで形式を指定しても守られないことがある。出力の上限(max_tokens)に達すると途中で打ち切られ、モデルによっては最終テキストの前に思考過程のブロック(reasoningContent)を挟んでくる。
対策:保存する前に、毎回3段階でチェックする。1つでも外れたら補正せずにエラーにして、DynamoDBには保存しない。
EXPECTED_KEYS = {"food", "calories", "protein", "fat", "carbs"}
def parse_meal(response):
# 1. 途中で打ち切られていないか
if response.get("stopReason") != "end_turn":
raise ValueError(f"stopReason が想定外: {response.get('stopReason')}")
# 2. 思考過程のブロックを読み飛ばし、テキストだけ結合してパース
blocks = response["output"]["message"]["content"]
text = "".join(b["text"] for b in blocks if set(b) != {"reasoningContent"})
meal = json.loads(text)
# 3. スキーマの厳格照合(補正はしない)
if set(meal) != EXPECTED_KEYS:
raise ValueError(f"キーが一致しない: {set(meal)}")
for key in ("calories", "protein", "fat", "carbs"):
v = meal[key]
if not isinstance(v, (int, float)) or isinstance(v, bool) or not math.isfinite(v) or v < 0:
raise ValueError(f"{key} が不正な値: {v}")
return meal
学び:AIの出力は、ユーザー入力と同じ「信用しない外部データ」として扱う。壊れたデータが1件入ると週次のまとめまで狂うので、入口で止めるのが一番早い。
③ 小数がDynamoDBに保存できない
TypeError: Float types are not supported. Use Decimal types instead.
原因:DynamoDB自体は小数を保存できるが、保存に使うPythonライブラリ(boto3)がfloat型を受け付けない。floatは0.1のような値を正確に持てず、わずかな誤差を含むため。
Decimal(49.8)
# → Decimal('49.7999999999999971578290569595992565155029296875')
# floatから直接変換すると、誤差ごと入ってしまう
対策:いったん文字列を経由してからDecimal型に変換する。
item[field] = Decimal(str(meal[field])) # str(49.8) → '49.8' → Decimal('49.8')
学び:エラーメッセージを読んで、「DynamoDBの仕様か、ライブラリの仕様か」を切り分けることが大事。AIに聞けば「文字列を経由して変換」という答えはすぐ出る。でも「なぜ文字列なのか」を説明できるかで、「知っている」と「できる」が分かれる。
手を動かして気づいたこと
できるようになったこと
- 構成の意図を、自分の言葉で説明できる
- 最小限の権限で、サービスを繋げる
- AIの出力を、確認してから使える
まだ「知っている」止まりだと気づいたこと
- 他の構成との比較(自分で検証できていない)
- 運用監視・コスト管理(概念は知っているが、実装できていない)
動かして初めて気づいたこと
- 同じ入力でも、AIの数値がぶれる(鶏胸肉200gと白米150gで、450〜564kcal)
- Lambdaがときどき60秒でタイムアウトする
「知っている」と「できる」の境目が、作る前より見えるようになった。AIを使った開発が増えると、レビューする機会も増える。自分のできないことに気づきながら仮説を持って質問できれば、上司のレビューの負担も減らせるはずだ。
これ、AWSだったらどう作る?
今回作ったものは、ものすごく高度なサービスではない。むしろAIを使えば、小さなPoCならかなり作りやすくなっている。
だから、AWSを勉強しているなら、次に新しい教材を開く前に、自分が普段やっていることを一つだけAWS化してみる。食事でも、筋トレでも、勉強でも、趣味でもいい。
「これ、AWSだったらどう作るんだろう?」
そこから実際に一度作ってみる。今回の話が、その一歩のきっかけになれば嬉しい。



