6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Amazon Bedrock AgentCoreとStrands Agentsで冷蔵庫の食材管理&レシピ提案AIエージェントを作る

6
Last updated at Posted at 2026-03-25

Amazon Bedrock AgentCoreとStrands Agentsで冷蔵庫の食材管理&レシピ提案AIエージェントを作る

はじめに

きっかけ

近年、カメラを搭載したARグラスの普及が加速しています。これらのデバイスが真に有用になるには、カメラ映像をリアルタイムに理解し、人間にとって意味のある情報をその場でフィードバックできる AI エージェントが不可欠だと考えています。

たとえば、ARグラスをかけたまま冷蔵庫を開けると「何が作れるか」を即座に提案してくれる、棚の前に立つだけで賞味期限切れを警告してくれる——そんな「現実空間を理解するAIエージェント」の実現に向けた技術が求められています。

本記事はその第一歩として、以下の2つのサービスを組み合わせたエージェントを構築した記録です。

  • Amazon Bedrock AgentCore — サーバーレスでAIエージェントをホスト・デプロイできる基盤
  • Strands Agents — ツール呼び出しや複数ステップの推論を扱うエージェントフレームワーク

ユースケースとして「冷蔵庫の食材管理&レシピ提案」を選んだのは、カメラ映像から現実の物体を認識して有用な情報を返すという、将来のARグラス連携に直結するシナリオだからです。


この記事では、Amazon Bedrock AgentCoreとClaude Sonnet 4のVision機能を組み合わせて、冷蔵庫の写真から食材を認識し、賞味期限を管理しながら最適なレシピを提案するAIエージェントを実装する方法を紹介します。

さらに、Knowledge Base(KB)を活用して、栄養情報や料理のコツ、食材の保存方法などの専門知識を参照しながら、より実用的な提案を実現する実装例も解説します。

実装する機能

  • 📸 食材認識: 冷蔵庫の写真からVision AIで食材を自動識別
  • 🔍 鮮度チェック: Knowledge Baseの保存期間データを参照して賞味期限を推定
  • 🍳 レシピ提案: 手持ちの食材で作れる料理を栄養バランスを考慮して提案
  • 📚 根拠の明示: どのKB資料(栄養データ、料理本など)を参照したかを明確に表示
  • 🚀 サーバーレスデプロイ: CodeBuildでARM64コンテナをビルドし、自動デプロイ

アーキテクチャ

アーキテクチャ全体図
クライアント → 冷蔵庫写真 + メタデータ
← 結果 JSON
AgentCore Runtime
my_agent.py(AWS)
── Step 1: 食材認識 ──
AgentCore Runtime → 冷蔵庫画像を送信
← 食材リスト返却
Bedrock Converse API
Claude Vision
── Step 2: レシピ・栄養情報検索 ──
AgentCore Runtime → 食材リストで検索
← 栄養情報・レシピ(Citation付き)返却
Knowledge Base
RAG
── Step 3: レシピ提案生成 ──
AgentCore Runtime → 食材リスト + KB情報を送信
← 最終レシピ提案 JSON 返却
Bedrock Converse API
Claude Vision

処理は3ステップで構成されています:

  1. Step 1 — Vision API が冷蔵庫画像から食材を客観的に列挙
  2. Step 2 — Knowledge Base で食材に関する栄養・保存・レシピ情報を検索
  3. Step 3 — Step 1 の食材リストと Step 2 の KB 情報を合わせて Vision API に再投入し、根拠付きのレシピ提案を生成

必要なもの

AWS側の準備

  1. AWS Bedrock の有効化

    • Claude 3.5 Sonnet v2 以上のモデルへのアクセス権
    • Inference Profile の作成(推奨: apac.anthropic.claude-sonnet-4-20250514-v1:0
  2. Knowledge Base の作成

    • S3バケットに栄養データベース、レシピ集、食材保存方法のドキュメントをアップロード
    • Bedrock Knowledge Baseを作成し、データソースを設定
    • ベクトル検索用のインデックスを構築
  3. IAM権限

    • bedrock:InvokeModel
    • bedrock-agent:Retrieve
    • bedrock-agent:RetrieveAndGenerate
    • ecr:CreateRepository(初回のみ)
    • iam:CreateRole(実行ロール自動作成の場合)

ローカル環境

# Python 3.10以上
cd src
pip install -r requirements.txt

requirements.txt に必要なパッケージがすべて記載されています(後述)。

ディレクトリ構成

AgentCore/
├── src/
│   ├── my_agent.py                    # メインのエージェント実装(AgentCore にデプロイされる)
│   ├── invoke_agentcore_runtime.py    # ローカルテスト用クライアント
│   ├── requirements.txt               # Python依存関係
│   └── .bedrock_agentcore.yaml        # AgentCore設定ファイル(エントリポイントを指定)
└── sample_images/
    └── fridge_photo.jpg               # テスト用冷蔵庫写真

my_agent.py はどのように使われるか

agentcore init / agentcore launch を実行すると、.bedrock_agentcore.yaml が生成・更新されます。このファイルの entrypoint フィールドに my_agent.py のパスが記録され、AgentCore はこのファイルをコンテナのエントリポイントとして使用します。

# .bedrock_agentcore.yaml(抜粋)
default_agent: my-fridge-agent
agents:
  my-fridge-agent:
    entrypoint: /path/to/src/my_agent.py   # ← このファイルがデプロイ対象
    ...

コード側では @app.entrypoint デコレータを付けた関数が AgentCore からのリクエストを受け取ります。

app = BedrockAgentCoreApp()

@app.entrypoint          # ← AgentCore がこの関数を呼び出す
def invoke(payload: dict):
    ...

実装

1. requirements.txt

strands-agents
strands-agents-tools
bedrock-agentcore
bedrock-agentcore-starter-toolkit
boto3

2. メインエージェント実装(my_agent.py)

"""
冷蔵庫食材管理 & レシピ提案 AI エージェント
Amazon Bedrock AgentCore + Claude Vision + Knowledge Base
"""
import os
import json
import base64
import traceback

import boto3
from bedrock_agentcore.runtime import BedrockAgentCoreApp

app = BedrockAgentCoreApp()

# Bedrock / KB クライアント
BR = boto3.client("bedrock-runtime")
BA = boto3.client("bedrock-agent-runtime")

# モデルIDとKB IDを環境変数から取得
MODEL_ID = os.environ.get(
    "BEDROCK_MODEL_ID",
    "apac.anthropic.claude-sonnet-4-20250514-v1:0",
)

KB_ID = os.environ.get("KB_ID")  # 環境変数に設定しておく


def call_vision_model(prompt: str, image_b64: str) -> str:
    """Converse API で Vision を叩く(PIL 不要)"""
    image_bytes = base64.b64decode(image_b64)

    message = {
        "role": "user",
        "content": [
            {
                "image": {
                    "format": "jpeg",
                    "source": {"bytes": image_bytes},
                }
            },
            {"text": prompt},
        ],
    }

    resp = BR.converse(
        modelId=MODEL_ID,
        messages=[message],
        inferenceConfig={"maxTokens": 4096, "temperature": 0.1},
    )

    out_msg = resp["output"]["message"]
    text = ""
    for part in out_msg.get("content", []):
        if "text" in part:
            text += part["text"]
    return text


def call_kb(summary_query: str, context: dict | None = None) -> dict:
    """Knowledge Base から食材情報・栄養データ・レシピを RetrieveAndGenerate"""
    if not KB_ID:
        return {"summary": "", "citations": [], "retrieved_content": []}

    context = context or {}
    prompt = (
        "以下の冷蔵庫内の食材について、栄養情報や保存期間、"
        "これらの食材で作れるレシピを提案してください。"
        "栄養バランスや調理時間も考慮し、200〜300字で要約し、"
        "必ず citation(レシピ名・出典・URL)を返してください。\n"
        f"食材リスト: {summary_query}\n"
        f"家族構成: {json.dumps(context, ensure_ascii=False)}"
    )

    resp = BA.retrieve_and_generate(
        input={"text": prompt},
        retrieveAndGenerateConfiguration={
            "type": "KNOWLEDGE_BASE",
            "knowledgeBaseConfiguration": {
                "knowledgeBaseId": KB_ID,
                "modelArn": f"arn:aws:bedrock:ap-northeast-1::foundation-model/{MODEL_ID}",
                "retrievalConfiguration": {
                    "vectorSearchConfiguration": {
                        "numberOfResults": 5
                    }
                }
            }
        }
    )

    summary = resp["output"]["text"]
    cites = []
    retrieved_content = []

    # Citation情報を詳細に抽出
    for c in resp.get("citations", []):
        for r in c.get("retrievedReferences", []):
            cite_info = {
                "title": r.get("metadata", {}).get("title", "不明な文書"),
                "url": r.get("location", {}).get("s3Location", {}).get("uri", ""),
                "updated_at": r.get("metadata", {}).get("updated_at", ""),
                "content_excerpt": (
                    r.get("content", {}).get("text", "")[:300] + "..."
                    if r.get("content", {}).get("text") else ""
                )
            }
            cites.append(cite_info)

            # 参照した具体的な内容も保存
            if r.get("content", {}).get("text"):
                retrieved_content.append({
                    "source": cite_info["title"],
                    "content": r.get("content", {}).get("text"),
                    "relevance_score": r.get("metadata", {}).get("score", 0)
                })

    return {
        "summary": summary,
        "citations": cites,
        "retrieved_content": retrieved_content,
        "kb_query": prompt
    }


@app.entrypoint
def invoke(payload: dict):
    """
    AgentCore Runtime エントリポイント

    期待する入力:
      {
        "prompt": "...",
        "media":  { "type": "image", "format": "jpeg", "data": "<base64>" },
        "meta":   { "family_size": 2, "dietary_restriction": "なし" }
      }
    """
    try:
        print("payload keys:", list(payload.keys()))

        media = payload.get("media") or {}
        meta = payload.get("meta") or {}
        img_b64 = media.get("data")

        if not img_b64:
            return {
                "result": {
                    "role": "assistant",
                    "content": [{"text": "画像が提供されていません"}],
                }
            }

        # ── ステップ1: 客観的な食材認識 ──────────────────────────────
        basic_vision_prompt = (
            "この冷蔵庫の写真を詳細に分析し、以下の情報を抽出してください:\n"
            "- 見える食材の種類と量\n"
            "- 食材の状態(新鮮さ、傷み具合など)\n"
            "- 調味料やドリンク類\n"
            "- その他観察できる重要な要素\n"
            "簡潔に箇条書きで記述してください。"
        )
        scene_description = call_vision_model(basic_vision_prompt, img_b64)

        # ── ステップ2: KB で食材情報・レシピを検索 ───────────────────
        kb_result = call_kb(scene_description, context=meta)

        # ── ステップ3: KB 情報を踏まえた最終レシピ提案 ───────────────
        enhanced_vision_prompt = (
            "あなたは栄養士と料理の専門家です。"
            "冷蔵庫の食材分析と栄養・レシピ情報を参考に、最適な提案をしてください。\n\n"
            f"【冷蔵庫の食材】\n{scene_description}\n\n"
            f"【栄養情報・レシピ提案】\n{kb_result.get('summary', '情報なし')}\n\n"
            "上記を踏まえて、次の形式の JSON で返してください:\n"
            "{\n"
            '  "ingredients":[{\n'
            '    "name":"食材名",\n'
            '    "quantity":"推定量",\n'
            '    "freshness":"新鮮/やや古い/要注意",\n'
            '    "estimated_days":"保存可能日数(KBから推定)",\n'
            '    "kb_reference":true/false,\n'
            '    "kb_source":"参照したKB資料名"\n'
            '  }],\n'
            '  "recipe_suggestions":[{\n'
            '    "name":"レシピ名",\n'
            '    "description":"簡単な説明",\n'
            '    "ingredients_needed":["必要な食材"],\n'
            '    "cooking_time":"調理時間",\n'
            '    "kb_source":"参照したレシピ出典"\n'
            '  }],\n'
            '  "summary":"冷蔵庫の状況と提案の要約"\n'
            "}\n"
            "kb_reference は、その判断に KB 情報が役立った場合に true にし、"
            "kb_source には具体的に参照した KB 資料名を記載してください。"
        )
        vision_text = call_vision_model(enhanced_vision_prompt, img_b64)

        # ── ステップ4: JSON パース ────────────────────────────────────
        result_obj: dict
        try:
            cleaned = vision_text.strip()
            if cleaned.startswith("```"):
                cleaned = cleaned.strip("`")
                if cleaned.startswith("json"):
                    cleaned = cleaned[4:]
            result_obj = json.loads(cleaned)
        except Exception:
            result_obj = {
                "ingredients": [],
                "recipe_suggestions": [],
                "summary": "",
                "raw_text": vision_text,
            }

        # KB 結果を含める
        result_obj["kb_nutrition_data"] = kb_result

        # ── ステップ5: 人が読みやすいサマリを作成 ─────────────────────
        lines = []
        if result_obj.get("summary"):
            lines.append("【冷蔵庫の状況】")
            lines.append(result_obj["summary"])
            lines.append("")

        if result_obj.get("ingredients"):
            lines.append("【食材リスト】")
            for ing in result_obj["ingredients"]:
                kb_ref = (
                    f" (KB参照: {ing.get('kb_source', '不明')})"
                    if ing.get("kb_reference") else ""
                )
                freshness_icon = {
                    "新鮮": "",
                    "やや古い": "⚠️",
                    "要注意": "",
                }.get(ing.get("freshness", ""), "")
                lines.append(
                    f"{freshness_icon} {ing.get('name')} "
                    f"({ing.get('quantity')}) - "
                    f"保存可能: 約{ing.get('estimated_days')}{kb_ref}"
                )

        if result_obj.get("recipe_suggestions"):
            lines.append("")
            lines.append("【おすすめレシピ】")
            for i, recipe in enumerate(result_obj["recipe_suggestions"], 1):
                lines.append(
                    f"\n{i}. {recipe.get('name')} (調理時間: {recipe.get('cooking_time')})"
                )
                lines.append(f"   {recipe.get('description')}")
                lines.append(
                    f"   必要な食材: {', '.join(recipe.get('ingredients_needed', []))}"
                )
                if recipe.get("kb_source"):
                    lines.append(f"   出典: {recipe['kb_source']}")

        if kb_result.get("summary"):
            lines.append("")
            lines.append("【参照した栄養・レシピ情報】")
            lines.append(kb_result["summary"])

            if kb_result.get("citations"):
                lines.append("")
                lines.append("【参照資料】")
                for i, cite in enumerate(kb_result["citations"], 1):
                    lines.append(f"{i}. {cite.get('title', '不明な文書')}")
                    if cite.get("updated_at"):
                        lines.append(f"   更新日: {cite['updated_at']}")
                    if cite.get("content_excerpt"):
                        lines.append(f"   抜粋: {cite['content_excerpt']}")
                    lines.append("")

        result_obj["summary_pretty"] = (
            "\n".join(lines) if lines else "食材が確認できませんでした。"
        )

        return {
            "result": {
                "role": "assistant",
                "content": [
                    {"text": json.dumps(result_obj, ensure_ascii=False)}
                ],
            }
        }

    except Exception as e:
        traceback.print_exc()
        return {
            "result": {
                "role": "assistant",
                "content": [{"text": f"Runtime error: {e}"}],
            }
        }


if __name__ == "__main__":
    app.run()

3. ローカルテスト用クライアント(invoke_agentcore_runtime.py)

"""
冷蔵庫食材管理 & レシピ提案 AI エージェント – ローカルテスト用クライアント

使い方:
  export AGENT_RUNTIME_ARN="arn:aws:bedrock-agentcore:ap-northeast-1:XXXX:runtime/test-XXXX"
  python invoke_agentcore_runtime.py ./fridge_photo.jpg [family_size] [dietary_restriction]
"""
import os
import json
import base64
import uuid
import boto3

AGENT_RUNTIME_ARN = os.environ["AGENT_RUNTIME_ARN"]
AWS_REGION = os.environ.get("AWS_REGION", "ap-northeast-1")


def read_image_base64(path: str) -> str:
    with open(path, "rb") as f:
        return base64.b64encode(f.read()).decode("utf-8")


def build_runtime_payload(
    image_b64: str,
    family_size: int = 2,
    dietary_restriction: str = "なし",
) -> bytes:
    """AgentCore Runtime 用のペイロード作成"""
    payload_obj = {
        "prompt": "冷蔵庫の食材を分析し、レシピを提案してください。",
        "media": {
            "type": "image",
            "format": "jpeg",
            "data": image_b64,
        },
        "meta": {
            "family_size": family_size,
            "dietary_restriction": dietary_restriction,
        },
    }
    return json.dumps(payload_obj, ensure_ascii=False).encode("utf-8")


def invoke_agentcore_runtime(
    image_path: str,
    family_size: int,
    dietary_restriction: str,
) -> None:
    """AgentCore Runtime を呼び出す"""
    session_id = f"session-{uuid.uuid4()}"
    print(f"Using session_id: {session_id}")

    image_b64 = read_image_base64(image_path)
    payload_bytes = build_runtime_payload(image_b64, family_size, dietary_restriction)

    # boto3 サービス名: bedrock-agentcore
    client = boto3.client("bedrock-agentcore", region_name=AWS_REGION)

    response = client.invoke_agent_runtime(
        agentRuntimeArn=AGENT_RUNTIME_ARN,
        qualifier="DEFAULT",
        runtimeSessionId=session_id,
        contentType="application/json",
        accept="application/json",
        payload=payload_bytes,
    )

    print("statusCode :", response.get("statusCode"))
    print("sessionId  :", response.get("runtimeSessionId"))

    body = response["response"].read().decode("utf-8")

    try:
        result = json.loads(body)
    except json.JSONDecodeError:
        print("=== raw body ===")
        print(body)
        return

    # result 構造: { "result": { "role": "assistant", "content": [{"text": "..."}] } }
    content = result.get("result", {}).get("content", [])
    if not content:
        print("レスポンスに content がありません。")
        print(json.dumps(result, ensure_ascii=False, indent=2))
        return

    try:
        output = json.loads(content[0]["text"])
    except (json.JSONDecodeError, KeyError):
        print(content[0].get("text", ""))
        return

    # 読みやすいサマリを表示
    print("\n" + "=" * 60)
    print(output.get("summary_pretty", "結果なし"))
    print("=" * 60)

    # JSON 全体を result.json に保存
    with open("result.json", "w", encoding="utf-8") as f:
        json.dump(output, f, ensure_ascii=False, indent=2)
    print("\n詳細結果を result.json に保存しました。")


if __name__ == "__main__":
    import sys

    if len(sys.argv) < 2:
        print(
            "Usage: python invoke_agentcore_runtime.py <image_path> "
            "[family_size] [dietary_restriction]"
        )
        sys.exit(1)

    img_path = sys.argv[1]
    fam_size = int(sys.argv[2]) if len(sys.argv) > 2 else 2
    diet = sys.argv[3] if len(sys.argv) > 3 else "なし"

    invoke_agentcore_runtime(img_path, fam_size, diet)

デプロイ手順

1. プロジェクト初期化

cd src
agentcore init

2. 環境変数設定

export KB_ID="your-knowledge-base-id"
export BEDROCK_MODEL_ID="apac.anthropic.claude-sonnet-4-20250514-v1:0"
export AWS_REGION="ap-northeast-1"

⚠️ シークレット管理について
export はシェル履歴に記録されるため、実際の開発・本番環境では以下のいずれかの方法を推奨します。

  • AWS Secrets Manager または AWS Systems Manager Parameter Store で値を管理し、コードから参照する
  • .env ファイルに書き出す場合は必ず .gitignore に登録してリポジトリにコミットしないようにする

3. CodeBuildでデプロイ

agentcore launch

このコマンドで以下が自動実行されます:

  • ECRリポジトリの作成(初回のみ)
  • 実行ロールの作成
  • CodeBuildプロジェクトの作成
  • ARM64コンテナのビルド
  • AgentCore Runtimeへのデプロイ

4. デプロイ完了後の確認

# .bedrock_agentcore.yaml からARNを確認
cat .bedrock_agentcore.yaml | grep agent_arn

# 環境変数に設定
export AGENT_RUNTIME_ARN="arn:aws:bedrock-agentcore:ap-northeast-1:xxxxx:runtime/test-xxxxx"

実行例

事前準備

# ディレクトリ構成
AgentCore/
├── src/
│   ├── my_agent.py
│   ├── invoke_agentcore_runtime.py
│   ├── requirements.txt
│   └── .bedrock_agentcore.yaml
└── sample_images/
    └── fridge_photo.jpg   # 冷蔵庫の写真を配置

今回のテストでは、ChatGPT(DALL·E)で生成した冷蔵庫の画像を使用しました。

fridge_photo.jpg

実際の冷蔵庫写真がなくても、AI生成画像でも十分に食材認識が機能することを確認できました。
5MB を超える画像は sips -Z 2000 fridge_photo.jpg でリサイズしてください。

デプロイ

cd src
agentcore launch

実行

cd src
export AGENT_RUNTIME_ARN="arn:aws:bedrock-agentcore:ap-northeast-1:xxxxxxxxxxxx:runtime/test-xxxxxxxxxx"
export AWS_REGION="ap-northeast-1"
# KB_IDは省略可(未設定でもVisionのみで動作)
# export KB_ID="your-knowledge-base-id"

python invoke_agentcore_runtime.py ../sample_images/fridge_photo.jpg

⚠️ 個人情報・プライバシーについて
冷蔵庫の写真には、家族の姿が映り込んでいたり、住所が書かれたメモや購入レシートなど個人情報が含まれる可能性があります。プライバシーに配慮した撮影をお勧めします。

  • 身元特定可能な情報が含まれない角度から撮影する
  • 番号・住所・顔などが写り込んでいる場合はトリミングまたはマスク処理を行う
  • 本番運用では画像を外部 API にそのまま送信するのではなく、S3経由で渡すなど匿名の伝送方法も検討する

実際の出力

$ python invoke_agentcore_runtime.py ../sample_images/fridge_photo.jpg
Using session_id: session-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
statusCode : 200
sessionId  : session-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

============================================================
【冷蔵庫の状況】
この冷蔵庫は非常に整理整頓されており、野菜類が豊富で栄養バランスが優秀です。特に緑黄色野菜(ブロッコリー、人参)、葉物野菜、果菜類がバランス良く揃っています。卵や肉類などのタンパク質も適量あり、完全な食事を作ることができます。食材の状態は全体的に新鮮で、適切な保存容器に分類されているため食材ロスのリスクも低いです。葉物野菜やアボカドなど日持ちの短い食材から優先的に使用することをお勧めします。

【食材リスト】
✅ ブロッコリー (2-3房) - 保存可能: 約3-5日日
✅ 人参 (5-6本) - 保存可能: 約7-10日日
✅ ミニトマト (1パック) - 保存可能: 約5-7日日
✅ レタス・葉物野菜 (複数種類) - 保存可能: 約3-5日日
✅ 卵 (1パック(10個程度)) - 保存可能: 約14-21日日
✅ 肉類(生肉・ハム) (適量) - 保存可能: 約2-3日(生肉)、7-10日(ハム)日
✅ きのこ類 (複数種類) - 保存可能: 約5-7日日
✅ とうもろこし (2本) - 保存可能: 約3-5日日
✅ アボカド (1個) - 保存可能: 約2-4日日
✅ 柑橘類・りんご (複数個) - 保存可能: 約7-14日日

【おすすめレシピ】

1. 彩り野菜炒め (調理時間: 15分)
   ブロッコリー、人参、きのこ類を使った栄養満点の炒め物
   必要な食材: ブロッコリー, 人参, きのこ類, 調味料

2. 野菜たっぷりオムレツ (調理時間: 10分)
   卵と冷蔵庫の野菜を使った栄養バランスの良い一品
   必要な食材: 卵, ミニトマト, 葉物野菜, きのこ類

3. フレッシュサラダボウル (調理時間: 5分)
   レタス、トマト、アボカドを使った栄養豊富なサラダ
   必要な食材: レタス, ミニトマト, アボカド, 人参

4. 野菜スープ (調理時間: 20分)
   根菜類と葉物野菜を使った温かいスープ
   必要な食材: 人参, 玉ねぎ, ブロッコリー, 葉物野菜

5. とうもろこしとハムの炒飯 (調理時間: 12分)
   とうもろこしとハム、卵を使った満足感のある炒飯
   必要な食材: とうもろこし, ハム, 卵, ご飯
============================================================

詳細結果を result.json に保存しました。

Knowledge Base なしでも Claude Vision だけで食材を正確に識別し、保存期間の推定からレシピ提案まで一連の処理が動作することを確認できました。詳細な JSON 結果は result.json に保存されます。

実装のポイント

1. Vision APIの2段階活用

# 第1段階: 客観的な食材認識
scene_description = call_vision_model(basic_vision_prompt, img_b64)

# 第2段階: KB情報を組み込んだレシピ提案
kb_result = call_kb(scene_description, context=meta)
enhanced_vision_prompt = f"""
【冷蔵庫の食材】
{scene_description}

【栄養情報・レシピ提案】
{kb_result.get('summary')}
"""
vision_text = call_vision_model(enhanced_vision_prompt, img_b64)

この2段階アプローチにより、KB情報で補強された実用的なレシピ提案が可能になります。

2. Knowledge BaseのRetrieve設定

retrieveAndGenerateConfiguration={
    "type": "KNOWLEDGE_BASE",
    "knowledgeBaseConfiguration": {
        "knowledgeBaseId": KB_ID,
        "modelArn": f"arn:aws:bedrock:ap-northeast-1::foundation-model/{MODEL_ID}",
        "retrievalConfiguration": {
            "vectorSearchConfiguration": {
                "numberOfResults": 5  # 検索結果数を調整
            }
        }
    }
}

検索結果数を適切に設定することで、関連性の高いレシピや栄養情報を確実に取得できます。

3. Citation情報の詳細抽出

for c in resp.get("citations", []):
    for r in c.get("retrievedReferences", []):
        cite_info = {
            "title": r.get("metadata", {}).get("title", "不明な文書"),
            "url": r.get("location", {}).get("s3Location", {}).get("uri", ""),
            "updated_at": r.get("metadata", {}).get("updated_at", ""),
            "content_excerpt": r.get("content", {}).get("text", "")[:300] + "..."
        }

どのレシピや栄養データを参照したかを明確にすることで、AIの提案根拠を追跡可能にします。

現在の制限・今後の課題

保存期間は「撮影時点からの残存日数」

今回の実装では購入日を入力する仕組みがないため、出力される保存可能日数は「この食材は一般的に購入後○日間保存できる」という統計的な目安です。実際にいつ購入したかが分からないと、「冷蔵庫に入っている食材があと何日で傷むか」は判断できません。

改善案として、以下のようなアプローチが考えられます:

  • 📅 購入日を手入力でメタデータとして渡すmeta.purchase_dates など)
  • 📷 定期撮影 + 画像差分で「前回から何日経過したか」を推定する
  • 🏷️ OCRでパッケージの賞味期限ラベルを直接読み取る

IAM権限は最小権限の原則で設定する

agentcore launch で自動生成される実行ロールに加え、デプロイ作業者の IAM ユーザーにも適切な権限設定が必要です。

{
    "Effect": "Allow",
    "Action": [
        "iam:CreateRole",
        "iam:AttachRolePolicy",
        "iam:PutRolePolicy",
        "iam:GetRole",
        "iam:PassRole"
    ],
    "Resource": [
        "arn:aws:iam::<アカウントID>:role/AmazonBedrockAgentCoreSDKRuntime-*",
        "arn:aws:iam::<アカウントID>:role/AmazonBedrockAgentCoreSDKCodeBuild-*"
    ]
}

⚠️ Resource* ワイルドカードは開発時の便宜上使用することがありますが、本番環境では <アカウントID> を実際の AWS アカウント ID に置き換え、対象リソースを具体的に絞り込んでください(最小権限の原則)。

この記事では、Amazon Bedrock AgentCoreとVision APIを組み合わせて、冷蔵庫の食材管理とレシピ提案AIエージェントを実装する方法を解説しました。

主要なポイント:

  • ✅ Vision APIで冷蔵庫の食材を自動識別
  • ✅ Knowledge Baseで栄養データとレシピを参照
  • ✅ 2段階のプロンプト設計でKB情報を効果的に活用
  • ✅ Citation情報で提案根拠を明確化
  • ✅ CodeBuildで簡単にデプロイ可能

AgentCoreを使うことで、複雑なインフラ構築なしに、本格的なAIエージェントをサーバーレスで運用できます。

Knowledge Baseとの組み合わせにより、単なる画像認識を超えた、栄養学やレシピの専門知識に基づく実用的な提案が可能になります。

応用例

このシステムは以下のような用途にも展開できます:

  • 🍱 お弁当作り支援: 冷蔵庫の食材から明日のお弁当レシピを提案
  • 🛒 買い物リスト自動生成: 不足している食材を検出し、買い物リストを作成
  • 💰 食費管理: 食材の無駄を減らし、家計の節約をサポート
  • 🏋️ ダイエット支援: カロリー計算や栄養バランスを考慮したレシピ提案
  • 🍼 離乳食管理: 月齢に応じた離乳食レシピを提案
  • 🌾 アレルギー対応: 特定の食材を避けたレシピを自動生成

質問やフィードバックがあれば、コメント欄でお気軽にどうぞ!

6
2
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
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?