1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「知っている」を「できる」に変えるには

1
Posted at

「知っている」を「できる」に変えるには

勉強会のLTで「『知っている』を『できる』に変えるには」というテーマで登壇した。題材は、普段からやっている食事管理。テキストで食事内容を送るとAIがカロリーとPFC(タンパク質・脂質・炭水化物)を推定し、記録・週次集計までしてくれる小さなPoCをAWSで作ってみた話だ。

この記事では、LTで話した内容に加えて、スライドに載せきれなかった構成図や、実際にハマった3つのエラーの中身まで書いておく。

記録して、改善するのが好き

好きなものはそば、コーヒー、ビール。趣味は「記録し、改善すること」。

原体験がある。幼い頃かなり太っていて、学校でおかわりを禁止された生徒だった。そこから食事を記録して可視化し、減量に成功した。それ以来、食事も学習も、記録して見える化して少しずつ改善するのが癖になっている。

AWSの資格勉強も、この「コツコツ記録して改善する」やり方でなんとか進められた。Lambda、DynamoDB、S3、API Gateway、Bedrock……単語カードみたいに、一つひとつは説明できる。

「知っている」と「できる」のギャップ

知っていること:AWS単体のサービス概要。
できないこと:複数サービスを繋いで機能させること。

image.png

資格試験は「What(何を知っているか)」を問うものが多く、「How(どう繋ぐか)」は実際に手を動かさないと身につかない。特にAIを使っていると、この2つが混在してしまう感覚があった。AIが答えをすぐ出してくれるので、自分ができるような気になってしまう。

見分ける方法は何だろう、と考えたときに出た仮説はシンプルだった。自分の手で作って、言葉にしてみることではないか。

image.png

何を作ったか

題材には、普段からやっている食事管理を選んだ。今は食事記録アプリを使っているが、データを自分で活用したり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週間分を読み込んで集計する。

image.png

実際に動かすとこうなる

① 「鶏胸肉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を使った開発が増えると、レビューする機会も増える。自分のできないことに気づきながら仮説を持って質問できれば、上司のレビューの負担も減らせるはずだ。

image.png

これ、AWSだったらどう作る?

今回作ったものは、ものすごく高度なサービスではない。むしろAIを使えば、小さなPoCならかなり作りやすくなっている。

だから、AWSを勉強しているなら、次に新しい教材を開く前に、自分が普段やっていることを一つだけAWS化してみる。食事でも、筋トレでも、勉強でも、趣味でもいい。

「これ、AWSだったらどう作るんだろう?」

そこから実際に一度作ってみる。今回の話が、その一歩のきっかけになれば嬉しい。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?