Amazon Bedrock AgentCore を一から触ってみる

記事タイトルとURLをコピーする

はじめに

こんにちは、アプリケーションサービス本部ディベロップメントサービス3課の北出です。

前回の記事では Amazon Bedrock Agents を使って天気エージェントを作りました。今回は同じ天気エージェントを Amazon Bedrock AgentCore(以下 AgentCore)で作り直してみます。

AgentCoreは「自分で書いたコードを本番で動かすための基盤」で、Bedrock Agentsとは設計思想が異なります。実際に触ってみて、何が違うのか、どういう場面で使うべきかを体感したかったのが動機です。

今回体験するAgentCore特有の機能は以下の通りです。

  • Runtime: サーバーレスなエージェント実行環境

  • Memory: 会話の文脈保持

  • Gateway: 外部APIをMCPツールとして接続

  • Observability: OpenTelemetry互換のトレース

今回作るもの

前回と同じ「天気を教えてくれるエージェント」ですが、AgentCoreの機能を活用します。

ユーザー
「ニセコの天気教えて」
    │
    ▼
AgentCore Runtime(Strands Agents + Claude Sonnet 4)
    │
    ├── Gateway経由: geocode_location(地名→緯度経度)
    │     └── Lambda → Open-Meteo Geocoding API
    │
    └── @tool: get_weather(緯度経度→天気)
          └── Open-Meteo Weather API
    │
    ▼
Memory に会話を保存
    │
    ▼
ユーザーに回答

前回のBedrock Agentsとの大きな違い:

  • コードベースで開発(@tool デコレータでツール定義)

  • ローカルで動作確認してからデプロイ

  • Gatewayで外部APIをMCPツール化

  • トレースで推論の各ステップを可視化

検証環境

  • Node.js 24

  • Python 3.14

  • Docker

  • AWS CLI

  • AWS CDK(npm install -g aws-cdk

  • Bedrock モデルアクセス(Claude Sonnet 4)

手順

まずは Gateway 経由の「地名→緯度経度」の機能はなく、前回作った Bedrock Agents と同等のエージェントを AgentCore で作ります。

1. AgentCore CLIのインストール

npm install -g @aws/agentcore

AgentCore CLI はプロジェクトの作成からデプロイ、呼び出しまでを一貫して行えるツールです。

2. プロジェクト作成

agentcore create \
  --name WeatherAgent \
  --framework Strands \
  --protocol HTTP \
  --model-provider Bedrock \
  --memory shortTerm \
  --build Container

--memory shortTerm で会話の文脈を保持するMemoryが自動設定されます。--build Container でDockerコンテナベースのデプロイになります。

CodeZipデプロイ(デフォルト)も選べますが、OpenTelemetry等の重い依存を含む場合はContainerの方が初期化タイムアウトを回避できて安定します。

生成されるプロジェクト構造:

WeatherAgent/
├── agentcore/                # AgentCoreの設定・インフラ定義
│   ├── agentcore.json        # プロジェクト設定(Memory設定含む)
│   ├── aws-targets.json      # デプロイ先リージョン
│   └── .env.local            # ローカル環境変数
├── app/
│   └── WeatherAgent/         # エージェントのアプリケーション
│       ├── main.py           # エージェントのエントリポイント
│       ├── Dockerfile        # コンテナビルド定義
│       └── pyproject.toml    # Python依存関係
└── README.md

3. エージェントコードの作成

app/WeatherAgent/main.py にエージェントのロジックを書きます。

import json
import urllib.request
from strands import Agent, tool
from bedrock_agentcore.runtime import BedrockAgentCoreApp

app = BedrockAgentCoreApp()


WEATHER_CODES = {
    0: "快晴", 1: "おおむね晴れ", 2: "一部曇り", 3: "曇り",
    45: "霧", 48: "着氷性の霧",
    51: "弱い霧雨", 53: "霧雨", 55: "強い霧雨",
    61: "弱い雨", 63: "雨", 65: "強い雨",
    71: "弱い雪", 73: "雪", 75: "強い雪",
    80: "弱いにわか雨", 81: "にわか雨", 82: "激しいにわか雨",
    95: "雷雨", 96: "雹を伴う雷雨", 99: "激しい雹を伴う雷雨",
}


@tool
def get_weather(latitude: str, longitude: str) -> str:
    """指定された緯度と経度の現在の天気情報を取得します。

    Args:
        latitude: 緯度(例: 35.6762)
        longitude: 経度(例: 139.6503)
    """
    url = (
        f"https://api.open-meteo.com/v1/forecast?"
        f"latitude={latitude}&longitude={longitude}"
        f"&current=temperature_2m,weather_code,wind_speed_10m,relative_humidity_2m"
        f"&timezone=auto"
    )
    req = urllib.request.Request(url)
    with urllib.request.urlopen(req) as res:
        data = json.loads(res.read().decode())

    current = data.get("current", {})
    temperature = current.get("temperature_2m", "不明")
    weather_code = current.get("weather_code", 0)
    wind_speed = current.get("wind_speed_10m", "不明")
    humidity = current.get("relative_humidity_2m", "不明")
    weather_desc = WEATHER_CODES.get(weather_code, "不明")

    return (
        f"現在の天気: {weather_desc}\n"
        f"気温: {temperature}°C\n"
        f"湿度: {humidity}%\n"
        f"風速: {wind_speed} km/h"
    )


SYSTEM_PROMPT = """あなたは天気情報を提供するアシスタントです。

ユーザーから場所を聞かれたら、その場所の緯度・経度を推定してget_weatherツールで天気情報を取得してください。

主要都市の緯度・経度:
- 東京: 緯度 35.6762, 経度 139.6503
- 大阪: 緯度 34.6937, 経度 135.5023
- 名古屋: 緯度 35.1815, 経度 136.9066
- 札幌: 緯度 43.0618, 経度 141.3545
- 福岡: 緯度 33.5904, 経度 130.4017

回答のルール:
1. 必ず日本語で回答する
2. 天気情報を分かりやすくフォーマットして表示する
3. 場所が曖昧な場合はユーザーに確認する
4. 天気に基づいたアドバイス(傘が必要か等)も添える
5. 前の会話で聞いた場所を覚えておき、「さっきの場所は?」と聞かれたら答える
"""


_agents = {}


@app.entrypoint
def handle(payload: dict, session_id: str = None):
    prompt = payload.get("prompt", "")
    session_id = session_id or "default"

    if session_id not in _agents:
        from memory.session import get_memory_session_manager
        session_manager = get_memory_session_manager(session_id=session_id, actor_id="user")

        _agents[session_id] = Agent(
            model="apac.anthropic.claude-sonnet-4-20250514-v1:0",
            system_prompt=SYSTEM_PROMPT,
            tools=[get_weather],
            session_manager=session_manager,
        )

    agent = _agents[session_id]
    result = agent(prompt)
    return {"response": str(result)}


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

コードのポイント:

  • @tool デコレータでツールを定義。docstringがツールの説明文、型ヒントがパラメータ定義になる

  • _agents 辞書でセッションごとにAgentをキャッシュ。handle関数内で毎回新規作成すると前の会話を忘れてしまう

  • session_manager でAgentCore Memoryと連携し、同一セッション内の会話履歴を自動保持

  • if __name__ == "__main__": app.run() はContainerデプロイで必須(HTTPサーバーを起動する)

注意: この時点ではGateway未接続のため、プロンプトに主要都市の緯度経度をハードコードしています。手順9でGatewayを追加し、任意の地名に対応できるようにします。

4. Dockerfileの作成

Containerデプロイには Dockerfile が必要です。

FROM public.ecr.aws/docker/library/python:3.14-slim

WORKDIR /app

COPY . .
RUN pip install --no-cache-dir .

EXPOSE 8080

CMD ["opentelemetry-instrument", "python", "main.py"]

CMDopentelemetry-instrument を使うのがポイントです。これによりトレースが自動的にCloudWatchに送信されます(CodeZipデプロイでは自動設定されますが、Containerでは明示的に指定が必要)。

5. ローカルテスト

cd WeatherAgent
agentcore dev

ブラウザでAgent Inspectorが開き、ローカルでエージェントの動作確認ができます。Bedrock Agentsではコンソール上でしかテストできませんでしたが、AgentCoreではデプロイ前にローカルで試行錯誤できるのが大きな利点です。

会話を見ると、Memoryも機能していることがわかります。

6. デプロイ

agentcore deploy

AWS Cloud Development Kit(AWS CDK)ベースで AWS CloudFormation スタックが作成され、Runtime・Memory・Gatewayなどのリソースがプロビジョニングされます。

7. 呼び出しと動作確認

agentcore invoke "東京の天気を教えて"

マネジメントコンソールのサンドボックスで動作確認することもできます。

8. Observability(トレース確認)

CloudWatchの生成 AI オブザーバビリティダッシュボードでトレースを確認できます。

トレースを見るには、アカウントで1回だけ CloudWatch トランザクション検索 を有効化する必要があります。

  1. CloudWatch コンソール →「Application Signals (APM)」→「Transaction search」
  2. 「Enable Transaction Search」→ スパンの取り込みを有効化
  3. インデックスするトレースの割合を設定(テスト中は100%推奨)

有効化後、以下の手順でトレースを確認します。

  1. CloudWatch コンソール →「生成 AI オブザーバビリティ」→「Bedrock AgentCore」
  2. 「Traces」タブ → 対象のトレースをクリック

トレースを見ると、各処理でかかっている時間や、get_weather tool が実行されていることがわかります。 また、処理時間の約7割がLLMの推論に費やされており、ツール実行やその他の操作は軽量であることがわかります。

9. Gateway でジオコーディングAPIを接続

ここまでは大体前回の記事の Amazon Bedrock Agents でやったことと同じで、エージェントはプロンプトに主要都市の緯度経度をハードコードしていました。

次に、Gatewayを使って任意の地名に対応できるようにします。

ただし、無理に Gateway を使う必要はなく、@tool を追加することでも十分対応できますが、理解のために Gateway で実装します。

Gatewayは、既存のAPIやLambda関数をMCP互換のツールに変換し、エージェントから統一的に呼び出せるようにするサービスです。

まず、ジオコーディング用のLambda関数を作成します。

  • 関数名: geocode-function

  • ランタイム: Python 3.14

  • タイムアウト: 30秒

import json
import urllib.request
import urllib.parse


def lambda_handler(event, context):
    """地名から緯度経度を取得する(Open-Meteo Geocoding API)"""
    tool_use = event.get("toolUse", {})
    tool_input = tool_use.get("input", {})
    location_name = tool_input.get("location", "Tokyo")

    url = f"https://geocoding-api.open-meteo.com/v1/search?name={urllib.parse.quote(location_name)}&count=1&language=ja"
    req = urllib.request.Request(url)
    with urllib.request.urlopen(req) as res:
        data = json.loads(res.read().decode())

    results = data.get("results", [])
    if not results:
        return {
            "content": [{"text": f"「{location_name}」の位置情報が見つかりませんでした。"}]
        }

    place = results[0]
    return {
        "content": [
            {
                "text": json.dumps({
                    "name": place.get("name", location_name),
                    "latitude": place.get("latitude"),
                    "longitude": place.get("longitude"),
                    "country": place.get("country", ""),
                    "admin1": place.get("admin1", "")
                }, ensure_ascii=False)
            }
        ]
    }

次に、プロジェクトルートに tools.json(ツールスキーマ)を作成します。

[
  {
    "name": "geocode_location",
    "description": "地名や都市名から緯度と経度を取得します。天気を調べる前に、ユーザーが指定した場所の座標を取得するために使います。",
    "inputSchema": {
      "type": "object",
      "properties": {
        "location": {
          "type": "string",
          "description": "検索する地名(例: 渋谷、横浜、Paris)"
        }
      },
      "required": ["location"]
    }
  }
]

Gatewayを作成し、Lambda関数をターゲットとして登録します。

agentcore add gateway --name WeatherGateway --authorizer-type NONE --runtimes WeatherAgent

agentcore add gateway-target \
  --name GeocodingTarget \
  --type lambda-function-arn \
  --lambda-arn <Lambda ARN> \
  --tool-schema-file tools.json \
  --gateway WeatherGateway

次に、エージェントのコードを更新してからデプロイします。

app/WeatherAgent/main.py を以下のように更新します。変更点は3つです。

変更1: import追加とGATEWAY_URL定義(ファイル先頭)

import json
import os
import urllib.request
from strands import Agent, tool
from strands.tools.mcp.mcp_client import MCPClient
from mcp.client.streamable_http import streamablehttp_client
from bedrock_agentcore.runtime import BedrockAgentCoreApp

app = BedrockAgentCoreApp()

GATEWAY_URL = os.getenv("GATEWAY_WEATHERGATEWAY_URL", "<デプロイ後に確認したURL>")

変更2: SYSTEM_PROMPTの更新(ハードコード緯度経度を削除し、geocode_locationの使用を指示)

SYSTEM_PROMPT = """あなたは天気情報を提供するアシスタントです。

ユーザーから場所を聞かれたら:
1. 必ず最初に geocode_location ツールで地名から緯度・経度を取得すること(自分の知識で緯度経度を推定してはいけない)
2. 次に get_weather ツールで天気情報を取得する

回答のルール:
1. 必ず日本語で回答する
2. 天気情報を分かりやすくフォーマットして表示する
3. 場所が曖昧な場合はユーザーに確認する
4. 天気に基づいたアドバイス(傘が必要か等)も添える
5. 前の会話で聞いた場所を覚えておき、「さっきの場所は?」と聞かれたら答える
"""

変更3: get_gateway_tools()関数の追加と、handle()内でGatewayツールを渡す

def get_gateway_tools():
    """Gateway経由のツールを取得する"""
    if not GATEWAY_URL:
        return []
    mcp_client = MCPClient(lambda: streamablehttp_client(GATEWAY_URL))
    mcp_client.__enter__()
    return mcp_client.list_tools_sync()


_agents = {}


@app.entrypoint
def handle(payload: dict, session_id: str = None):
    prompt = payload.get("prompt", "")
    session_id = session_id or "default"

    if session_id not in _agents:
        from memory.session import get_memory_session_manager
        session_manager = get_memory_session_manager(session_id=session_id, actor_id="user")

        gateway_tools = get_gateway_tools()

        _agents[session_id] = Agent(
            model="apac.anthropic.claude-sonnet-4-20250514-v1:0",
            system_prompt=SYSTEM_PROMPT,
            tools=[get_weather] + gateway_tools,  # ← Gatewayツールを追加
            session_manager=session_manager,
        )

    agent = _agents[session_id]
    result = agent(prompt)
    return {"response": str(result)}

注意: Gateway URLはデプロイ後にしか確定しないため、2回デプロイが必要です。1回目でGatewayを作成し、agentcore status でURLを確認してコードに設定し、2回目で反映します。ドキュメントを見てもGatewayのデプロイとGateway URLの受け渡し方法のベストプラクティスはわかりませんでした。Gatewayとエージェントは別チームが管理する想定で、同時にデプロイする設計思想ではないかもしれません。

コードを更新したらデプロイします。

# 1回目: Gateway作成 + コード反映
agentcore deploy

# Gateway URLを確認
aws cloudformation describe-stacks \
  --stack-name AgentCore-WeatherAgent-default \
  --region ap-northeast-1 \
  --query "Stacks[0].Outputs[?contains(OutputKey,'GatewayUrl')].OutputValue" \
  --output text

# 確認したURLを main.py の GATEWAY_URL に設定してから2回目のデプロイ
agentcore deploy

これで「ニセコの天気を教えて」のような任意の地名にも対応できるようになりました。

質問後にトレースを見ると、API に対する POST リクエストがあり、 Gateway が実行されていることがわかります。

@tool と Gateway の使い分け

今回のエージェントでは2種類のツール定義を使いました。

@tool(コード内定義) Gateway経由
今回の例 get_weather geocode_location
向いているケース そのエージェント専用のロジック 複数エージェントで共有したいツール
変更時 コード修正 + 再デプロイ Gateway設定変更のみ
管理者 エージェント開発者 プラットフォームチーム

Bedrock Agents との比較

同じ天気エージェントを両方で作ってみた結果の比較です。

観点 Bedrock Agents AgentCore
開発方法 コンソールGUI コード + CLI
ツール定義 アクショングループ(GUI設定 + Lambda) @tool デコレータ or Gateway(MCP)
ローカルテスト 不可 agentcore dev で可能
トレース コンソールで確認 OpenTelemetry互換(CloudWatch GenAI Observability)
構築難易度 低い 高い.

ハマったポイント

実際に触ってみていくつかハマった点があったので共有します。

CodeZipデプロイの初期化タイムアウト

デフォルトのCodeZipデプロイでは、OpenTelemetryの初期化が重く30秒の制限を超えてしまいました。Containerデプロイに切り替えることで解決しました。

AgentCoreを使うときは基本的にContainerデプロイが良いと思います。

Containerデプロイでのトレース設定

Containerデプロイの場合、Dockerfileの CMDopentelemetry-instrument を明示的に指定する必要があります。

# NG: トレースが送信されない
CMD ["python", "main.py"]

# OK: トレースが送信される
CMD ["opentelemetry-instrument", "python", "main.py"]

opentelemetry-instrumentaws-opentelemetry-distro パッケージに含まれるコマンドで、pyproject.toml の依存関係に含まれていれば pip install 時に自動でインストールされます。CodeZipデプロイではCLIが自動設定してくれるため、ドキュメントを読み飛ばすとハマります。

参考: Enabling observability in agent code for AgentCore-hosted agents

まとめ

Amazon Bedrock AgentCore で天気エージェントを作成し、Runtime・Memory・Gateway・Observabilityの4つの機能を体験しました。

  • Runtime: Dockerコンテナをサーバーレスでデプロイ。セッション分離されたmicroVMで実行

  • Memory: 会話の文脈を自動保持。コード数行で設定可能

  • Gateway: 外部APIをMCPツールとして接続。エージェントのコード変更なしでツール追加可能

  • Observability: 推論の各ステップをトレースで可視化。ボトルネック分析に有用

Bedrock Agentsが「設定だけで素早くエージェントを作る」サービスなのに対し、AgentCoreは「自分のコードを本番品質で運用する」ためのプラットフォームです。軽く試すならBedrock Agents、本番運用はAgentCoreという使い分けが良さそうです。

北出 宏紀(執筆記事の一覧)

アプリケーションサービス本部ディベロップメントサービス3課

2024年9月中途入社です。