4
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?

Amazon Bedrock AgentCoreで、Slackに回答を行う栄養指導エージェントを作成する

Last updated at Posted at 2025-10-27

本記事について

Amazon Bedrock AgentCoreに入門しつつ、AIエージェントを活用したアプリケーションを何か作れないかと思い執筆しました。

導入(AgentCoreのデプロイ方法について記載しました。他の記事でも数多く紹介されているかと思いますので、要点が気になる方は本項はスキップいただくことをお勧めします。)

導入〜チュートリアルを一通りこなしてみる〜

Amazon Bedrock AgentCoreの使い方を学ぶためにチュートリアルを実施してみます。
チュートリアルを参考に、エージェントをAmazon Bedrock AgentCoreにデプロイしてみます。

bedrock-agentcore-starter-toolkitをインストール

uvで仮想環境を作成

$ uv venv
Using CPython 3.12.9
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate
$ source .venv/bin/activate

Agent作成のためのライブラリ群をインストール

https://github.com/aws/bedrock-agentcore-starter-toolkit

を参考に以下をインストールします。

 $ uv add strands-agents bedrock-agentcore bedrock-agentcore-starter-toolkit

Agentをローカル実行

以下で提供されているサンプルコードを貼り付け、実行してみます。(※AWSアカウントへの認証情報の設定に関しては済んでいる前提。)

https://aws.github.io/bedrock-agentcore-starter-toolkit/user-guide/runtime/quickstart.html

 uv run python main.py
uv run python main.py
{"timestamp": "2025-09-28T12:50:30.373Z", "level": "INFO", "message": "Invocation completed successfully (2.204s)", "logger": "bedrock_agentcore.app", "requestId": "f789f3c2-977e-4023-84a5-5eaf84e81fb5"}
# 別ターミナルで以下を叩く
 curl -X POST http://localhost:8080/invocations \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Hello!"}'
{"result": {"role": "assistant", "content": [{"text": "Hello! It's nice to meet you. How are you doing today? Is there anything I can help you with?"}]}}             

agentcore --helpコマンドでコマンドのオプションが確認できるようです。

$ agentcore --help
                                     
 Usage: agentcore [OPTIONS] COMMAND [ARGS]...                                                                                                                                                       
                                                                                                                                                                                                    
 BedrockAgentCore CLI                                                                                                                                                                               
                                                                                                                                                                                                    
╭─ Options ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --help          Show this message and exit.                                                                                                                                                      │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ invoke                      Invoke Bedrock AgentCore endpoint.                                                                                                                                   │
│ status                      Get Bedrock AgentCore status including config and runtime details.                                                                                                   │
│ launch                      Launch Bedrock AgentCore with three deployment modes.                                                                                                                │
│ import-agent                Use a Bedrock Agent to generate a LangChain or Strands agent with AgentCore primitives.                                                                              │
│ destroy                     Destroy Bedrock AgentCore resources.                                                                                                                                 │
│ create_mcp_gateway          Creates an MCP Gateway.                                                                                                                                              │
│ create_mcp_gateway_target   Creates an MCP Gateway Target.                                                                                                                                       │
│ configure                   Configuration management                                                                                                                                             │
│ gateway                     Manage Bedrock AgentCore Gateways                                                                                                                                    │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

agentcore launchを使うとデプロイを行うことができるとの記載があったので、コマンドを叩いてみます。
実際にデプロイしようとしたら以下エラーが表示されました。

$ agentcore launch
🚀 Launching Bedrock AgentCore (codebuild mode - RECOMMENDED)...
   • Build ARM64 containers in the cloud with CodeBuild
   • No local Docker required (DEFAULT behavior)
   • Production-ready deployment

💡 Deployment options:
   • agentcore launch                → CodeBuild (current)
   • agentcore launch --local        → Local development
   • agentcore launch --local-build  → Local build + cloud deploy

❌ .bedrock_agentcore.yaml not found. Run 'agentcore configure --entrypoint <file>' first

どうやらagentcore configure --entrypoint <file>の実行を事前にする必要があるようです。

エラーの表示通り、agentcore configure --entrypoint <file>を実際のファイルに置き換えて実行してみます。
以下のような表示が出ました。IAMロールの作成が必要なようですが、未作成の場合はEnterを押下することで自動作成してくれるようです。

$ agentcore configure --entrypoint main.py
Configuring Bedrock AgentCore...
Entrypoint parsed: file=/Users/XXX/dev/agentcore-test/main.py, bedrock_agentcore_name=main
Agent name: main

🔐 Execution Role
Press Enter to auto-create execution role, or provide execution role ARN/name to use existing
Execution role ARN/name (or press Enter to auto-create):

→Enterを押下

Enter押下後、以下の表示が出ました。こちらもECRリポジトリが未作成の場合は作成してくれるらしいです。

🏗️  ECR Repository
Press Enter to auto-create ECR repository, or provide ECR Repository URI to use existing

→Enterを押下

依存関係が記載されたファイルを指定します。(pyproject.tomlが存在する場合自動的に検出してくれるようです)

🔍 Detected dependency file: pyproject.toml
Press Enter to use this file, or type a different path (use Tab for autocomplete):
Path or Press Enter to use detected dependency file:

→Enterを押下


🔐 Authorization Configuration
By default, Bedrock AgentCore uses IAM authorization.
Configure OAuth authorizer instead? (yes/no) [no]:

→認証機能をつけるかどうかの設定であるようで、デフォルトではIAM認証を利用するようです。
今回はデフォルトのno(IAM authorization)を選択することとします。

🔒 Request Header Allowlist
Configure which request headers are allowed to pass through to your agent.
Common headers: Authorization, X-Amzn-Bedrock-AgentCore-Runtime-Custom-*
Configure request header allowlist? (yes/no) [no]:

→リクエストを許可するリストをこちらで設定できるようです。こちらも今回はデフォルトのnoを選択することとします。

上記のやり取りが終わったのち、設定が完了するようです。このタイミングで.bedrock_agentcore.yamlが作成されました。これでagentcore launchが実行可能になるようです。

Configure request header allowlist? (yes/no) [no]:
✓ Using default request header configuration
Configuring BedrockAgentCore agent: main
Generated .dockerignore
Generated Dockerfile: /Users/maeno/dev/agentcore-test/Dockerfile
Generated .dockerignore: /Users/maeno/dev/agentcore-test/.dockerignore
Setting 'main' as default agent
╭───────────────────────────────────────────────────────────────────────────────────── Configuration Success ──────────────────────────────────────────────────────────────────────────────────────╮
│ Configuration Complete                                                                                                                                                                           │
│                                                                                                                                                                                                  │
│ Agent Details:                                                                                                                                                                                   │
│ Agent Name: main                                                                                                                                                                                 │
│ Runtime: Docker                                                                                                                                                                                  │
│ Region: <your-region>                                                                                                                                                                      │
│ Account: <your-account-id>                                                                                                                                                                            │
│                                                                                                                                                                                                  │
│ Configuration:                                                                                                                                                                                   │
│ Execution Role: None                                                                                                                                                                             │
│ ECR Repository: Auto-create                                                                                                                                                                      │
│ Authorization: IAM (default)                                                                                                                                                                     │
│                                                                                                                                                                                                  │
│ 📄 Config saved to: /Users/maeno/dev/agentcore-test/.bedrock_agentcore.yaml                                                                                                                      │
│                                                                                                                                                                                                  │
│ Next Steps:                                                                                                                                                                                      │
│    agentcore launch                                 

以下.bedrock_agentcore.yamlの内容です。

default_agent: main
agents:
  main:
    name: main
    entrypoint: main.py
    platform: linux/arm64
    container_runtime: docker
    aws:
      execution_role: null
      execution_role_auto_create: true
      account: '<your-account-id>'
      region: us-east-1
      ecr_repository: null
      ecr_auto_create: true
      network_configuration:
        network_mode: PUBLIC
      protocol_configuration:
        server_protocol: HTTP
      observability:
        enabled: true
    bedrock_agentcore:
      agent_id: null
      agent_arn: null
      agent_session_id: null
    codebuild:
      project_name: null
      execution_role: null
      source_bucket: null
    authorizer_configuration: null
    request_header_configuration: null
    oauth_configuration: null

agentcore launchを叩いてみます。

$ agentcore launch
🚀 Launching Bedrock AgentCore (codebuild mode - RECOMMENDED)...
   • Build ARM64 containers in the cloud with CodeBuild
   • No local Docker required (DEFAULT behavior)
   • Production-ready deployment

💡 Deployment options:
   • agentcore launch                → CodeBuild (current)
   • agentcore launch --local        → Local development
   • agentcore launch --local-build  → Local build + cloud deploy

Starting CodeBuild ARM64 deployment for agent 'main' to account <your-account-id> (your-region)
Setting up AWS resources (ECR repository, execution roles)...
Getting or creating ECR repository for agent: main
Repository doesn't exist, creating new ECR repository: bedrock-agentcore-main
⠹ Launching Bedrock AgentCore...✅ ECR repository available: <your-account-id>.dkr.ecr.<your-region>.amazonaws.com/bedrock-agentcore-main
Getting or creating execution role for agent: main
Using AWS region: us-east-1, account ID: <your-account-id>
Role name: AmazonBedrockAgentCoreSDKRuntime-us-east-1-0d6e4079e3
⠹ Launching Bedrock AgentCore...Role doesn't exist, creating new execution role: AmazonBedrockAgentCoreSDKRuntime-us-east-1-0d6e4079e3
Starting execution role creation process for agent: main
✓ Role creating: AmazonBedrockAgentCoreSDKRuntime-us-east-1-0d6e4079e3
Creating IAM role: AmazonBedrockAgentCoreSDKRuntime-us-east-1-0d6e4079e3
⠴ Launching Bedrock AgentCore...✓ Role created: arn:aws:iam::<your-account-id>:role/AmazonBedrockAgentCoreSDKRuntime-us-east-1-0d6e4079e3
⠏ Launching Bedrock AgentCore...✓ Execution policy attached: BedrockAgentCoreRuntimeExecutionPolicy-main
Role creation complete and ready for use with Bedrock AgentCore
✅ Execution role available: arn:aws:iam::<your-account-id>:role/AmazonBedrockAgentCoreSDKRuntime-us-east-1-0d6e4079e3
Preparing CodeBuild project and uploading source...
⠏ Launching Bedrock AgentCore...Getting or creating CodeBuild execution role for agent: main
Role name: AmazonBedrockAgentCoreSDKCodeBuild-us-east-1-0d6e4079e3
⠏ Launching Bedrock AgentCore...CodeBuild role doesn't exist, creating new role: AmazonBedrockAgentCoreSDKCodeBuild-us-east-1-0d6e4079e3
Creating IAM role: AmazonBedrockAgentCoreSDKCodeBuild-us-east-1-0d6e4079e3
⠏ Launching Bedrock AgentCore...✓ Role created: arn:aws:iam::<your-account-id>:role/AmazonBedrockAgentCoreSDKCodeBuild-us-east-1-0d6e4079e3
Attaching inline policy: CodeBuildExecutionPolicy to role: AmazonBedrockAgentCoreSDKCodeBuild-us-east-1-0d6e4079e3
⠹ Launching Bedrock AgentCore...✓ Policy attached: CodeBuildExecutionPolicy
Waiting for IAM role propagation...
⠇ Launching Bedrock AgentCore...CodeBuild execution role creation complete: arn:aws:iam::<your-account-id>:role/AmazonBedrockAgentCoreSDKCodeBuild-us-east-1-0d6e4079e3
⠼ Launching Bedrock AgentCore...Created S3 bucket: bedrock-agentcore-codebuild-sources-<your-account-id>-us-east-1
Using .dockerignore with 44 patterns
⠋ Launching Bedrock AgentCore...Uploaded source to S3: main/source.zip
⠸ Launching Bedrock AgentCore...Created CodeBuild project: bedrock-agentcore-main-builder
Starting CodeBuild build (this may take several minutes)...
⠧ Launching Bedrock AgentCore...Starting CodeBuild monitoring...
⠋ Launching Bedrock AgentCore...🔄 QUEUED started (total: 0s)
⠴ Launching Bedrock AgentCore...✅ QUEUED completed in 1.2s
🔄 PROVISIONING started (total: 1s)
⠧ Launching Bedrock AgentCore...✅ PROVISIONING completed in 7.3s
🔄 DOWNLOAD_SOURCE started (total: 9s)
⠙ Launching Bedrock AgentCore...✅ DOWNLOAD_SOURCE completed in 1.2s
🔄 BUILD started (total: 10s)
⠼ Launching Bedrock AgentCore...✅ BUILD completed in 12.2s
🔄 POST_BUILD started (total: 22s)
⠴ Launching Bedrock AgentCore...✅ POST_BUILD completed in 7.3s
🔄 COMPLETED started (total: 29s)
⠙ Launching Bedrock AgentCore...✅ COMPLETED completed in 1.2s
🎉 CodeBuild completed successfully in 0m 30s
CodeBuild completed successfully
✅ CodeBuild project configuration saved
Deploying to Bedrock AgentCore...
⠧ Launching Bedrock AgentCore...✅ Agent created/updated: arn:aws:bedrock-agentcore:us-east-1:<your-account-id>:runtime/main-M274PpCNM0
Observability is enabled, configuring Transaction Search...
⠏ Launching Bedrock AgentCore...Created/updated CloudWatch Logs resource policy
⠼ Launching Bedrock AgentCore...Configured X-Ray trace segment destination to CloudWatch Logs
⠧ Launching Bedrock AgentCore...X-Ray indexing rule already configured
✅ Transaction Search configured: resource_policy, trace_destination
🔍 GenAI Observability Dashboard:
   https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#gen-ai-observability/agent-core
Polling for endpoint to be ready...
⠙ Launching Bedrock AgentCore...Agent endpoint: arn:aws:bedrock-agentcore:us-east-1:<your-account-id>:runtime/main-M274PpCNM0/runtime-endpoint/DEFAULT
Deployment completed successfully - Agent: arn:aws:bedrock-agentcore:us-east-1:<your-account-id>:runtime/main-M274PpCNM0
╭─────────────────────────────────────────────────────────────────────────────────────── Deployment Success ───────────────────────────────────────────────────────────────────────────────────────╮
│ ✅ CodeBuild Deployment Successful!                                                                                                                                                              │
│                                                                                                                                                                                                  │
│ Agent Details:                                                                                                                                                                                   │
│ Agent Name: main                                                                                                                                                                                 │
│ Agent ARN: arn:aws:bedrock-agentcore:us-east-1:<your-account-id>:runtime/main-M274PpCNM0                                                                                                              │
│ ECR URI: <your-account-id>.dkr.ecr.us-east-1.amazonaws.com/bedrock-agentcore-main:latest                                                                                                              │
│ CodeBuild ID: bedrock-agentcore-main-builder:9bc08315-89a8-45dd-b2dd-0664de1e1c2c                                                                                                                │
│                                                                                                                                                                                                  │
│ 🚀 ARM64 container deployed to Bedrock AgentCore                                                                                                                                                 │
│                                                                                                                                                                                                  │
│ Next Steps:                                                                                                                                                                                      │
│    agentcore status                                                                                                                                                                              │
│    agentcore invoke '{"prompt": "Hello"}'                                                                                                                                                        │
│                                                                                                                                                                                                  │
│ 📋 CloudWatch Logs:                                                                                                                                                                              │
│    /aws/bedrock-agentcore/runtimes/main-M274PpCNM0-DEFAULT --log-stream-name-prefix "2025/09/28/[runtime-logs]"                                                                                  │
│    /aws/bedrock-agentcore/runtimes/main-M274PpCNM0-DEFAULT --log-stream-names "otel-rt-logs"                                                                                                     │
│                                                                                                                                                                                                  │
│ 🔍 GenAI Observability Dashboard:                                                                                                                                                                │
│    https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#gen-ai-observability/agent-core                                                                                               │
│                                                                                                                                                                                                  │
│ ⏱️  Note: Observability data may take up to 10 minutes to appear after first launch                                                                                                               │
│                                                                                                                                                                                                  │
│ 💡 Tail logs with:                                                                                                                                                                               │
│    aws logs tail /aws/bedrock-agentcore/runtimes/main-M274PpCNM0-DEFAULT --log-stream-name-prefix "2025/09/28/[runtime-logs]" --follow                                                           │
│    aws logs tail /aws/bedrock-agentcore/runtimes/main-M274PpCNM0-DEFAULT --log-stream-name-prefix "2025/09/28/[runtime-logs]" --since 1h     

スクリーンショット 2025-09-28 22.22.29.png

デプロイが完了したので、エージェントを呼び出してみます。

$ agentcore invoke '{"prompt": "tell me a joke"}'
╭────────────────────────────────────────────────────────────────────────────────────────────── main ──────────────────────────────────────────────────────────────────────────────────────────────╮
│ Session: 3bf3c95c-73bb-4998-a506-14851e3608ac                                                                                                                                                    │
│ Request ID: d51d661b-0b96-4889-913b-b0b9d14ea299                                                                                                                                                 │
│ ARN: arn:aws:bedrock-agentcore:us-east-1:<your-account-id>:runtime/main-M274PpCNM0                                                                                                                    │
│ Logs: aws logs tail /aws/bedrock-agentcore/runtimes/main-M274PpCNM0-DEFAULT --log-stream-name-prefix "2025/09/28/[runtime-logs]" --follow                                                        │
│       aws logs tail /aws/bedrock-agentcore/runtimes/main-M274PpCNM0-DEFAULT --log-stream-name-prefix "2025/09/28/[runtime-logs]" --since 1h                                                      │
│ GenAI Dashboard: https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#gen-ai-observability/agent-core                                                                                 │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

# 以下回答が返ってきた
Response:
{"result": {"role": "assistant", "content": [{"text": "Why don't scientists trust atoms?\n\nBecause they make up everything!"}]}}

CloudWatch> GenAI Observability>Bedrock AgentCoreの部分からトレースを確認できるようです。
スクリーンショット 2025-09-28 22.31.21.png

エラー発生率やモデルのトークン使用数など様々なメトリクスを確認できるようでした。
スクリーンショット 2025-09-28 22.35.23.png

CLI上で数コマンドの実行で、AgentCoreにエージェントをデプロイできることがわかりました。

栄養指導エージェントを作ってみる

個人的に、最近食事管理を意識しているので、何かそれに活用できる簡単なアプリを作成してみることとします。

要件

ざっくり、以下の要件を満たすことができるものを作ることとします。

  • ①ユーザーが自然言語で入力した食品名を理解し、栄養情報を検索する

  • ②取得した情報からPFCバランスなどの栄養情報を計算して、Slackに回答する

米国農務省(USDA)が運営する、食品の栄養素に関する情報を検索できる統合データベース「Food Data Central」において、
OpenAPI形式でAPIが提供されているので、これを参考にして、Agentに検索をしてもらうこととします。

事前にAPIキーを取得(無料)しておく必要があります。
https://fdc.nal.usda.gov/api-key-signup

手順

全体像は以下の図が分かりやすそうです。※下記記事から引用させていただきました。
AgentCore RuntimeでホストされているエージェントがMCPクライアントを通じてAgentCore Gatewayと通信し、Gateway経由で右側の多様なツールを利用する、(その通信に際してAgentCore Identityが関与する)といった具合でしょうか。
image.png

①任意のSlackアプリを作成し、Slack Bot Tokenを控え、Scopesの設定をする

スクリーンショット 2025-10-28 6.01.43.png

Scopesの設定を通して、エージェントがどのようなツールを利用できるかを定義することになります。
以下の設定があれば十分そうです。(channels:history/channels:read/chat:write/users/read
スクリーンショット 2025-10-28 6.21.22.png

②AgentCore Gatewayを作成し、Slackとの統合を設定

この設定により、SlackアプリをMCP対応のツールに対応させることが可能です。
スクリーンショット 2025-10-28 6.08.29.png
スクリーンショット 2025-10-28 6.45.26.png

③CognitoのクライアントID、クライアントシークレット、カスタムスコープを控える。これらをAgentCore Identityのページ>OAuthクライアントの追加のページで入力

Gatewayを作成すると、Cognitoにて、ユーザープールが作成されるため、アプリケーションクライアントのページに移動し、クライアントID、クライアントシークレット、カスタムスコープを確認します。
これらをAgentCore Identityのページ>OAuthクライアントの追加にて入力します。
上記作業は、
エージェントが、自動的にSlackにアクセスするための、OAuth2.0トークン(認証情報)を取得できるように、必要なOAuth2.0クライアント情報をAgentCore Identityの「Vault」(≒情報の保管庫)に登録する作業です。これにより、Agentの実装コードに、認証情報を埋め込まずに済む、といった具合です。
スクリーンショット 2025-10-28 6.27.08.png
スクリーンショット 2025-10-28 6.29.46.png
スクリーンショット 2025-10-28 6.33.53.png

④AgentCore Runtimeにエージェントを作成・デプロイ

$ agentcore launch --env GATEWAY_URL=XXXX --env COGNITO_SCOPE=XXXXX --env FDC_API_KEY=XXXXX

エージェント作成後は、上記コマンドでデプロイを行う必要があります。

コードはGitHubにアップロードしているので全体コードはそちらをご覧ください。

以下のような形で、 FoodData Central APIを叩き、結果を整形して返す独自toolを定義しつつ、特定のチャンネルへ回答をさせるような指示をAgentにしています。

独自ツール
@tool
def search_fdc(query: str) -> dict:
    """
    FoodData Central APIで食品データを検索する

    Args:
        query: 検索したい食品名

    Returns:
        dict: 検索結果(食品リストとFDC ID)

    Example:
        >>> search_fdc("chicken breast")
        {
            "foods": [
                {"fdcId": 171477, "description": "Chicken, broilers or fryers, breast, meat only, raw"},
                ...
            ]
        }
    """
    fdc_api_key = os.environ.get("FDC_API_KEY")
    if not fdc_api_key:
        return {"error": "FDC_API_KEY environment variable is not set"}

    url = f"https://api.nal.usda.gov/fdc/v1/foods/search?api_key={fdc_api_key}"
    payload = {
        "query": query,
        "pageSize": 3,  # 検索結果の上位3件を取得
        "requireAllWords": False,
        "sortBy": "dataType.keyword",
        "sortOrder": "asc",
    }
    headers = {"Content-Type": "application/json"}

    try:
        response = requests.post(url, json=payload, headers=headers, timeout=10)
        response.raise_for_status()
        return response.json()
    except requests.RequestException as e:
        return {"error": f"FDC API request failed: {str(e)}"}


@tool
def get_food_details(fdc_id: int) -> dict:
    """
    食品IDから詳細な栄養素情報を取得する

    Args:
        fdc_id: FDC(FoodData Central)の食品ID

    Returns:
        dict: 食品の詳細な栄養素データ

    Example:
        >>> get_food_details(171477)
        {
            "description": "Chicken, broilers or fryers, breast, meat only, raw",
            "foodNutrients": [
                {"nutrient": {"name": "Energy"}, "amount": 165},
                {"nutrient": {"name": "Protein"}, "amount": 31.0},
                ...
            ]
        }
    """
    fdc_api_key = os.environ.get("FDC_API_KEY")
    if not fdc_api_key:
        return {"error": "FDC_API_KEY environment variable is not set"}

    url = f"https://api.nal.usda.gov/fdc/v1/food/{fdc_id}?api_key={fdc_api_key}"

    try:
        response = requests.get(url, timeout=10)
        response.raise_for_status()
        return response.json()
    except requests.RequestException as e:
        return {"error": f"FDC API request failed: {str(e)}"}


@tool
def analyze_nutrition(fdc_id: int) -> str:
    """
    食品IDから詳細な栄養分析レポートを生成する

    このツールは以下を計算します:
    - カロリー
    - PFCバランス(たんぱく質・脂質・炭水化物の割合)
    - 各種栄養素(食物繊維、ナトリウム、カルシウム、鉄)

    Args:
        fdc_id: FDC(FoodData Central)の食品ID

    Returns:
        str: 詳細な栄養分析レポート(日本語)

    Note:
        - PFCバランスはカロリーベースで計算
        - たんぱく質・炭水化物: 4kcal/g
        - 脂質: 9kcal/g
    """
    # 食品の詳細データを取得
    food_data = get_food_details(fdc_id)

    if "error" in food_data:
        return f"エラー: {food_data['error']}"

    # 栄養素データを辞書に変換
    nutrients = {
        n["nutrient"]["name"]: n["amount"] for n in food_data.get("foodNutrients", [])
    }

    # 主要栄養素の取得
    calories = nutrients.get("Energy", 0)
    protein = nutrients.get("Protein", 0)
    fat = nutrients.get("Total lipid (fat)", 0)
    carbs = nutrients.get("Carbohydrate, by difference", 0)
    fiber = nutrients.get("Fiber, total dietary", 0)
    sodium = nutrients.get("Sodium, Na", 0)
    calcium = nutrients.get("Calcium, Ca", 0)
    iron = nutrients.get("Iron, Fe", 0)

    # PFCバランスを計算
    # たんぱく質・炭水化物: 4kcal/g、脂質: 9kcal/g
    total_macro_cal = (protein + carbs) * 4 + fat * 9
    if total_macro_cal > 0:
        p_pct = (protein * 4) / total_macro_cal * 100
        f_pct = (fat * 9) / total_macro_cal * 100
        c_pct = (carbs * 4) / total_macro_cal * 100
    else:
        p_pct = f_pct = c_pct = 0

    # 栄養分析レポートを生成
    return f"""📊 {food_data.get("description", "食品")}の栄養情報

カロリー: {calories:.0f} kcal
PFCバランス: P{p_pct:.0f}% F{f_pct:.0f}% C{c_pct:.0f}%
たんぱく質: {protein:.1f}g
脂質: {fat:.1f}g
炭水化物: {carbs:.1f}g
食物繊維: {fiber:.1f}g
ナトリウム: {sodium:.0f}mg
カルシウム: {calcium:.0f}mg
鉄: {iron:.1f}mg

※ 100gあたりの値です。
FDC ID: {fdc_id}"""
Agentへの指示
                # エージェント作成
                logger.info("Creating Strands agent...")
                agent = Agent(
                    tools=all_tools,
                    model="us.anthropic.claude-sonnet-4-20250514-v1:0",
                    system_prompt="""あなたはSlack統合と栄養分析が可能なアシスタントです。

利用可能な機能:
1. Slack操作: チャンネル管理、メッセージ送信など
2. 栄養素分析: FoodData Central APIを使用した食品検索と栄養分析

指示:
- 食事や栄養についての質問には、search_fdcで食品を検索し、analyze_nutritionで栄養分析を実行してください
- 分析結果は`#agentcore-slack-integrate-test`チャンネルに投稿してください
- ツールを使用した場合は、使用したツール名とFDC IDを回答に含めてください
- ユーザーのリクエストを正確に理解し、適切な操作を実行してください
"""
main.py
main.py
"""
栄養管理AIエージェント

このエージェントは以下の機能を提供します。
1. Slack連携: チャンネル管理、メッセージ送信など
2. 栄養素分析: FoodData Central APIを使用した食品検索と栄養分析
   API仕様: https://app.swaggerhub.com/apis/fdcnal/food-data_central_api/1.0.1

必要な環境変数:
- GATEWAY_URL: Slackツールを提供するGatewayのエンドポイント(必須)
- COGNITO_SCOPE: Cognito OAuth2のスコープ(必須)
  例: slack-gateway/genesis-gateway:invoke
- WORKLOAD_NAME: Workload Identity名(デフォルト: nutrition_agent)
  例: nutrition_agent-vrJNU36B6Q
- FDC_API_KEY: FoodData Central APIキー(必須)
- USER_ID: ユーザー識別子(デフォルト: test-user)
  * 任意の値を設定可能
  * Machine-to-Machine(M2M)認証では固定値でも問題なし
"""

import logging
import os
from typing import Any, Dict

import requests
from bedrock_agentcore.identity.auth import requires_access_token
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from boto3.session import Session
from mcp.client.streamable_http import streamablehttp_client
from strands import Agent
from strands.tools import tool
from strands.tools.mcp import MCPClient

# ============================================================================
# ロギング設定
# ============================================================================
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# AWS Region取得
boto_session = Session()
region = boto_session.region_name


# ============================================================================
# FDC APIツール定義(栄養素分析用)
# ============================================================================


@tool
def search_fdc(query: str) -> dict:
    """
    FoodData Central APIで食品データを検索する

    Args:
        query: 検索したい食品名(英語推奨、日本語も可)

    Returns:
        dict: 検索結果(食品リストとFDC ID)

    Example:
        >>> search_fdc("chicken breast")
        {
            "foods": [
                {"fdcId": 171477, "description": "Chicken, broilers or fryers, breast, meat only, raw"},
                ...
            ]
        }
    """
    fdc_api_key = os.environ.get("FDC_API_KEY")
    if not fdc_api_key:
        return {"error": "FDC_API_KEY environment variable is not set"}

    url = f"https://api.nal.usda.gov/fdc/v1/foods/search?api_key={fdc_api_key}"
    payload = {
        "query": query,
        "pageSize": 3,  # 検索結果の上位3件を取得
        "requireAllWords": False,
        "sortBy": "dataType.keyword",
        "sortOrder": "asc",
    }
    headers = {"Content-Type": "application/json"}

    try:
        response = requests.post(url, json=payload, headers=headers, timeout=10)
        response.raise_for_status()
        return response.json()
    except requests.RequestException as e:
        return {"error": f"FDC API request failed: {str(e)}"}


@tool
def get_food_details(fdc_id: int) -> dict:
    """
    食品IDから詳細な栄養素情報を取得する

    Args:
        fdc_id: FDC(FoodData Central)の食品ID

    Returns:
        dict: 食品の詳細な栄養素データ

    Example:
        >>> get_food_details(171477)
        {
            "description": "Chicken, broilers or fryers, breast, meat only, raw",
            "foodNutrients": [
                {"nutrient": {"name": "Energy"}, "amount": 165},
                {"nutrient": {"name": "Protein"}, "amount": 31.0},
                ...
            ]
        }
    """
    fdc_api_key = os.environ.get("FDC_API_KEY")
    if not fdc_api_key:
        return {"error": "FDC_API_KEY environment variable is not set"}

    url = f"https://api.nal.usda.gov/fdc/v1/food/{fdc_id}?api_key={fdc_api_key}"

    try:
        response = requests.get(url, timeout=10)
        response.raise_for_status()
        return response.json()
    except requests.RequestException as e:
        return {"error": f"FDC API request failed: {str(e)}"}


@tool
def analyze_nutrition(fdc_id: int) -> str:
    """
    食品IDから詳細な栄養分析レポートを生成する

    このツールは以下を計算します:
    - カロリー
    - PFCバランス(たんぱく質・脂質・炭水化物の割合)
    - 各種栄養素(食物繊維、ナトリウム、カルシウム、鉄)

    Args:
        fdc_id: FDC(FoodData Central)の食品ID

    Returns:
        str: 詳細な栄養分析レポート(日本語)

    Note:
        - PFCバランスはカロリーベースで計算
        - たんぱく質・炭水化物: 4kcal/g
        - 脂質: 9kcal/g
    """
    # 食品の詳細データを取得
    food_data = get_food_details(fdc_id)

    if "error" in food_data:
        return f"エラー: {food_data['error']}"

    # 栄養素データを辞書に変換
    nutrients = {
        n["nutrient"]["name"]: n["amount"] for n in food_data.get("foodNutrients", [])
    }

    # 主要栄養素の取得
    calories = nutrients.get("Energy", 0)
    protein = nutrients.get("Protein", 0)
    fat = nutrients.get("Total lipid (fat)", 0)
    carbs = nutrients.get("Carbohydrate, by difference", 0)
    fiber = nutrients.get("Fiber, total dietary", 0)
    sodium = nutrients.get("Sodium, Na", 0)
    calcium = nutrients.get("Calcium, Ca", 0)
    iron = nutrients.get("Iron, Fe", 0)

    # PFCバランスを計算
    # たんぱく質・炭水化物: 4kcal/g、脂質: 9kcal/g
    total_macro_cal = (protein + carbs) * 4 + fat * 9
    if total_macro_cal > 0:
        p_pct = (protein * 4) / total_macro_cal * 100
        f_pct = (fat * 9) / total_macro_cal * 100
        c_pct = (carbs * 4) / total_macro_cal * 100
    else:
        p_pct = f_pct = c_pct = 0

    # 栄養分析レポートを生成
    return f"""📊 {food_data.get("description", "食品")}の栄養情報

カロリー: {calories:.0f} kcal
PFCバランス: P{p_pct:.0f}% F{f_pct:.0f}% C{c_pct:.0f}%
たんぱく質: {protein:.1f}g
脂質: {fat:.1f}g
炭水化物: {carbs:.1f}g
食物繊維: {fiber:.1f}g
ナトリウム: {sodium:.0f}mg
カルシウム: {calcium:.0f}mg
鉄: {iron:.1f}mg

※ 100gあたりの値です。
FDC ID: {fdc_id}"""


# ============================================================================
# AgentCore Identity統合クラス
# ============================================================================


class AgentWithIdentity:
    """
    AgentCore Identityを使用してOAuth2認証を行うエージェント

    以下を実行する
    1. AgentCore Identity経由でのOAuth2トークン取得
    2. Slack Gateway(MCP)経由でのSlackツール取得
    3. FDC APIツールとSlackツールの統合
    4. Strandsエージェントによるストリーミング実行
    """

    def __init__(self):
        """
        環境変数から設定を読み込み、初期化する

        環境変数の説明:
        - GATEWAY_URL/MCP_URL: Slack Gatewayのエンドポイント(必須)
        - COGNITO_SCOPE: OAuth2認証のスコープ(必須)
        - WORKLOAD_NAME: Workload Identity名(デフォルト: nutrition_agent)
        - USER_ID: ユーザー識別子(デフォルト: test-user)
          * Machine-to-Machine認証では通常は固定値でOK
        """
        # 環境変数の取得
        self.gateway_url = os.environ.get("GATEWAY_URL") or os.environ.get("MCP_URL")
        self.cognito_scope = os.environ.get("COGNITO_SCOPE")
        self.workload_name = os.environ.get("WORKLOAD_NAME", "nutrition_agent")
        self.user_id = os.environ.get("USER_ID", "test-user")
        self.region = region

        # 必須環境変数の検証
        if not self.gateway_url:
            raise ValueError("GATEWAY_URL or MCP_URL environment variable is required")
        if not self.cognito_scope:
            raise ValueError("COGNITO_SCOPE environment variable is required")

    async def get_access_token(self) -> str:
        """
        AgentCore Identity経由でOAuth2アクセストークンを取得

        @requires_access_tokenデコレータを使用して、以下の処理を内部で行う
        - GetWorkloadAccessToken APIでWorkloadトークンを取得
        - GetResourceOauth2Token APIでOAuth2トークンを取得
        - Cognito Token Endpointからアクセストークンを取得

        OAuth2の認証フロー(Client Credentials Grant)
        1. エージェントがAgentCore Identityにトークンを要求
        2. IdentityがClient IDとClient Secretを使ってCognitoに認証
        3. Cognitoがアクセストークン(JWT形式)を発行
        4. トークンを使ってAgentCore Gatewayにアクセス


        Returns:
            str: OAuth2アクセストークン(JWT形式)

        Raises:
            Exception: トークン取得に失敗した場合の例外エラー
        """

        @requires_access_token(
            provider_name="resource-provider-oauth-client-79wpb",
            scopes=[self.cognito_scope],
            auth_flow="M2M",  # Machine-to-Machine認証
            force_authentication=False,
        )
        async def _get_token(*, access_token: str) -> str:
            """
            デコレータによってアクセストークンが注入される内部関数

            Args:
                access_token: OAuth2アクセストークン(デコレータが自動注入)

            Returns:
                str: OAuth2アクセストークン
            """
            logger.info("✅ OAuth2 access token acquired successfully")
            return access_token

        try:
            return await _get_token()
        except Exception as e:
            logger.error(f"❌ Failed to get OAuth2 token: {type(e).__name__}: {str(e)}")
            raise

    async def access_to_slack(self, payload: Dict[str, Any]):
        """
        統合エージェントを実行してSlack操作と栄養分析を行う

        処理フロー:
        - OAuth2トークンを取得
        - MCPクライアントを作成してSlackツールを取得
        - FDC APIツール(自作ツール)を追加
        - toolを持たせたエージェントを作成して実行

        Args:
            payload: ユーザープロンプトを含むペイロード
                - prompt: ユーザーからの入力メッセージ

        Yields:
            dict: エージェントからのストリーミングレスポンス
        """

        # OAuth2トークン取得
        logger.info("Acquiring OAuth2 access token...")
        access_token = await self.get_access_token()

        # MCPトランスポート作成
        # ここでは、HTTP通信でOAuth2トークンを使ってGatewayに接続
        def create_streamable_http_transport():
            """
            OAuth2認証ヘッダー付きMCPトランスポートを作成

            Returns:
                StreamableHttpTransport: 認証済みトランスポート
            """
            return streamablehttp_client(
                self.gateway_url, headers={"Authorization": f"Bearer {access_token}"}
            )

        # Slackツール取得(ページネーション対応)
        def get_full_tools_list(client):
            """
            MCPクライアントから全ツールをページネーション付きで取得する
            処理の流れは以下
            1. 最初のページを取得(pagination_token=None)
            2. 次のページがあれば、pagination_tokenを使って続きを取得
            3. pagination_tokenがNullになるまで繰り返す

            Args:
                client: MCPクライアント

            Returns:
                list: 利用可能なツールのリスト
            """
            tools = []
            pagination_token = None

            while True:
                result = client.list_tools_sync(pagination_token=pagination_token)
                tools.extend(result)

                if result.pagination_token is None:
                    break
                pagination_token = result.pagination_token

            return tools

        # MCPクライアント作成
        mcp_client = MCPClient(create_streamable_http_transport)

        try:
            with mcp_client:
                # Slackツールを取得
                logger.info("Fetching Slack tools from MCP...")
                slack_tools = get_full_tools_list(mcp_client)
                logger.info(f"Found {len(slack_tools)} Slack tools")

                # 既存のツールに、栄養素分析ツール(自作ツール)を追加
                logger.info("Adding nutrition analysis tools...")
                nutrition_tools = [search_fdc, get_food_details, analyze_nutrition]
                all_tools = slack_tools + nutrition_tools
                logger.info(f"Total {len(all_tools)} tools available")

                # エージェント作成
                logger.info("Creating Strands agent...")
                agent = Agent(
                    tools=all_tools,
                    model="us.anthropic.claude-sonnet-4-20250514-v1:0",
                    system_prompt="""あなたはSlack統合と栄養分析が可能なアシスタントです。

利用可能な機能:
1. Slack操作: チャンネル管理、メッセージ送信など
2. 栄養素分析: FoodData Central APIを使用した食品検索と栄養分析

指示:
- 食事や栄養についての質問には、search_fdcで食品を検索し、analyze_nutritionで栄養分析を実行してください
- 分析結果は`#agentcore-slack-integrate-test`チャンネルに投稿してください
- ツールを使用した場合は、使用したツール名とFDC IDを回答に含めてください
- ユーザーのリクエストを正確に理解し、適切な操作を実行してください
""",
                )

                # エージェント実行
                logger.info("Running agent...")
                user_message = payload.get("prompt", "")

                async for event in agent.stream_async(user_message):
                    yield event

                logger.info("✅ Agent execution completed")

        except Exception as e:
            logger.error(f"❌ Agent execution error: {e}")

            # エラーの種類に応じた適切なメッセージを返す
            error_msg = str(e).lower()
            if "timeout" in error_msg:
                yield {"error": f"Gateway timeout: {str(e)}"}
            else:
                yield {"error": f"Agent execution failed: {str(e)}"}


# ============================================================================
# AgentCoreアプリケーション
# ============================================================================

# BedrockAgentCoreアプリケーションの初期化
app = BedrockAgentCoreApp()


@app.entrypoint
async def slack_agent(payload: Dict[str, Any]):
    """
    統合栄養管理エージェントのエントリーポイント

    このエージェントは、Slack連携と栄養素分析の両方の機能を提供します。
    AgentCore Runtimeから呼び出され、ストリーミングレスポンスを返します。

    Args:
        payload: AgentCore Runtimeからのペイロード
            - prompt: ユーザーからの入力メッセージ(必須)

    Yields:
        dict: ストリーミングレスポンス
            - 正常時: エージェントからのイベント
            - エラー時: {"error": "エラーメッセージ"}

    Example:
        >>> payload = {"prompt": "鶏胸肉の栄養素を教えて"}
        >>> async for event in slack_agent(payload):
        ...     print(event)
    """

    # エージェント初期化
    try:
        agent_with_identity = AgentWithIdentity()
    except ValueError as e:
        logger.error(f"Configuration error: {e}")
        yield {
            "error": f"Configuration error: {str(e)}. "
            "Please check GATEWAY_URL and COGNITO_SCOPE environment variables."
        }
        return
    except Exception as e:
        logger.error(f"Initialization error: {e}")
        yield {"error": f"Agent initialization failed: {str(e)}"}
        return

    # ペイロード検証
    if not payload or "prompt" not in payload:
        yield {"error": "Invalid payload: 'prompt' field is required"}
        return

    # エージェント実行
    try:
        async for event in agent_with_identity.access_to_slack(payload):
            yield event
    except Exception as e:
        logger.error(f"Error in slack_agent: {e}", exc_info=True)
        yield {"error": f"Request processing failed: {str(e)}"}


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

最終的に出来上がったもの

鶏胸肉と、鶏もも肉の栄養素を比較してほしいと呼び出してみると、以下のようにSlackに回答が投稿されました。
正確な値かは若干怪しいですが、回答はしてくれているようです。

$ agentcore invoke '{"prompt": 鶏胸肉と、鶏ももの栄養素を比較してほしい"}' \
  --agent nutrition_agent \
  --user-id "test-user"

処理の一部を抜粋していますが、以下のようにsearch_fdcツールが使われていることが分かります。

{"event": {"messageStop": {"stopReason": "tool_use"}}}

{"event": {"metadata": {"usage": {"inputTokens": 23120, "outputTokens": 104, "totalTokens": 23224}, "metrics": {"latencyMs": 3458}}}}

{"message": {"role": "assistant", "content": [{"text": "より一般的で比較しやすい食品データを取得するため、生の鶏胸肉と鶏もも肉のFDC IDを探します。"}, {"toolUse": {"toolUseId": 
"tooluse_S2q7D_16Q8a2gto9G6uYAQ", "name": "search_fdc", "input": {"query": "chicken breast meat raw"}}}]}}

{"message": {"role": "user", "content": [{"toolResult": {"toolUseId": "tooluse_S2q7D_16Q8a2gto9G6uYAQ", "status": "success", "content": [{"text": "{'totalHits': 41005, 'currentPage': 1, 
'totalPages': 13669, 'pageList': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 'foodSearchCriteria': {'query': 'chicken breast meat raw', 'generalSearchInput': 'chicken breast meat raw', 'pageNumber': 1, 
'sortBy': 'dataType.keyword', 'sortOrder': 'asc', 'numberOfResultsPerPage': 50, 'pageSize': 3, 'requireAllWords': False .....(tool使用結果が続く)

さらに、Slackへの回答を行われていることも確認できました。
スクリーンショット 2025-10-26 11.59.04.png

※普通にChatGPTなどAIに聞けば回答は得られるのではないか、という議論はさておき、ツールを使って外部のAPIを呼び出し、詳細な栄養素の情報を取得し、その結果から回答を行ってくれていることが確認できた、というのがポイントです。

おまけ

agentcore invoke '{"prompt": 鶏胸肉と、鶏ももの栄養素を比較して、トレーニー向けにアドバイスをください"}' \
  --agent nutrition_agent \
  --user-id "test-user"

減量期は鶏胸肉がおすすめです。(笑)
スクリーンショット 2025-10-26 12.20.32.png

詰まった箇所

An error occurred (ValidationException) when calling the GetResourceOauth2Token operation: Error parsing ClientCredentials response"
  • カスタムスコープ
    <gateway-name>/genesis-gateway:invoke
    とすべきところをgenesis-gateway:invokeとしていたためでした。
    上記エラーは、何かしらの設定ミス(コピペミスなど)に起因する可能性が高いので、疑ってみるのが良いかもしれません。
Workload access token has not been set. If invoking agent runtime via SIGV4 inbound auth, please specify the 
X-Amzn-Bedrock-AgentCore-Runtime-User-Id header and retry. For details, see - https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-oauth.html
  • agentcore invoke時に、user-idを指定しないと、以下のようなエラーが出るようでした。user-idはどんな値でも良いので、何かしらの値を指定する必要がありそうです

まとめ

本記事では、Amazon Bedrock AgentCoreで簡単なエージェントを作成し、アプリケーションを作成してみました。
コマンドを数回入力するだけで簡単にAgentをデプロイすることができる利便性を実感しつつ、また
各機能が抽象化されている分内部の仕組みをしっかり理解していく必要性を実感しました。内部的な認証/認可の仕組みをしっかりと理解するのが難しかった印象です。ただ、安全にエージェント開発を行うためには重要な仕組みであるなと感じましたので、深く理解していきたいなと思いました。

参考文献

4
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
4
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?