どうもこんにちは。ちょっと久々の技術記事なのですが...
今回は、Amazon Bedrock AgentCoreを使って、AIにブラウザ操作をさせてみる検証をしました。(というかまだやってなかったんかい...)
この記事は誰向け?
この記事は以下のような方向けに執筆しています。
- Amazon Bedrock AgentCore を使ってみたい方
- Amazon Bedrock AgentCore を使って、AIにブラウザを操作させたい方
- AWS CDK(Python)でAmazon Bedrock AgentCore Runtimeへエージェントをデプロイしたい方
デモコードの場所
実際に動かしたい方は、以下を参照ください。
実装の流れ
0. 作業プロジェクトの用意
mkdir bedrock-agentcore-browser-demo && cd $_
1. CDKプロジェクトを初期化する
1-1 初期化コマンド
空の作業ディレクトリへ移動し、Python CDKプロジェクトを生成します。
mkdir bedrock-agentcore-browser-demo
cd bedrock-agentcore-browser-demo
cdk init app --language python
主に次のファイルが自動生成されます。
.
├── app.py
├── cdk.json
├── requirements.txt
├── requirements-dev.txt
├── source.bat
├── .gitignore
├── bedrock_agentcore_browser_demo/
│ ├── __init__.py
│ └── bedrock_agentcore_browser_demo_stack.py
└── tests/
├── __init__.py
└── unit/
├── __init__.py
└── test_bedrock_agentcore_browser_demo_stack.py
このプロジェクトでは、CDKの標準生成ディレクトリをinfra/へ整理します。
mkdir -p infra agent/browser_agent tests
touch infra/__init__.py
touch agent/browser_agent/__init__.py
rm -rf bedrock_agentcore_browser_demo
rm -rf tests/unit
最終的な構成は次のとおりです。
.
├── app.py
├── cdk.json
├── requirements.txt
├── requirements-dev.txt
├── infra/
│ ├── __init__.py
│ └── agentcore_browser_stack.py
├── agent/
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── requirements.txt
│ └── browser_agent/
│ ├── __init__.py
│ ├── agent.py
│ └── runtime_app.py
└── tests/
├── test_agent.py
└── test_agentcore_browser_stack.py
1-2. CDK依存パッケージ
生成されたrequirements.txtの内容を次の内容へ置き換えます。
aws-cdk-lib==2.255.0
constructs>=10.0.0,<11.0.0
cdk-ecr-deployment>=4.0.0,<5.0.0
cdk-ecr-deploymentは、CDKがビルドしたイメージを専用ECR Repositoryへ
コピーするために使用します。
-r requirements.txt
pytest>=8.0.0,<9.0.0
1-3. CDKエントリーポイント
生成されたapp.pyの内容を次のコードへ置き換えます。
#!/usr/bin/env python3
import os
import aws_cdk as cdk
from infra.agentcore_browser_stack import AgentCoreBrowserDemoStack
app = cdk.App()
AgentCoreBrowserDemoStack(
app,
"AgentCoreBrowserDemoStack",
env=cdk.Environment(
account=os.environ.get("CDK_DEFAULT_ACCOUNT"),
region=os.environ.get("CDK_DEFAULT_REGION", "ap-northeast-1"),
),
)
app.synth()
1-4. CDK実行設定
cdk.jsonのappを次のように変更します。他のCDK contextは生成された内容を維持して構いません。
{
"app": ".venv/bin/python app.py"
}
.venv/bin/pythonを明示することで、仮想環境をactivateしていなくてもCDKが依存パッケージの入ったPythonを使用します。
1-5. 仮想環境
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements-dev.txt
AWS認証とリージョンを確認します。
aws sts get-caller-identity
export AWS_REGION=ap-northeast-1
対象アカウントとリージョンで初めてCDKを使う場合はbootstrapします。
cdk bootstrap
2. カスタムAgentCore Browserを作成する
以降のインフラコードは、特記がない限り同じCDKスタックへ記述します。
2-1. importとスタッククラスを定義
from pathlib import Path
import cdk_ecr_deployment as ecr_deployment
from aws_cdk import Aws, CfnOutput, Duration, RemovalPolicy, Stack
from aws_cdk import aws_bedrockagentcore as bedrock_agentcore
from aws_cdk import aws_ecr as ecr
from aws_cdk import aws_ecr_assets as ecr_assets
from aws_cdk import aws_iam as iam
from aws_cdk import aws_s3 as s3
from constructs import Construct
DEFAULT_MODEL_ID = "jp.anthropic.claude-sonnet-4-6"
class AgentCoreBrowserDemoStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
model_id = self.node.try_get_context("modelId") or DEFAULT_MODEL_ID
リソースの定義は、この__init__内へ追加します。
2-2. 録画用S3バケットを用意
今回は、エージェントがブラウザ操作している様子を録画してS3に保存しておこうと思います。
infra/agentcore_browser_stack.pyの__init__関数に以下を追加します。
recording_bucket = s3.Bucket(
self,
"BrowserRecordingBucket",
block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
encryption=s3.BucketEncryption.S3_MANAGED,
enforce_ssl=True,
lifecycle_rules=[
s3.LifecycleRule(
expiration=Duration.days(30),
noncurrent_version_expiration=Duration.days(7),
)
],
auto_delete_objects=True,
removal_policy=RemovalPolicy.DESTROY,
)
- 録画は30日後に削除します
- パブリックアクセスは全てブロックしておきましょう
2-3. Browser実行ロール
次に、録画されたファイルをS3バケットに保存するための権限(ロール)を定義します。
infra/agentcore_browser_stack.pyの__init__関数に以下を追加します。
browser_execution_role = iam.Role(
self,
"BrowserExecutionRole",
assumed_by=iam.ServicePrincipal("bedrock-agentcore.amazonaws.com"),
description="Execution role used by the custom AgentCore Browser.",
)
recording_bucket.grant_read_write(browser_execution_role)
2.4 カスタムBrowser
エージェントが使用するためのブラウザを定義します。これはメソッドとして定義します。(メソッド化するかは好みの問題。)
def bedrock_agent_core_browser(
scope: Construct,
*,
recording_bucket: s3.Bucket,
execution_role: iam.Role,
) -> bedrock_agentcore.CfnBrowserCustom:
browser = bedrock_agentcore.CfnBrowserCustom(
scope,
"CustomBrowser",
name="AgentCoreBrowserDemo",
description="Custom browser used by the Strands AgentCore Browser demo.",
execution_role_arn=execution_role.role_arn,
network_configuration=(
bedrock_agentcore.CfnBrowserCustom.BrowserNetworkConfigurationProperty(
network_mode="PUBLIC",
)
),
recording_config=bedrock_agentcore.CfnBrowserCustom.RecordingConfigProperty(
enabled=True,
s3_location=bedrock_agentcore.CfnBrowserCustom.S3LocationProperty(
bucket=recording_bucket.bucket_name,
prefix="browser-recordings/",
),
),
tags={
"Application": "agentcore-browser-demo",
"Purpose": "browser-validation",
},
)
browser.node.add_dependency(execution_role)
return browser
AgentCoreBrowserDemoStackクラス内に定義する必要はありません。
network_modeをPUBLICを指定することで、Browserから外部Webサイトへアクセスできます。
録画を有効化し、実行中はAWSコンソールのLive Viewで操作を確認できます。
3. Strands Agentを実装する
import os
import boto3
from strands import Agent
from strands.models import BedrockModel
from strands_tools.browser import AgentCoreBrowser
DEFAULT_REGION = "ap-northeast-1"
DEFAULT_MODEL_ID = "jp.anthropic.claude-sonnet-4-6"
SYSTEM_PROMPT = """You are a web-browsing assistant.
Use the browser tool whenever the user asks you to visit, inspect, search, or
interact with a website. Report what you actually observed. Do not claim that
you opened a page or performed an action unless the browser tool succeeded.
Avoid submitting purchases, publishing content, or changing account settings
unless the user explicitly requests the action.
"""
def get_region() -> str:
return (
os.environ.get("AWS_REGION")
or os.environ.get("AWS_DEFAULT_REGION")
or DEFAULT_REGION
)
def get_model_id() -> str:
return os.environ.get("BEDROCK_MODEL_ID", DEFAULT_MODEL_ID)
def get_browser_id() -> str:
browser_id = os.environ.get("AGENTCORE_BROWSER_ID", "").strip()
if not browser_id:
raise RuntimeError("AGENTCORE_BROWSER_ID is required")
return browser_id
def create_agent() -> Agent:
region = get_region()
model = BedrockModel(
model_id=get_model_id(),
boto_session=boto3.Session(region_name=region),
)
browser_tool = AgentCoreBrowser(
region=region,
identifier=get_browser_id(),
session_timeout=900,
)
return Agent(
model=model,
tools=[browser_tool.browser],
system_prompt=SYSTEM_PROMPT,
callback_handler=None,
)
def extract_text(response: object) -> str:
message = getattr(response, "message", None)
if not isinstance(message, dict):
return str(response)
content = message.get("content") or []
texts = [
item["text"].strip()
for item in content
if isinstance(item, dict) and isinstance(item.get("text"), str)
]
return "\n".join(text for text in texts if text) or str(response)
重要なのは次の部分です。
browser_tool = AgentCoreBrowser(
region=region,
identifier=get_browser_id(),
session_timeout=900,
)
Agent(tools=[browser_tool.browser])
CDKで作成したBrowser IDを使ってAgentCoreBrowserを生成し、browserをStrands Agentのツールとして登録します。
4. AgentCore Runtime APIを実装する
エージェントを呼び出すためのエンドポイント(@app.entrypoint)を定義します。
import logging
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from browser_agent.agent import create_agent, extract_text
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = BedrockAgentCoreApp()
@app.entrypoint
def invoke(payload: dict) -> dict:
prompt = payload.get("prompt")
if not isinstance(prompt, str) or not prompt.strip():
raise ValueError("payload.prompt must be a non-empty string")
logger.info("Invoking browser agent")
response = create_agent()(prompt.strip())
return {"response": extract_text(response)}
if __name__ == "__main__":
app.run()
Runtimeの入力は次の形式とします。
{
"prompt": "指定したWebサイトを開いて内容を要約してください"
}
BedrockAgentCoreAppがポート8080でHTTPサーバーを起動し、
AgentCore Runtimeに必要な/invocationsと/pingを提供します。
5. エージェントをDocker化する
実装したStrands Agentsをデプロイするために、Docker化します。
5-1. コンテナ側のPython依存
bedrock-agentcore>=1.3.0,<2.0.0
strands-agents>=1.0.0,<2.0.0
strands-agents-tools>=0.2.0,<1.0.0
boto3>=1.42.0
playwright>=1.52.0,<2.0.0
nest-asyncio>=1.6.0,<2.0.0
5-2. Dockerfile
DockerイメージをビルドするためのDockerfileを用意します。
FROM public.ecr.aws/docker/library/python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PORT=8080
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY browser_agent ./browser_agent
EXPOSE 8080
CMD ["python", "-m", "browser_agent.runtime_app"]
依存ファイルを先にコピーすることで、エージェントコードだけを変更した場合に
pipインストール層のDockerキャッシュが再利用されます。
5-3. Docker除外設定
イメージビルドする必要のないファイルは除外しておきます。
__pycache__/
*.py[cod]
.pytest_cache/
5-4. ローカルビルド
プロジェクトルートで実行します。
docker build \
--platform linux/arm64 \
-t agentcore-browser-demo:test \
agent
モジュールを読み込めることを確認します。
docker run --rm \
--platform linux/arm64 \
agentcore-browser-demo:test \
python -c \
"from browser_agent.runtime_app import app; from strands_tools.browser import AgentCoreBrowser; print(type(app).__name__, AgentCoreBrowser.__name__)"
6. ECRリポジトリとイメージ配布を実装する
ECRリポジトリを用意して、デプロイする処理を定義します。__init__関数内へ追記してください。
6-1. 専用ECRリポジトリ
ECRリポジトリを用意します。
image_repository = ecr.Repository(
self,
"AgentImageRepository",
repository_name="agentcore-browser-demo",
image_scan_on_push=True,
image_tag_mutability=ecr.TagMutability.IMMUTABLE,
lifecycle_rules=[
ecr.LifecycleRule(
description="Keep the ten most recent agent images",
max_image_count=10,
)
],
removal_policy=RemovalPolicy.DESTROY,
empty_on_delete=True,
)
6-2. ARM64イメージのビルド
image_asset = ecr_assets.DockerImageAsset(
self,
"AgentImageAsset",
directory=str(Path(__file__).resolve().parents[1] / "agent"),
platform=ecr_assets.Platform.LINUX_ARM64,
)
6.3 専用ECRへのコピー
image_tag = image_asset.asset_hash
runtime_image_uri = image_repository.repository_uri_for_tag(image_tag)
image_deployment = ecr_deployment.ECRDeployment(
self,
"DeployAgentImage",
src=ecr_deployment.DockerImageName(image_asset.image_uri),
dest=ecr_deployment.DockerImageName(runtime_image_uri),
)
DockerImageAssetはイメージをCDKアセット用ECRへPushします。
ECRDeploymentがそこから専用ECRへコピーします。
タグにはソース内容のハッシュを使用するため、コードが変更されると新しいタグになります。
7. Runtime用IAMロールを実装する
AgentCoreBrowserDemoStackクラスに次のメソッドを追加します。
def _create_runtime_role(
self,
*,
image_repository: ecr.Repository,
browser: bedrock_agentcore.CfnBrowserCustom,
) -> iam.Role:
runtime_role = iam.Role(
self,
"AgentRuntimeRole",
assumed_by=iam.ServicePrincipal(
"bedrock-agentcore.amazonaws.com",
conditions={
"StringEquals": {"aws:SourceAccount": Aws.ACCOUNT_ID},
"ArnLike": {
"aws:SourceArn": (
f"arn:{Aws.PARTITION}:bedrock-agentcore:"
f"{Aws.REGION}:{Aws.ACCOUNT_ID}:*"
)
},
},
),
)
image_repository.grant_pull(runtime_role)
runtime_role.add_to_policy(
iam.PolicyStatement(
actions=["ecr:GetAuthorizationToken"],
resources=["*"],
)
)
runtime_role.add_to_policy(
iam.PolicyStatement(
actions=[
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
"logs:PutLogEvents",
],
resources=["*"],
)
)
runtime_role.add_to_policy(
iam.PolicyStatement(
actions=[
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream",
],
resources=[
f"arn:{Aws.PARTITION}:bedrock:*::foundation-model/*",
(
f"arn:{Aws.PARTITION}:bedrock:{Aws.REGION}:"
f"{Aws.ACCOUNT_ID}:inference-profile/*"
),
],
)
)
runtime_role.add_to_policy(
iam.PolicyStatement(
actions=[
"bedrock-agentcore:StartBrowserSession",
"bedrock-agentcore:GetBrowserSession",
"bedrock-agentcore:ListBrowserSessions",
"bedrock-agentcore:StopBrowserSession",
"bedrock-agentcore:UpdateBrowserStream",
"bedrock-agentcore:ConnectBrowserAutomationStream",
"bedrock-agentcore:ConnectBrowserLiveViewStream",
],
resources=[browser.attr_browser_arn],
)
)
return runtime_role
__init__内でメソッドを呼び出します。
runtime_role = self._create_runtime_role(
image_repository=image_repository,
browser=browser,
)
このロールには以下を許可しています。
- ECRからのイメージ取得
- CloudWatch Logsへの出力
- Bedrockモデル呼び出し
- Browserセッション操作
- Automation StreamおよびLive View Streamへの接続
8. AgentCore Runtimeを作成する
AgentCoreBrowserDemoStack.__init__内に以下を記述します。
runtime = bedrock_agentcore.CfnRuntime(
self,
"AgentRuntime",
agent_runtime_name="AgentCoreBrowserDemoRuntime",
description="Strands agent that operates a custom AgentCore Browser.",
agent_runtime_artifact=(
bedrock_agentcore.CfnRuntime.AgentRuntimeArtifactProperty(
container_configuration=(
bedrock_agentcore.CfnRuntime.ContainerConfigurationProperty(
container_uri=runtime_image_uri,
)
)
)
),
network_configuration=(
bedrock_agentcore.CfnRuntime.NetworkConfigurationProperty(
network_mode="PUBLIC",
)
),
protocol_configuration="HTTP",
role_arn=runtime_role.role_arn,
environment_variables={
"AWS_REGION": self.region,
"BEDROCK_MODEL_ID": model_id,
"AGENTCORE_BROWSER_ID": browser.attr_browser_id,
},
lifecycle_configuration=(
bedrock_agentcore.CfnRuntime.LifecycleConfigurationProperty(
idle_runtime_session_timeout=900,
max_lifetime=3600,
)
),
)
runtime.node.add_dependency(image_deployment)
runtime.node.add_dependency(runtime_role)
runtime.node.add_dependency(browser)
環境変数の用途は次のとおり。
| 環境変数 | 用途 |
|---|---|
AWS_REGION |
BedrockとBrowserを呼び出すリージョン |
BEDROCK_MODEL_ID |
Strands Agentが使用するモデル |
AGENTCORE_BROWSER_ID |
操作対象のカスタムBrowser |
依存関係により、イメージコピーとBrowser作成の完了後にRuntimeを作成します。
CloudFormation Outputs
上記の記述の直後に以下を記述します。
CfnOutput(self, "AgentRuntimeArn", value=runtime.attr_agent_runtime_arn)
CfnOutput(self, "AgentRuntimeId", value=runtime.attr_agent_runtime_id)
CfnOutput(self, "CustomBrowserArn", value=browser.attr_browser_arn)
CfnOutput(self, "CustomBrowserId", value=browser.attr_browser_id)
CfnOutput(
self,
"AgentImageRepositoryUri",
value=image_repository.repository_uri,
)
CfnOutput(self, "AgentImageUri", value=runtime_image_uri)
CfnOutput(
self,
"BrowserRecordingBucketName",
value=recording_bucket.bucket_name,
)
9. テストする
9-1. CDKテスト
CDKコードの単体テストを記述します。
import aws_cdk as cdk
from aws_cdk.assertions import Match, Template
from infra.agentcore_browser_stack import AgentCoreBrowserDemoStack
def synth_template() -> Template:
app = cdk.App(context={"modelId": "test.model-id"})
stack = AgentCoreBrowserDemoStack(
app,
"TestStack",
env=cdk.Environment(
account="123456789012",
region="ap-northeast-1",
),
)
return Template.from_stack(stack)
def test_creates_custom_browser_with_recording() -> None:
template = synth_template()
template.has_resource_properties(
"AWS::BedrockAgentCore::BrowserCustom",
{
"Name": "AgentCoreBrowserDemo",
"NetworkConfiguration": {"NetworkMode": "PUBLIC"},
"RecordingConfig": {
"Enabled": True,
"S3Location": {
"Prefix": "browser-recordings/",
"Bucket": Match.any_value(),
},
},
},
)
実際のプロジェクトでは、このほかにECR、Runtime環境変数、IAM権限も検証します。
9-2. エージェント応答変換テスト
from types import SimpleNamespace
from agent.browser_agent.agent import extract_text
def test_extract_text_combines_text_blocks() -> None:
response = SimpleNamespace(
message={
"content": [
{"text": "first"},
{"toolUse": {"name": "browser"}},
{"text": "second"},
]
}
)
assert extract_text(response) == "first\nsecond"
9-3. テストコマンド
source .venv/bin/activate
pytest
cdk synth
10. デプロイして動作確認する
10-1. デプロイ
cdk deploy
別のモデルを使う場合はCDK contextで上書きします。
cdk deploy -c modelId=your-model-or-inference-profile-id
また、Dockerイメージのビルドもこのタイミングでやってくれます。
10-2. AWSコンソールでの確認
今回は、ブラウザでYahooの検索画面を読んでもらおうと思います。以下の操作をして、 ランタイムプレイグラウンド の画面へ遷移してください。
- Bedrock AgentCore コンソールへ遷移
- 左メニューの テスト セクション以下の ランタイムプレイグラウンド をクリック
- ランタイムエージェントで、今回作成したランタイム名を選択し、エンドポイントが DEFAULT となっていることを確認
では、入力欄に以下を入力します。
{"prompt": "ブラウザでYahooの検索画面を開いでください。"}
実行 ボタンをクリックしたら、以下のようにレスポンスが返ってくるはずです。(ちょっと見づらいけど、整形したら以下のようになります!)
検索ボックスの場所とかがちゃんと解釈できているので、ブラウザ操作はできていると判断できそうですね!
11. リソースを削除する
検証終了後は、無駄な料金支払いを抑えるためにも以下のコマンドを実行しておきましょう。
cdk destroy
ただし、以下の場合で、削除が失敗する可能性があります。
Browser削除がAlreadyExistsで失敗する場合
Browserを直接削除してから、CloudFormationの削除を再実行します。
aws bedrock-agentcore-control list-browsers \
--region ap-northeast-1
aws bedrock-agentcore-control delete-browser \
--browser-id <browser-id> \
--region ap-northeast-1
aws cloudformation delete-stack \
--stack-name AgentCoreBrowserDemoStack \
--region ap-northeast-1
上記はAWS CLIを使用して削除するコマンドです。AWS マネジメントコンソール上で削除することも可能です。
録画バケットが空でない場合
Browser削除後に最終録画が遅延書き込みされると、S3バケットの削除が失敗する場合があります。
検証用データであることを確認してから空にします。
aws s3 rm s3://<recording-bucket-name> \
--recursive \
--region ap-northeast-1
aws cloudformation delete-stack \
--stack-name AgentCoreBrowserDemoStack \
--region ap-northeast-1
aws cloudformation wait stack-delete-complete \
--stack-name AgentCoreBrowserDemoStack \
--region ap-northeast-1
まとめ
今回は、Strands Agents × Amazon Bedrock AgentCore × AWS CDK でAIにブラウザ操作させてみる検証を行いました。
ソースコードについては、以下のリポジトリを参照いただけますと幸いです。
以上
