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

同じエージェントコードをローカルと AgentCore Runtime で動かしたら、ツールの選び方が変わった — 直接コードデプロイで実測比較

0
Posted at

TL;DR

自作の AWS 運用エージェント (Converse API の tool use ループ + read-only ツール4種) を、同じコードのままローカル実行と Amazon Bedrock AgentCore Runtime の2通りで動かして比較しました。

  • 移行作業は「Lambda の zip デプロイ」とほぼ同型でした。60行弱のラッパ + arm64 向け zip + Terraform で、Docker も ECR も不要 (直接コードデプロイ)。
  • 同じコード・同じ質問なのに、ツールの選び方が変わりました。絞り込みのフォローアップに対し、ローカルではツールを呼ばず前回結果から回答 (steps=0)、Runtime では無料のログ検索ツールを自発的に追加。方式の差ではなく LLM の非決定性で、エージェントの出力は固定値の一致を前提にできない — 回帰テストは観点評価で設計する必要があることが、2回の実行で体感できます。
  • 今回の構成では、Runtime のセッション内でプロセスメモリ (会話履歴) が生き残りました。アプリの会話履歴 (ただの Python リスト) が microVM のメモリに残り、コードを変えずにマルチターンが成立。アイドルタイムアウトが「会話の寿命」になります。
  • レイテンシは今回も生成が支配。Runtime 化のオーバーヘッドは生成時間の揺れに沈み、アイドル後1回の観測ではコールドスタートも体感できませんでした。
  • 背景: Bedrock Agents (Classic) は 2026-07-30 で新規顧客の受付を終了します。これからエージェントを作るなら AgentCore です (東京対応済み)。
  • 検証環境一式 (Terraform + クライアント) は GitHub で公開: bedrock-lab (phase3 タグ)

この記事で扱うこと・扱わないこと

扱わないこと — tool use ループの作り方そのものは、@502_ さんのエージェントループ記事@ren8k さんの詳細解説が既に丁寧です。AgentCore の直接コードデプロイ機能の紹介も AWS 公式ブログDevelopersIO の記事があります (末尾の参考にまとめています)。

扱うこと — 本記事は「手元で動いているエージェントを、そのまま Runtime に持ち込む移行」の記録です。移行に何が必要で、移行の前後で何が同じで何が変わるのかを、同一の質問セットで実測します。この構図の比較は探した範囲で見つからなかったので、一次データとして残します。

背景: Bedrock Agents Classic が新規終了する

従来の Amazon Bedrock Agents (2023年11月〜) は「Bedrock Agents Classic」となり、公式ドキュメント2026年7月30日から新規顧客に開放されない旨が明記されました。既存利用者はそのまま使えますが、モデルカタログは凍結され、以後の新モデルは後継の AgentCore 側に提供されます。

AgentCore はコードファーストのエージェント実行基盤で、東京リージョンにほぼ全機能が対応済みです (公式リージョン表)。Classic が「エージェントの定義を AWS に登録する」方式だったのに対し、AgentCore は自分のコードを持ち込む方式 — つまり「エージェント」というリソースは登録せず、実体はただのコードです。だからこそ「ローカルで動くものをそのまま持ち込んで比較する」という本記事の実験が成立します。

作ったもの: AWS 運用エージェント

比較の題材は、自分の AWS アカウントを調べる運用エージェントです。

figure1_bedrock_agentcore_local_vs_runtime.png
図1: 同じエージェントコード (橙) を2つの実行基盤で動かす。ツールの鍵束 (IAM ポリシー) も同一で、持ち主だけが違う

項目 内容
エージェント本体 Converse API の tool use ループ (toolConfig / toolUse / toolResult を自前で回す。最大8ステップ)
ツール (すべて read-only) コスト照会 (Cost Explorer) / ログ調査 (CloudWatch Logs) / リソース棚卸し (Tagging API) / ナレッジ検索 (Knowledge Base)
モデル jp.anthropic.claude-sonnet-4-6 (東京/大阪ルーティング)
実行基盤 ② ローカル (Mac で python 実行) / ① AgentCore Runtime (東京、直接コードデプロイ)
時期 2026年7月

安全設計として、ツールは照会系のみです。「FSx を削除して」と頼むと、照会専用である旨を説明して手順だけ案内します (実測済み)。エージェントに書き込み権限を持たせない線引きは、企業導入でも最初に問われるポイントだと思います。

移行作業: 実体は「Lambda の zip デプロイ」だった

ローカルで動いていたエージェント (agent_cli.py) を Runtime に持ち込むのにやったことは3つだけです。

1. エントリポイントのラッパを書く (60行弱)

from bedrock_agentcore.runtime import BedrockAgentCoreApp
from agent_cli import AgentSession, build_clients, resolve_kb_id  # ← 既存コードをそのまま import

app = BedrockAgentCoreApp()
_agent = None  # microVM (セッション単位) のプロセスメモリに履歴が残る

def _get_agent():
    global _agent
    if _agent is None:
        session = boto3.Session()  # Runtime 実行ロールの一時クレデンシャル
        _agent = AgentSession(clients=build_clients(session), ...)
    return _agent

@app.entrypoint
def handler(payload: dict):
    answer = _get_agent().ask(payload.get("prompt", ""))
    return {"result": answer}

Lambda ハンドラを書く感覚そのものです。エージェント本体には一切手を入れていません。

2. arm64 向けの zip を作る

Runtime は ARM64 で動きます (Python はバージョン選択式、今回は 3.13 を指定)。依存は実行先アーキテクチャ向けに取得する必要がありますが、手元が Mac でも uv のクロスプラットフォーム指定で解決できます。

uv pip install \
  --python-platform aarch64-manylinux2014 \
  --python-version 3.13 \
  --target=build/pkg \
  --only-binary=:all: \
  bedrock-agentcore boto3
# → 依存 + agent_cli.py + ラッパを zip (今回 19MB)

3. Terraform でデプロイ

直接コードデプロイは Terraform がネイティブ対応しています (aws_bedrockagentcore_agent_runtimecode_configuration)。Docker も ECR も出てきません。

resource "aws_bedrockagentcore_agent_runtime" "ops_agent" {
  agent_runtime_name = "bedrock_lab_ops_agent"
  role_arn           = aws_iam_role.agent_runtime.arn  # ツール権限はこのロールへ

  agent_runtime_artifact {
    code_configuration {
      entry_point = ["agent_runtime.py"]
      runtime     = "PYTHON_3_13"
      code {
        s3 {
          bucket = aws_s3_bucket.agent_code.id
          prefix = aws_s3_object.agent_package.key  # ← zip
        }
      }
    }
  }
  network_configuration { network_mode = "PUBLIC" }
  lifecycle_configuration {
    idle_runtime_session_timeout = 300   # ← 後述の「会話の寿命」
    max_lifetime                 = 1800
  }
}

IAM は、ローカル実行で利用者ロールに付けていた同じポリシー2枚 (モデル呼び出し+KB / read-only ツール) を Runtime 実行ロールにアタッチしただけ。「運用端末の作業者権限を、バッチサーバのサービスアカウントに移す」あの定番作業と同型です。

実測比較

同一の質問セット (単一ツール / ナレッジ検索 / マルチステップ / 絞り込みフォローアップ) を両方式で実行しました。生成条件も両方式で同一です (inferenceConfig は maxTokens のみ指定、temperature は未指定 = API デフォルト)。

② ローカル ① AgentCore Runtime
実行場所 手元の Mac 東京の microVM (セッション単位で分離)
認証・ツールの鍵 利用者ロール Runtime 実行ロール (同一ポリシーを付け替え)
会話文脈 CLI プロセスのメモリ セッション microVM のメモリ (idle 5分で消滅)
ツール実行の観測 --verbose で手元に表示 クライアントからは見えない。stdout が CloudWatch へ自動転送
同一質問のレイテンシ (棚卸し) 18.2s 14.1s (差は生成の揺れの範囲)
コールドスタート なし 体感差なし (アイドル後の初回も 14.0s)
絞り込みフォローアップ steps=0 (ツールを呼ばず前回回答から再構成) ✅ ただしログ検索を自発追加してよりリッチに回答
追加コスト なし 実行秒課金 (vCPU $0.0895/h + メモリ $0.00945/GB-h。I/O 待機中の CPU は無課金、メモリはピーク分課金が継続)

発見

1. 同じコード・同じ質問でも、ツールの選び方が変わった

タイトルの件です。「今月のコストは?」→「そのうち Bedrock 分だけ詳しく」という絞り込みのフォローアップに対して:

  • ローカル: ツールを一切呼ばず (steps=0)、前回の回答に含まれる内訳から再構成。「追加のコスト照会は行っていません (前回取得データを再利用)。$0.01 の節約です」と自己申告までした
  • Runtime: コスト照会 ($0.01) の再実行は同じく節約しつつ、無料のログ検索ツールを自発的に追加し、ログ考察付きのよりリッチな回答を返した (Runtime 側の CloudWatch ログでツール呼び出しを確認)

どちらも合理的ですが、同じではありません。方式の差というより LLM の非決定性で、「今回の2回の実行では、たまたま同じにならなかった」が正確なところです (temperature 未指定のデフォルト設定)。逆にいえば、エージェントの出力は固定値の一致を前提にできない。回帰テストは「出力の一致」では書けず、観点評価 (禁止操作をしていないか・ツール呼び出し回数が上限内か・回答に根拠があるか) で設計する必要があります。テスト計画を書く立場として、これが2回の実行であっさり観測できたのは収穫でした。

2. 今回の構成では、セッション内でプロセスメモリが生き残った

Runtime はセッション ID (runtimeSessionId) ごとに分離された microVM を立て、プロセスが数分生き残ります。ラッパでグローバル変数に置いたエージェント (会話履歴 = ただの Python リスト) がそのまま残るため、アプリコードを一切変えずにセッション内マルチターンが成立しました。別セッション ID にすると文脈は消えます (実測)。

ただしこの履歴の寿命は idle_runtime_session_timeout (今回 300 秒) まで。インフラの設定値が「会話の寿命」を決める運用パラメータになるのが面白いところです。恒久的な記憶が要件なら、プロセスメモリ頼みではなく AgentCore Memory 等の外部化が筋になります。

3. 観測性が逆転する

ローカルの --verbose (手元にツール実行が見える) は、Runtime ではクライアントに届きません。代わりに stdout が CloudWatch (/aws/bedrock-agentcore/runtimes/...) へ自動転送されます。発見1で「Runtime が実際に何のツールを呼んだか」を確認できたのも CloudWatch のログでした。

デバッグ出力がそのまま運用ログになるということです。エージェントの行動を後から説明できるか (監査・インシデント調査) はログ設計次第 — 運用化するなら、ツール呼び出しの構造化ログを最初から仕込んでおくべきでした。

4. レイテンシは生成が支配 — Runtime 化のオーバーヘッドは誤差

同一質問でローカル 18.2s vs Runtime 14.1s — Runtime 化のオーバーヘッドは生成時間の揺れに沈みます。ツール実行は 0.1〜1.1 秒 (最も遅いのは Cost Explorer)。アイドルタイムアウト後の初回呼び出しも 14.0s (1回の観測) で、直接コードデプロイのセッション生成は速いという仕様どおりでした。

実行基盤の選択はレイテンシではなく、権限・観測性・共用性の要件で決めるのがよさそうです。個人の使い分けとしては「開発・実験はローカル、共用・常設は Runtime」で、移行コストが小さい (ラッパ数十行) ので両対応にしておくのが現実解でした。

おまけ: Gateway でツールを MCP 化 — IAM 認証なら Cognito 不要

仕上げに、エージェント専用だったツール4種を AgentCore Gateway で MCP ツールとして公開しました (構成は「API Gateway + Lambda」と同型)。詳細な手順は DevelopersIO の記事群に譲りますが、1点だけ:

Gateway のインバウンド認証は IAM (SigV4) が選べます (authorizer_type = "AWS_IAM")。個人・社内用途なら Cognito や外部 IdP を立てずに、利用者ロールへ bedrock-agentcore:InvokeGateway を付与するだけで MCP サーバをマネージド公開できました。ツール名が <ターゲット名>___<ツール名> 形式になるので、Lambda 側でプレフィックスを剥がす小細工だけ必要です。

これで同じ read-only ツールの「鍵束」の持ち主が3人になりました: 利用者ロール (ローカル) / Runtime 実行ロール / Lambda 実行ロール (Gateway)。実行基盤が増えても鍵束は同じ、持ち主が変わるだけ — エージェント運用の権限設計は、結局いつもの IAM 設計でした。

まとめ

学び 内容
移行の実体 Lambda の zip デプロイと同型 (ラッパ60行弱 + arm64 zip + Terraform)。Docker/ECR 不要
ツール選択 同一コード・同一質問でも出力の固定一致は前提にできない。回帰テストは観点評価で設計
セッション 今回の構成ではプロセスメモリに履歴が残存。idle timeout が会話の寿命
観測性 verbose → CloudWatch へ逆転。ログ設計 = エージェントの説明責任
レイテンシ 生成が支配。Runtime 化のオーバーヘッドとコールドスタートは誤差
権限 同じポリシーの持ち主が変わるだけ。read-only の線引きが企業導入の第一関門

Terraform 一式・エージェント実装・比較の生データは GitHub で公開しています。

シリーズ前作: モデルアクセス編 (Phase 1) / RAG 3方式比較編 (Phase 2)

参考

AWS 公式

先行記事 (tool use ループの作り方・機能紹介はこちらが詳しいです)

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