電通総研 テックブログ

電通総研が運営する技術ブログ

Amazon Bedrock AgentCoreでAI Agentを作成する

XI本部、2025 Japan AWS Jr. Champions の佐藤悠です。

本記事ではAWSのAmazon Bedrock AgentCore(以下:AgentCore)を用いて、AI AgentをAWSにホストするところまでをやっていきます。

Agentを用いて自分専用のチャットアプリを作成したいと考えたのが、モチベーションです。

AgentCoreとは

Amazon Bedrock AgentCore は、効果的なエージェントを大規模かつ安全に構築、デプロイ、運用するためのエージェントプラットフォームです。

引用:https://aws.amazon.com/jp/bedrock/agentcore/

上記のようにAI Agent構築のためのプラットフォームになっており、変化の速いAIサービスに対応可能な素早いデプロイに強みがあると思っています。

デモ構成のAgentをローカルで立ち上げる

コード

エージェントの作成はStrands Agents というOSSのSDKを使用します。

数行のコードと直感的なアノテーションをもとにAgnetを実装できるので今回はこちらを採用し、検証をします。

早速、公式ドキュメントの例を参考にStrandsでツールの作成、Agentの定義をします。

from strands import Agent, tool
from strands_tools import calculator # Import the calculator tool
import argparse
import json
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from strands.models import BedrockModel

app = BedrockAgentCoreApp()

# Create a custom tool 
@tool
def weather():
    """ Get weather """ # Dummy implementation
    return "sunny"


model_id = "global.anthropic.claude-haiku-4-5-20251001-v1:0"
model = BedrockModel(
    model_id=model_id,
)
agent = Agent(
    model=model,
    tools=[calculator, weather],
    system_prompt="You're a helpful assistant. You can do simple math calculation, and tell the weather."
)

@app.entrypoint
def strands_agent_bedrock(payload):
    """
    Invoke the agent with a payload
    """
    user_input = payload.get("prompt")
    print("User input:", user_input)
    response = agent(user_input)
    return response.message['content'][0]['text']

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

引用:runtime_with_strands_and_bedrock_models.ipynb

このコードではstrands_toolsで既に構築済みのツール群を用い、計算ツールが定義されています。

また、カスタムツールも使用しています。

自作の関数を@toolデコレーターで使用可能なツールとして認識させられます。

この関数名をAgent()の初期化時に渡すことでツールの使用が可能になる仕組みです。

Agentがユーザーの入力を受け取り回答を生成する関数を、@app.entrypointデコレーターでハンドラーとして指定しています。

app = BedrockAgentCoreApp()でランタイムアプリケーションを初期化し、app.run()でローカルHTTPサーバーを起動してリクエストを受け付けられる状態にしています。

python weather.py 実行
  ↓
app.run() でサーバー起動
  ↓
HTTPリクエスト待ち受け
  ↓
POST /invocations にリクエスト
  ↓
@app.entrypoint の関数が実行
  ↓
レスポンス返却

環境構築

次にvenvを用いた環境を構築します。

#python version確認(※v3.10以上が必須です)
> python --version
Python 3.14.2
#環境分離
> python -m venv .venv
#仮想環境を有効にする
> source .venv/bin/activate

以下のrequiremets.txtを記述します。

strands-agents
strands-agents-tools
bedrock-agentcore-starter-toolkit

フォルダ構成は現時点で以下のようになっています。

workspace/
├──.venv/
├──weather.py
└──requirements.txt

workspaceで以下のコマンドを実行し、モジュールのインストールを実行します。

pip insatll -r requirements.txt

ローカルでの実行

次に以下のコマンドを実行し、サーバーの起動を実施します。

#ローカルでの起動
>python3 weather.py
#別ターミナルでのレスポンス確認
> curl -X POST http://localhost:8080/invocations   -H "Content-Type: application/json"   -d '{"prompt": "What is the weather?"}'
#レスポンス
"The weather is **sunny**! It looks like a beautiful day outside."

以上のようにしてレスポンスを確認することができました。

ここまでまとめ

AgentCoreを用いてサーバーを起動すると以下のような構成でAgentをローカルで起動することができます。

  • HTTPを8080でリッスンする
  • /invocationエンドポイントが作成される
  • /pingのエンドポイントがhealth checkのために作成される
  • レスポンスの自動フォーマット
  • エラーハンドリング(※AWS基準に準拠)

デプロイ設定

コード

以下のコードでDockerFileの作成を含む構成要件の設定をします。

from bedrock_agentcore_starter_toolkit import Runtime
from boto3.session import Session
boto_session = Session()
region = boto_session.region_name

agentcore_runtime = Runtime()
agent_name = "strands_claude_getting_started"
response = agentcore_runtime.configure(
    entrypoint="strands_claude.py",
    auto_create_execution_role=True,
    auto_create_ecr=True,
    requirements_file="requirements.txt",
    region=region,
    agent_name=agent_name
)
response

設定ファイル実行

フォルダ構成

workspace/
├──.venv/
├──weather.py
├──cofigure.py(※本手順で作成)
├──Dockerfile(※ファイル実行後生成)
├──.bedrock_agentcore.yaml(※ファイル実行後生成)
└──requirements.txt

以下のコマンドを実行します

python3 cofigure.py

Dockerfile

作成されるDockerfileは以下のような構成になります

FROM ghcr.io/astral-sh/uv:python3.14-bookworm-slim
WORKDIR /app

# All environment variables in one layer
ENV UV_SYSTEM_PYTHON=1 \
    UV_COMPILE_BYTECODE=1 \
    UV_NO_PROGRESS=1 \
    PYTHONUNBUFFERED=1 \
    DOCKER_CONTAINER=1 \
    AWS_REGION=ap-northeast-1 \
    AWS_DEFAULT_REGION=ap-northeast-1

COPY requirements.txt requirements.txt
# Install from requirements file
RUN uv pip install -r requirements.txt
RUN uv pip install aws-opentelemetry-distro==0.12.2

# Signal that this is running in Docker for host binding logic
ENV DOCKER_CONTAINER=1

# Create non-root user
RUN useradd -m -u 1000 bedrock_agentcore
USER bedrock_agentcore

EXPOSE 9000
EXPOSE 8000
EXPOSE 8080

# Copy entire project (respecting .dockerignore)
COPY . .

# Use the full module path

CMD ["opentelemetry-instrument", "python", "-m", "weather"]
  • Python 3.14 + uv の軽量イメージを使用
  • 東京リージョンをデフォルトに設定
  • 依存パッケージをインストール
  • OpenTelemetryを追加
  • 非rootユーザー(bedrock_agentcore)を作成して切り替え
  • 8080・8000・9000 ポートを公開
  • OpenTelemetry計装付きで weather.py を起動

以上のようなDockerfileが自動で作成されます。

AWS設定項目

ECRのレポジトリを自動で作成する設定や、agentの名前が渡されたyaml(.bedrock_agentcore.yaml)が作成されることが確認できました。

default_agent: strands_claude_getting_started
agents:
  strands_claude_getting_started:
    name: strands_claude_getting_started
    language: python
    node_version: null
    entrypoint: /home/sato/agent-core/weather.py
    deployment_type: container
    runtime_type: null
    platform: linux/arm64
    container_runtime: none
    source_path: null
    aws:
      execution_role: null
      execution_role_auto_create: true
      account: 'mask' #アカウント番号
      region: ap-northeast-1
      ecr_repository: null
      ecr_auto_create: true
      s3_path: null
      s3_auto_create: false
      network_configuration:
        network_mode: PUBLIC
        network_mode_config: null
      protocol_configuration:
        server_protocol: HTTP
      observability:
        enabled: true
      lifecycle_configuration:
        idle_runtime_session_timeout: null
        max_lifetime: null
    bedrock_agentcore:
      agent_id: null
      agent_arn: null
      agent_session_id: null
    codebuild:
      project_name: null
      execution_role: null
      source_bucket: null
    memory:
      mode: NO_MEMORY
      memory_id: null
      memory_arn: null
      memory_name: null
      event_expiry_days: 30
      first_invoke_memory_check_done: false
      was_created_by_toolkit: false
    identity:
      credential_providers: []
      workload: null
    aws_jwt:
      enabled: false
      audiences: []
      signing_algorithm: ES384
      issuer_url: null
      duration_seconds: 300
    authorizer_configuration: null
    request_header_configuration: null
    oauth_configuration: null
    api_key_env_var_name: null
    api_key_credential_provider_name: null
    is_generated_by_agentcore_create: false

この後にデプロイをしますが、AgentCoreはこの設定項目を参照します。

デモ構成のAgentをAWSにデプロイする

コード

フォルダ構成

workspace/
├──.venv/
├──weather.py
├──cofigure.py(※本手順で編集)
├──Dockerfile
├──.bedrock_agentcore.yaml
└──requirements.txt

起動のためのコード

# configure.pyの末尾のresponceを以下の記述に置換
launch_result = agentcore_runtime.launch()

デプロイ実行

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

python3 configure.py

これまでの設定方法に問題がなければエラーなくデプロイが完了するはずです。

呼び出しテスト

コード

フォルダ構成

workspace/
├──.venv/
├──weather.py
├──cofigure.py
├──Dockerfile
├──.bedrock_agentcore.yaml
├──client.py(※本手順で追加)
└──requirements.txt

以下のコードでAgentを呼び出してみます

import boto3, json

agent_arn = "arn:aws:bedrock-agentcore:<your_arn>"

agentcore_client = boto3.client('bedrock-agentcore', region_name="ap-northeast-1")

response = agentcore_client.invoke_agent_runtime(
    agentRuntimeArn=agent_arn,
    qualifier="DEFAULT",
    payload=json.dumps({"prompt": "What is the weather?"})
)

# レスポンスの中身を取り出して表示
body = response['response'].read()
print(json.loads(body))

実行結果

以下のコマンドを実行して上記のコードを実行し、レスポンスが返却されることを確認しました。

❯ python client.py
The weather is **sunny**! ☀️ It looks like a nice day out there.

ここまでまとめ

今回の手順では、以下のようなフローでエンドポイントがホストされます

  • zip化したコードとDockerfileをS3にアップロードする
  • CodeBuildがS3からダウンロードしビルドを実行、ECRへプッシュする
  • RuntimeAgentがこのイメージを利用して、エンドポイントを公開

※この手順ではエンドポイントの呼び出しにAWSの認証情報を使用

Terraformコード化する

実際の運用では、デプロイフローをIaCで管理することになるでしょう。

bedrock_agentcore_starter_toolkitで自動作成できる良さはありますが、使用するコンポーネントをTerraformする試みを実施します。

Terraformコード化

どのような設定でリソースが作成されるかを知りたいので、観測可能な範囲で(※本当はCloudTrailでトレースするべきではありますが...)importを実行します。

この際にimport時の自動生成機能を用いてtfファイルを記述します。

import

以下のように記述してimportをします。

ロールだけはCreateRoleでフィルターし、作業時間からCloudTrailで特定しました。

import {
  id = "AmazonBedrockAgentCoreSDKCodeBuild-ap-northeast-1-mask"
  to = aws_iam_role.example_role_codebuild
}

import {
  id = "AmazonBedrockAgentCoreSDKRuntime-ap-northeast-1-mask"
  to = aws_iam_role.example_role_runtime
}

import {
  id = "bedrock-agentcore-runtime-415467724776-ap-northeast-1-7pd9unr6i"
  to = aws_s3_bucket.example
}

import {
  id = "bedrock-agentcore-strands_claude_getting_started"
  to = aws_ecr_repository.example
}

import {
  id = "bedrock-agentcore-strands_claude_getting_started-builder"
  to = aws_codebuild_project.example
}

import {
  id = "strands_claude_getting_started-mask"
  to = awscc_bedrockagentcore_runtime.example
}
💡AgentCore Runtime import の id に何を指定するの?
AgentCore RuntimeのIDとは個別のランタイムをひらいた際に表示される「ランタイム ID」のことでした。

自動生成結果

自動生成の結果は以下のようになりました。

サフィックスやアカウント番号はマスクしています。

# __generated__ by Terraform
# Please review these resources and move them into your main configuration files.

# __generated__ by Terraform
resource "aws_codebuild_project" "example" {
  badge_enabled          = false
  build_timeout          = 60
  concurrent_build_limit = 1
  description            = null
  encryption_key         = "arn:aws:kms:ap-northeast-1:mask:alias/aws/s3"
  name                   = "bedrock-agentcore-strands_claude_getting_started-builder"
  project_visibility     = "PRIVATE"
  queued_timeout         = 480
  resource_access_role   = null
  service_role           = "arn:aws:iam::mask:role/AmazonBedrockAgentCoreSDKCodeBuild-ap-northeast-1-mask"
  source_version         = null
  tags                   = {}
  tags_all               = {}
  artifacts {
    artifact_identifier    = null
    bucket_owner_access    = null
    encryption_disabled    = false
    location               = null
    name                   = null
    namespace_type         = null
    override_artifact_name = false
    packaging              = null
    path                   = null
    type                   = "NO_ARTIFACTS"
  }
  cache {
    location = null
    modes    = []
    type     = "NO_CACHE"
  }
  environment {
    certificate                 = null
    compute_type                = "BUILD_GENERAL1_MEDIUM"
    image                       = "aws/codebuild/amazonlinux2-aarch64-standard:3.0"
    image_pull_credentials_type = "CODEBUILD"
    privileged_mode             = true
    type                        = "ARM_CONTAINER"
  }
  source {
    buildspec           = "\nversion: 0.2\nphases:\n  build:\n    commands:\n      - echo \"Starting parallel Docker build and ECR authentication...\"\n      - |\n        docker build -t bedrock-agentcore-arm64 . &\n        BUILD_PID=$!\n        aws ecr get-login-password --region $AWS_DEFAULT_REGION | \\\n        docker login --username AWS --password-stdin mask.dkr.ecr.ap-northeast-1.amazonaws.com/bedrock-agentcore-strands_claude_getting_started &\n        AUTH_PID=$!\n        echo \"Waiting for Docker build to complete...\"\n        wait $BUILD_PID\n        if [ $? -ne 0 ]; then\n          echo \"Docker build failed\"\n          exit 1\n        fi\n        echo \"Waiting for ECR authentication to complete...\"\n        wait $AUTH_PID\n        if [ $? -ne 0 ]; then\n          echo \"ECR authentication failed\"\n          exit 1\n        fi\n        echo \"Both build and auth completed successfully\"\n      - echo \"Tagging image with version 20260219-074315-241...\"\n      - \"docker tag bedrock-agentcore-arm64:latest mask.dkr.ecr.ap-northeast-1.amazonaws.com/bedrock-agentcore-strands_claude_getting_started:20260219-074315-241\"\n  post_build:\n    commands:\n      - echo \"Pushing versioned image to ECR...\"\n      - \"docker push mask.dkr.ecr.ap-northeast-1.amazonaws.com/bedrock-agentcore-strands_claude_getting_started:20260219-074315-241\"\n      - echo \"Build completed at $(date)\"\n"
    git_clone_depth     = 0
    insecure_ssl        = false
    location            = "bedrock-agentcore-codebuild-sources-mask-ap-northeast-1/strands_claude_getting_started/source.zip"
    report_build_status = false
    type                = "S3"
  }
}

# __generated__ by Terraform from "bedrock-agentcore-strands_claude_getting_started"
resource "aws_ecr_repository" "example" {
  force_delete         = null
  image_tag_mutability = "MUTABLE"
  name                 = "bedrock-agentcore-strands_claude_getting_started"
  tags                 = {}
  tags_all             = {}
  encryption_configuration {
    encryption_type = "AES256"
    kms_key         = null
  }
  image_scanning_configuration {
    scan_on_push = false
  }
}

# __generated__ by Terraform from "bedrock-agentcore-runtime-mask-ap-northeast-1-7pd9unr6i"
resource "aws_s3_bucket" "example" {
  bucket              = "bedrock-agentcore-runtime-mask-ap-northeast-1-7pd9unr6i"
  bucket_prefix       = null
  force_destroy       = null
  object_lock_enabled = false
  tags                = {}
  tags_all            = {}
}

# __generated__ by Terraform from "strands_claude_getting_started-mask"
resource "awscc_bedrockagentcore_runtime" "example" {
  agent_runtime_artifact = {
    code_configuration = null
    container_configuration = {
      container_uri = "mask.dkr.ecr.ap-northeast-1.amazonaws.com/bedrock-agentcore-strands_claude_getting_started:20260219-074315-241"
    }
  }
  agent_runtime_name       = "strands_claude_getting_started"
  authorizer_configuration = null
  description              = null
  environment_variables    = {}
  lifecycle_configuration = {
    idle_runtime_session_timeout = 900
    max_lifetime                 = 28800
  }
  network_configuration = {
    network_mode        = "PUBLIC"
    network_mode_config = null
  }
  protocol_configuration       = "HTTP"
  request_header_configuration = null
  role_arn                     = "arn:aws:iam::mask:role/AmazonBedrockAgentCoreSDKRuntime-ap-northeast-1-mask"
  tags                         = {}
}

# __generated__ by Terraform from "AmazonBedrockAgentCoreSDKCodeBuild-ap-northeast-1-mask"
resource "aws_iam_role" "example_role_codebuild" {
  assume_role_policy = jsonencode({
    Statement = [{
      Action = "sts:AssumeRole"
      Condition = {
        StringEquals = {
          "aws:SourceAccount" = "mask"
        }
      }
      Effect = "Allow"
      Principal = {
        Service = "codebuild.amazonaws.com"
      }
    }]
    Version = "2012-10-17"
  })
  description           = "CodeBuild execution role for Bedrock AgentCore ARM64 builds"
  force_detach_policies = false
  max_session_duration  = 3600
  name                  = "AmazonBedrockAgentCoreSDKCodeBuild-ap-northeast-1-mask"
  name_prefix           = null
  path                  = "/"
  permissions_boundary  = null
  tags                  = {}
  tags_all              = {}
}

# __generated__ by Terraform from "AmazonBedrockAgentCoreSDKRuntime-ap-northeast-1-mask"
resource "aws_iam_role" "example_role_runtime" {
  assume_role_policy = jsonencode({
    Statement = [{
      Action = "sts:AssumeRole"
      Condition = {
        ArnLike = {
          "aws:SourceArn" = "arn:aws:bedrock-agentcore:ap-northeast-1:mask:*"
        }
        StringEquals = {
          "aws:SourceAccount" = "mask"
        }
      }
      Effect = "Allow"
      Principal = {
        Service = "bedrock-agentcore.amazonaws.com"
      }
      Sid = "AssumeRolePolicy"
    }]
    Version = "2012-10-17"
  })
  description           = "Execution role for BedrockAgentCore Runtime - strands_claude_getting_started"
  force_detach_policies = false
  max_session_duration  = 3600
  name                  = "AmazonBedrockAgentCoreSDKRuntime-ap-northeast-1-mask"
  name_prefix           = null
  path                  = "/"
  permissions_boundary  = null
  tags                  = {}
  tags_all              = {}
}
💡importの不具合
`aws_codebuild_project`の`concurrent_build_limit = 1`はなぜかインポートした際に0になってました。
1に変更してplan後に差分がなかったのでこれが正ですが、生成ができなかった理由は追及してません。

Terraform化したことで、AgentCore周辺の基本的な構成がはっきりしてきました。

感想

記事が長くなったので一旦ここで切ります。

構成図を眺めてTerraform化してみるとAWSの一般的なCI/CDパイプラインの部分は理解できますが、AgentをホストするRuntimeがどのような機能を担うのかよくわかりません。

この詳細を追うのがAgentCoreプラットフォームを理解する重要な観点になると考えました。

今は認証等を実装しつつあるので、今後はその記事を出せればと思っています。

私たちは一緒に働いてくれる仲間を募集しています!

電通総研 キャリア採用サイト 電通総研 新卒採用サイト

執筆:@sato.yu
レビュー:Ishizawa Kento (@kent)
Shodoで執筆されました