はじめに
Amazon Bedrock AgentCore を使って、「コストを調べて」と話しかけるだけで Cost Explorer を自律的に掘り下げてくれるエージェントを作りました。
これまで AI エージェントをクラウドで動かそうとすると、Lambda でエージェントループを自前実装するか、Bedrock Agents のマネージドフローに乗せるかの二択でした。
AgentCore は第三の選択肢で、好きなフレームワーク(LangGraph / Strands / OpenAI 等)をそのままクラウドにデプロイできます。
この記事では AgentCore の概要から実装・デプロイ・動作確認までを一気通貫で解説します。
AgentCore は 2025年7月にプレビュー公開、2025年10月13日に GA しました。
本記事の検証は 2026年5月初旬(CLI v0.13.0)時点のものです。
参考: Amazon Bedrock AgentCore is now generally available
AgentCore とは
AgentCore は「AI エージェントをホスティングするためのマネージドプラットフォーム」です。
エージェントループ(認識 → 推論 → 行動 → 観察を繰り返すサイクル)を自前で実装する場合、Lambda でループ制御を書く必要がありました。AgentCore は Runtime がホスティングされているため、フレームワークをそのまま動かせます。
今回作るもの
「コストを調べて」と話しかけると、Cost Explorer を自律的に掘り下げて報告してくれる
Python エージェントを AgentCore CLI でクラウドにデプロイする
AgentCore が便利なユースケース
「複数のサービスをまたいで、長時間・自律的に動く必要があるもの」が向いています。
| 向いている | 向いていない |
|---|---|
| 複数サービスをまたぐ | 単一 API を呼ぶだけ |
| 15 分以上かかる可能性がある | 数秒で完結する |
| 「調査 → 判断 → 次のアクション」を繰り返す | 決まった手順を実行するだけ |
| 操作ログの監査が必要 | 監査不要な社内ツール |
単一 API を呼ぶだけなら Lambda + Bedrock Converse API で十分です。AgentCore は「自律的にループを回す」ユースケースで真価を発揮します。
構成要素(3 つ)
AgentCore は以下の 3 つのコンポーネントで構成されています。
| コンポーネント | 役割 |
|---|---|
| AgentCore Runtime | エージェント本体のホスティング環境。microVM でセッション分離。フレームワーク非依存 |
| AgentCore Gateway | 既存の API / Lambda / MCP サーバーを MCP 互換ツールに変換し、エージェントから統一的に呼べるようにする |
AgentCore CLI (@aws/agentcore) |
CDK ベースのデプロイツール。agentcore create → agentcore dev → agentcore deploy の流れで完結 |
Strands Agents とは
AWS が 2025 年 5 月にオープンソースで公開した Python フレームワークです。「LLM にツール一覧を渡して、LLM が必要なツールを選んで呼び出す」ループを書きやすくしたものです。
from strands import Agent, tool
@tool
def get_cost_by_service(start_date: str, end_date: str) -> str:
"""指定期間のサービス別コストを取得する。コスト増加の原因調査の最初のステップで使う。
Args:
start_date: 開始日(YYYY-MM-DD 形式)
end_date: 終了日(YYYY-MM-DD 形式)
"""
# boto3 で Cost Explorer を呼ぶ
...
@tool デコレータを付けるだけでツール登録完了です。あとは LLM が「このツールを呼ぼう」と判断したら自動で実行されます。
なぜ Strands を選んだか
| フレームワーク | 判定 | 理由 |
|---|---|---|
| Strands Agents | ◎ 採用 | AgentCore CLI のデフォルトテンプレートが Strands。AWS ネイティブ統合が強い。@tool デコレータだけでツール定義が完結する |
| LangGraph | × | グラフベースのワークフロー定義が必要。今回のような単純なツール呼び出しエージェントには過剰 |
| CrewAI | × | マルチエージェント協調が主眼。単一エージェントのユースケースには不向き。CLI の正式サポート対象(Strands / LangGraph / Google ADK / OpenAI Agents SDK)から外れつつある |
AgentCore Runtime とは
Strands で書いたエージェントコードを「クラウドで常時動かせる環境」に乗せるものです。Lambda との主な違いは以下の通りです。
| 観点 | AgentCore Runtime | Lambda |
|---|---|---|
| 最大実行時間 | 8 時間(maxLifetime のデフォルト値かつ上限) |
15 分 |
| セッション状態 | microVM 内で保持。セッションはインスタンスを超えて永続可能 | 呼び出しのたびにリセット |
| 課金モデル | 消費ベース(LLM レスポンス待ち中は CPU 課金なし) | リクエスト数 + 実行時間 |
| フレームワーク | 任意(Strands / LangGraph / CrewAI 等) | 自前実装 |
コスト調査エージェントの場合、「調査 → 仮説 → 深掘り」と何度もツールを呼び続けるので、15 分制限の Lambda では詰まります。
AgentCore Runtime の課金は消費ベースです。エージェントが LLM のレスポンスを待っている間(I/O wait)は CPU 課金が発生しないため、長時間セッションでもコストが抑えられます。
参考: Host agent or tools with Amazon Bedrock AgentCore Runtime
AgentCore Gateway とは
既存の API や Lambda 関数を MCP 互換ツールに変換し、エージェントから統一的に呼び出せるようにするサービスです。
エージェント
↓ MCP プロトコルで統一
AgentCore Gateway
├── Lambda ツール
├── MCP サーバー
├── OpenAPI の REST API
└── Smithy モデル
Gateway が必要になるのは「外部の MCP サーバーや REST API をツールとして使いたい」場合です。今回のコスト調査エージェントはツールが全部 boto3 の呼び出しで完結するため、Gateway は不要です。
設計ポイント
- ツールの粒度 — 1 ツール 1 API 呼び出しにします。LLM が組み合わせを判断するので、ツールは小さく保つのが原則です
-
ツールの説明文が設計の核心 —
@toolの docstring が LLM への指示になります。「いつ使うか」「何を返すか」を明確に書かないと LLM が誤った判断をします - IAM 設計は従来通り — エージェントに渡す IAM ロールは最小権限です。LLM が予期しないツール呼び出しをする可能性があるため、むしろ従来より厳密に絞る必要があります
前提条件(ローカル環境)
- Node.js 20 以上(CLI が npm パッケージのため)
- Python 3.10 以上(エージェントコードが Python)
- AWS CLI 設定済み
- IAM 権限(AgentCore API 呼び出し + CDK bootstrap ロールの assume)
参考: Get started with Amazon Bedrock AgentCore
手順
1. CLI インストール
npm install -g @aws/agentcore
agentcore --version # 0.13.0
2. プロジェクト作成(対話式ウィザード)
agentcore create
対話式ウィザードでプロジェクト名・フレームワーク・モデルなどを選択していきます。今回の選択は以下の通りです。
| 項目 | 選択 | 理由 |
|---|---|---|
| エージェントタイプ | Agent | ツール呼び出しを行うため |
| デプロイ先 | Runtime | クラウドで常時稼働させたいため |
| プロトコル | HTTP | 単純なリクエスト/レスポンスで十分なため |
| フレームワーク | Strands | AWS ネイティブ統合が強く、CLI デフォルトテンプレートのため |
| モデル | Claude Sonnet(後で Nova Lite に変更) | デフォルト選択。コスト削減のため後から変更 |
| メモリ | なし | セッション間で状態を引き継ぐ必要がないため |
生成されるディレクトリ構成:
CostInvestigator/
├── agentcore/
│ ├── agentcore.json # CLI が管理するリソース定義
│ ├── aws-targets.json # デプロイ先アカウント・リージョン
│ └── cdk/ # CLI が自動生成・管理(手動編集不要)
└── app/
└── MyAgent/
├── main.py # エージェントロジック(Strands)
├── model/load.py # モデル定義
└── pyproject.toml # Python 依存関係
3. エージェントコードの実装
生成直後の main.py はサンプルツール(add_numbers)のみです。以下の変更を加えます。
model/load.py — モデルを Nova Lite に変更
- return BedrockModel(model_id="global.anthropic.claude-sonnet-4-5-20250929-v1:0")
+ return BedrockModel(model_id="amazon.nova-lite-v1:0")
main.py — ツールを実装
実装するツールは 2 本です。
| ツール | 用途 |
|---|---|
get_cost_by_service |
サービス別コスト取得(調査の起点) |
get_cost_by_day |
特定サービスの日別コスト推移(スパイク日の特定) |
import json
from datetime import datetime, timezone
import boto3
from strands import Agent, tool
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from model.load import load_model
app = BedrockAgentCoreApp()
log = app.logger
def build_system_prompt() -> str:
today = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d")
return f"""
You are an AWS cost investigation agent. Today's date is {today}.
When asked about costs, follow this process:
1. Start by getting cost breakdown by service for the relevant period
2. Identify the top services by cost
3. Drill down into suspicious services using available tools
4. Provide a clear summary of findings and likely causes
Always respond in Japanese regardless of the input language.
"""
@tool
def get_cost_by_service(start_date: str, end_date: str) -> str:
"""Get AWS cost breakdown by service for a given period. Use this as the first step in any cost investigation.
Args:
start_date: Start date in YYYY-MM-DD format
end_date: End date in YYYY-MM-DD format (exclusive)
"""
client = boto3.client("ce", region_name="us-east-1")
response = client.get_cost_and_usage(
TimePeriod={"Start": start_date, "End": end_date},
Granularity="MONTHLY",
Metrics=["UnblendedCost"],
GroupBy=[{"Type": "DIMENSION", "Key": "SERVICE"}],
)
results = []
for result in response["ResultsByTime"]:
for group in sorted(
result["Groups"],
key=lambda x: float(x["Metrics"]["UnblendedCost"]["Amount"]),
reverse=True,
):
amount = float(group["Metrics"]["UnblendedCost"]["Amount"])
if amount > 0:
results.append({"service": group["Keys"][0], "cost_usd": round(amount, 4)})
return json.dumps(results, ensure_ascii=False)
@tool
def get_cost_by_day(service: str, start_date: str, end_date: str) -> str:
"""Get daily cost trend for a specific AWS service. Use this to identify which days had cost spikes.
Args:
service: AWS service name (e.g. 'Amazon EC2', 'Amazon S3')
start_date: Start date in YYYY-MM-DD format
end_date: End date in YYYY-MM-DD format (exclusive)
"""
client = boto3.client("ce", region_name="us-east-1")
response = client.get_cost_and_usage(
TimePeriod={"Start": start_date, "End": end_date},
Granularity="DAILY",
Metrics=["UnblendedCost"],
Filter={"Dimensions": {"Key": "SERVICE", "Values": [service]}},
)
results = [
{
"date": r["TimePeriod"]["Start"],
"cost_usd": round(float(r["Total"]["UnblendedCost"]["Amount"]), 4),
}
for r in response["ResultsByTime"]
]
return json.dumps(results, ensure_ascii=False)
_agent = None
def get_or_create_agent():
global _agent
if _agent is None:
_agent = Agent(
model=load_model(),
system_prompt=build_system_prompt(),
tools=[get_cost_by_service, get_cost_by_day],
)
return _agent
@app.entrypoint
async def invoke(payload, context):
log.info("Invoking Agent.....")
agent = get_or_create_agent()
stream = agent.stream_async(payload.get("prompt"))
async for event in stream:
if "data" in event and isinstance(event["data"], str):
yield event["data"]
if __name__ == "__main__":
app.run()
4. ローカルテスト
WSL 環境では xdg-open が存在しないため --no-browser で起動し、curl で直接叩きます。
AWS_PROFILE=<PROFILE> agentcore dev --no-browser
別ターミナルから動作確認:
curl -s -X POST http://localhost:8080/invocations \
-H "Content-Type: application/json" \
-d '{"prompt": "Hello"}'
data: "Hello! How can I help you today?"
5. デプロイ
AWS_PROFILE=<PROFILE> agentcore deploy
初回は CDK bootstrap が走るため数分かかります。IAM ロール・AgentCore Runtime エンドポイントが自動作成されます。
デプロイ後に自動作成される IAM ロールのポリシーは以下の通りです(Bedrock / CloudWatch Logs のみ)。
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
],
"Resource": [
"arn:aws:bedrock:*:<ACCOUNTID>:inference-profile/*",
"arn:aws:bedrock:*::foundation-model/*"
],
"Effect": "Allow"
},
{
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:ap-northeast-1:<ACCOUNTID>:log-group:/aws/bedrock-agentcore/runtimes/*",
"Effect": "Allow"
}
]
}
Cost Explorer 権限は含まれていないため、手動で追加する必要があります。本来は agentcore/cdk/lib/cdk-stack.ts でロールに権限を定義するのが正しいですが、今回はお試しのため手動で attach しました。
aws iam attach-role-policy \
--role-name <RUNTIME_ROLE_NAME> \
--policy-arn arn:aws:iam::aws:policy/AWSBillingReadOnlyAccess \
--profile <PROFILE>
6. 動作確認
AWS_PROFILE=<PROFILE> agentcore invoke \
--prompt "AWSコストをサービス別に調べて。期間は2026-04-01から2026-04-30"
エージェントが以下を自律的に実行します:
-
get_cost_by_serviceでサービス別コストを取得 - 上位サービス(CloudWatch / Amazon Q)を自分で判断して
get_cost_by_dayで深掘り - スパイク日(4/20)を特定してサマリーを生成
実行結果(抜粋):
コストが高い順にサービスを並べると、以下のようになります:
1. AmazonCloudWatch: $19.621
2. Amazon Q: $18.3667
3. Amazon Elastic Load Balancing: $15.3173
...
AmazonCloudWatch の変動を見ると、特に 2026-04-20 に $2.5392 という高いコストが記録されています。
この変動は、新しいメトリクスやアラームが作成されたり、既存のリソースの使用量が急増したりすることが考えられます。
Session: a9ee6072-424f-4d9f-8157-3fa1b30c8b26
To resume: agentcore invoke --session-id a9ee6072-424f-4d9f-8157-3fa1b30c8b26
指示していないのに get_cost_by_day を自分で呼んで深掘りしているのがポイントです。 エージェントは「どのツールをどの順番で呼ぶか」を自律的に判断します。docstring に「コスト増加の原因調査の最初のステップで使う」「スパイク日の特定に使う」と書いたことで、LLM が適切な順序でツールを選択しています。
7. リソース削除
AgentCore CLI でリソースを削除します。
agentcore remove all
agentcore deploy
remove all で agentcore/agentcore.json の設定をリセットし、続く deploy で AWS 上のリソース(CloudFormation スタック)が削除されます。
詰まったポイント
agentcore dev がブラウザに到達しない(WSL)
agentcore dev 起動時に Error: spawn xdg-open ENOENT が出てブラウザが開きません。WSL 環境では xdg-open が存在しないためです。
対処: --no-browser フラグで起動し、curl で直接叩きます。
AWS 認証情報が見つからない
agentcore dev 起動後に curl を叩くと NoCredentialsError が発生します。agentcore dev 自体に AWS_PROFILE を渡す必要があります。
AWS_PROFILE=<PROFILE> agentcore dev --no-browser
モデルが「未来の日付」と判断してツールを呼ばない
Nova Lite の学習データカットオフが 2024 年以前のため、2025 年・2026 年を「未来」と判断してツール呼び出しを拒否します。
対処: システムプロンプトに現在日付を動的に埋め込みます。
def build_system_prompt() -> str:
today = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d")
return f"Today's date is {today}. ..."
これは Nova Lite 固有の問題です。Claude 系モデルでは発生しません。コストを抑えるために Nova Lite を選んだトレードオフとして認識しておく必要があります。また、英語のシステムプロンプトに Always respond in Japanese と書く方式は、Nova Lite では指示追従が弱く無視されることがあります。確実に日本語で返したい場合はシステムプロンプト自体を日本語で書くことも検討してください。
agentcore invoke がローカルではなくクラウドに向く
agentcore dev でローカル起動中でも agentcore invoke はクラウドのデプロイ済みエンドポイントに向きます。ローカルテストは curl で http://localhost:8080/invocations を直接叩きます。
クラウドの実行ロールに Cost Explorer 権限がない
agentcore deploy で自動作成される実行ロールには Cost Explorer 権限が含まれていません。本来は agentcore/cdk/lib/cdk-stack.ts でロールに権限を追加するのが正しいですが、今回はお試しのため手動で AWSBillingReadOnlyAccess を attach しました。
まとめ
-
agentcore create→agentcore dev→agentcore deployの 3 ステップで CDK を自分で書かずにデプロイできます - CDK インフラは CLI が自動管理するため、自分で書くのはエージェントロジック(
main.py)のみです - ツールの docstring が LLM へのツール説明になります。「いつ使うか」を明記することが設計の核心です
- エージェントは「どのツールをどの順番で呼ぶか」を自律的に判断します。今回は指示していないのに
get_cost_by_dayを自分で呼んで深掘りしました - IAM 権限はデプロイ後に不足が判明することがあります。本番では CDK でロールに権限を定義しておくべきです
AgentCore は「フレームワークを選ばない」「インフラを意識しない」という点で、エージェント開発の敷居を大きく下げてくれるサービスだと感じました。一方で、IAM 権限の管理やモデル固有の癖(日付問題など)は従来通り自分で対処する必要があるので、マネージドだからといって全部お任せにはできません。












