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?

Bedrock AgentCore RuntimeでAIエージェントを簡単デプロイ

Posted at

みなさん、こんにちは!
AIエージェントは用意した。さて、どうやってデプロイしよう...
そんなときにうってつけなのが Bedrock AgentCore の Runtime です。

本記事では、AIエージェント運用を包括的にサポートするサービスである Bedrock AgentCore の Runtime について詳しくご紹介します。

Runtimeとは?

Runtime は、AIエージェントをホスティングするためのマネージドサービスです。

以下のような特徴があります:

  • 各セッションが microVM により分離されている
  • 最大8時間まで実行可能
  • モデルやフレームワークに対する制約がない

AIエージェントをデプロイする場合、セッション管理やセキュリティについて考慮する必要がありますが、Runtime には専用の実行環境が用意されており、そうした事柄について心配することなくAIエージェントワークロードの構築に集中することができます。

ホスティング特化のサービスとなっており、モデルやフレームワークに対する制約がなく、リソースがAWS上にある必要もありません。Strands Agents や LangGraph など好みのフレームワークで実装したAIエージェントを用意するだけで、すぐにAIエージェントをデプロイして利用することができます。

AIエージェントは設定ファイルの内容に基づいて CodeBuild でビルドされた後、コンテナイメージとして ECR にpushされ、AgentCore にデプロイされます。デプロイまでの流れは以下のイメージです。
image.png

利用手順

セットアップ

Pythonのパッケージマネージャーであるuvのインストールと仮想環境の構築を行います。

qiita.rb
# uvをインストール
curl -LsSf https://astral.sh/uv/install.sh | sh
# 仮想環境構築
mkdir agentcore
cd agentcore
uv init
uv venv
source .venv/bin/activate

pyproject.toml を以下の内容に置き換えます。

qiita.rb
[project]
name = "agentcore"
version = "0.1.0"
description = "AgentCore Hands-on"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
    "strands-agents",
    "strands-agents-tools",
    "bedrock-agentcore",
    "bedrock-agentcore-starter-toolkit"
]

依存関係を同期し、dependenciesに指定したライブラリをインストールします。

qiita.rb
uv sync

デプロイ

main.pyを以下に置き換えます。

qiita.rb
from strands import Agent
from strands.models import BedrockModel
from strands.tools.mcp import MCPClient
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from mcp import stdio_client, StdioServerParameters

app = BedrockAgentCoreApp()

@app.entrypoint
async def entrypoint(payload):
    # ペイロードからメッセージとモデルIDを取得
    message = payload.get("prompt", "")
    model = payload.get("model", {})
    model_id = model.get("modelId", "apac.anthropic.claude-sonnet-4-20250514-v1:0")

    # モデルとMCPクライアントを設定
    model = BedrockModel(model_id=model_id, params={"max_tokens": 4096, "temperature": 0.7}, region="ap-northeast-1")
    fetch_mcp_client = MCPClient(
        lambda: stdio_client(StdioServerParameters(command="uvx", args=["mcp-server-fetch"]))
    )

    with fetch_mcp_client:
        # エージェントを作成
        agent = Agent(model=model, tools=fetch_mcp_client.list_tools_sync())
        # メッセージに対する応答を返す
        stream_messages = agent.stream_async(message)
        async for message in stream_messages:
            if "event" in message:
                yield message

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

公式のFetch MCPサーバを利用し、Web検索結果をもとに回答するAIエージェントを作成しています。
事前にAWS CLIの認証情報を設定したうえで、以下を実行します。

agentcore configure --entrypoint main.py

対話的に設定を行っていきます。

Configuring Bedrock AgentCore...
✓ Using file: main.py
...

Options:
  • Press Enter to create new memory
  • Type 's' to skip memory setup

Your choice: s
✓ Skipping memory configuration
Memory disabled by user choice
Network mode: PUBLIC

設定項目は以下の通りです。

  • AgentCore の実行ロール
    ・指定しない場合は自動作成
  • ECRリポジトリ
    ・指定しない場合は自動作成
  • 認証設定
    ・デフォルトではIAM認証
    ・noの場合はOAuth認証
  • リクエストヘッダの許可リスト
    ・指定しない場合はデフォルトのヘッダ構成
  • メモリ設定
    ・短期/長期記憶の設定

設定完了後、以下のファイルが作成されます。

  • .bedrock_agentcore.yaml:デプロイの設定ファイル
  • .dockerignore:Dockerビルド時の除外ファイル設定
  • Dockerfile:コンテナイメージの定義

以下のコマンドでデプロイを実行します。

agentcore launch

上記を実行すると、AIエージェントが AgentCore にデプロイされます。

実行

agentcore invokeでAIエージェントの実行が可能です。

agentcore invoke '{"prompt": "現在の日本の首相は?"}'

実行結果は以下のようになり、Fetch MCPサーバを利用して最新の情報をもとに回答できていることが確認できます。
MCPサーバの利用はレスポンスの中でtoolUseとして表示されています。

{"event": {"messageStart": {"role": "assistant"}}}

{"event": {"contentBlockDelta": {"delta": {"text": "現在の日本の首"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "相について最新の"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "情報を確認い"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "たします。"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockStop": {"contentBlockIndex": 0}}}

{"event": {"contentBlockStart": {"start": {"toolUse": {"toolUseId": "tooluse_dk5ESU8lT8CA6KqDLv8mOw", "name": "fetch"}}, "contentBlockIndex": 1}}}

{"event": {"contentBlockDelta": {"delta": {"toolUse": {"input": ""}}, "contentBlockIndex": 1}}}

{"event": {"contentBlockDelta": {"delta": {"toolUse": {"input": "{\"u"}}, "contentBlockIndex": 1}}}

{"event": {"contentBlockDelta": {"delta": {"toolUse": {"input": "rl\": \""}}, "contentBlockIndex": 1}}}

{"event": {"contentBlockDelta": {"delta": {"toolUse": {"input": "https://ww"}}, "contentBlockIndex": 1}}}

{"event": {"contentBlockDelta": {"delta": {"toolUse": {"input": "w.kant"}}, "contentBlockIndex": 1}}}

{"event": {"contentBlockDelta": {"delta": {"toolUse": {"input": "ei.go.jp"}}, "contentBlockIndex": 1}}}

{"event": {"contentBlockDelta": {"delta": {"toolUse": {"input": "/\"}"}}, "contentBlockIndex": 1}}}

{"event": {"contentBlockStop": {"contentBlockIndex": 1}}}

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

{"event": {"metadata": {"usage": {"inputTokens": 648, "outputTokens": 84, "totalTokens": 732}, "metrics": {"latencyMs": 1765}}}}

{"event": {"messageStart": {"role": "assistant"}}}

{"event": {"contentBlockDelta": {"delta": {"text": "首"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "相官邸の"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "ホームページを"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "確認した"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "ところ、現在の日本"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "の首相は**高市早"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "苗**"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "氏であることが分かります。"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "\n\nページには「高市内閣総理大"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "臣所信表明演説」"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "や「高市総理」"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "という表記が"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "複数箇所に見られ"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "、2024"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "年の活"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "動記録も掲載されています。"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "高市総理はAP"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "EC首脳会議への"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "出席や日米"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "首脳会談、"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "ASEAN関連首脳"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "会議など、精"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "力的に"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "外交活動を行"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "ってい"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockDelta": {"delta": {"text": "ることが伺えます。"}, "contentBlockIndex": 0}}}

{"event": {"contentBlockStop": {"contentBlockIndex": 0}}}

{"event": {"messageStop": {"stopReason": "end_turn"}}}

{"event": {"metadata": {"usage": {"inputTokens": 2152, "outputTokens": 157, "totalTokens": 2309}, "metrics": {"latencyMs": 3753}}}}

なお、CLIではなくPythonやTypeScriptのプログラムから呼び出すことも可能です。
以下のPythonコードを実行すると、CLIの場合と同じ結果を取得できます。

qiita.rb
import json
import boto3

client = boto3.client('bedrock-agentcore', region_name='ap-northeast-1')
payload = json.dumps({"prompt": "現在の日本の首相は?"})

# レスポンス取得
response = client.invoke_agent_runtime(
    agentRuntimeArn='<AGENT_RUNTIME_ARN>',
    payload=payload
)

if "text/event-stream" in response.get("contentType", ""):
    # ストリーミングレスポンスの場合
    content = []
    for line in response["response"].iter_lines(chunk_size=10):
        if line:
            line = line.decode("utf-8")
            if line.startswith("data: "):
                line = line[6:]
                print(line)
                content.append(line)
    print("\nComplete response:", "\n".join(content))

elif response.get("contentType") == "application/json":
    # 標準的なJSONレスポンスの場合
    content = []
    for chunk in response.get("response", []):
        content.append(chunk.decode('utf-8'))
    print(json.loads(''.join(content)))

else:
    # その他の場合
    print(response)

AGENT_RUNTIME_ARNは AgentCore の「エージェントランタイム」を開き、エージェント詳細画面の「呼び出しコードを表示」タブで確認できます。
image.png

課金について

Runtime は従量課金制であり、リソースの事前設定は不要です。

課金対象リソースは以下の通りです:

  • CPUリソース
    ・エージェントの実稼働時間に応じて課金(LLM応答のI/O待機時間は含まれない)
    ・vCPU 時間あたり 0.0895 USD
  • メモリリソース
    ・エージェントの消費メモリに対して課金
    ・GB 時間あたり 0.00945 USD

例)月間1万リクエスト、1セッションあたりの実行時間60秒、I/O待機時間(LLM応答の待機または内部APIからのネットワーク応答の待機)70%で、各エージェントが稼働中に1vCPUを使用し、2GBのメモリを断続的に使用する場合

  • セッションあたりのCPUコスト:60 x 0.3 x 0.0895 / 3600 = 0.0004475 USD
  • セッションあたりのメモリコスト:60 x 2 x 0.00945 / 3600 = 0.000315 USD
  • セッションあたりの総コスト:0.0007625 USD

より、月額合計は 10,000 x 0.0007625 = 7.625 USD
エージェントの実稼働時間に応じた課金となっており、I/O待機時間は含まれないため、従来のホスティング方法と比較して高いコストパフォーマンスが得られます。

さいごに

AIエージェントのホスティング機能である Runtime についてご紹介しました。

Runtime を利用することで、自作またはマーケットプレイスで取得したAIエージェントを安全かつスケーラブルなAWS環境に素早くデプロイし、API経由で利用することができます。AIエージェントのホスティング方法でお悩みの方は、ぜひ Runtime を試してみてください。

参考

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?