🌐

Bedrock AgentCore Browser の CDK L1 Construct を試す

に公開

AgentCore Runtime/Code Interpreter の CDK L1 Construct の使い方をご紹介しました。

https://zenn.dev/aws_japan/articles/2025-09-25-agentcore-runtime-l1
https://zenn.dev/aws_japan/articles/agentcore-code-interpreter-l1

今回は AgentCore BrowserCDK L1 Construct を試してみます。

AgentCore Browserとは

AgentCore Browser は、隔離された環境で安全にブラウザによる Web アプリケーションの操作が行える機能です。セッションごとに一時環境が立ち上がり、操作が行われます
。主に Agent が人間向けの Web を操作することが想定されています。


(画像は Interact with web applications using Amazon Bedrock AgentCore Browser より引用)

作るもの

AgentCore Runtime 上にデプロイした Agent から AgentCore Browser により Web ブラウザを操作を行います。

Agent に対しては AgentCore Browser を使用する Tool を設定します。

事前準備

ディレクトリ構成

ここは前回記事と全く同じです。

.
├── bin
│   └── cdk-bedrock-agentcore.ts
├── cdk.json
├── lib
│   ├── app_tools
│   └── cdk-buitin-tools-stack.ts
├── package-lock.json
├── package.json
├── README.md
├── script
│   └── generate_csv.py
└── tsconfig.json

Agent の実装

AgentCore Browser 連携の実装は、シンプルな Strands Agents ToolsAgentCoreBrowser を使用します。

この Tool は Bedrock AgentCore SDK を使用して実装されており、AgentCore Browser 連携を行うのみあれば、既存の Agent に数行追加するだけで実装が可能です。

この実装はこちらの AWS Docs にも記載されています。

Strands Agents Tools を追加した上で、AgentCoreBrowser の import および Agent への Tool 設定を行います(以下は uv を使用する場合)

uv add 'strands-agents-tools[agent_core_browser]'
+ from strands_tools.browser import AgentCoreBrowser

+ gent_core_browser = AgentCoreBrowser(region=REGION, identifier=BROWSER_ID) # `identifier` で作成した AgentCore Browser リソースを渡す(未設定の場合はデフォルトで存在するリソースが使用される)
  
agent = Agent(
    tools=[
        execute_command,
+       agent_core_browser.browser, # tool として Browser を渡す
    ],
    system_prompt=SYSTEM_PROMPT,
)

Agent の実装の全体は以下です。差分は前回記事からの追加点です。

Agent の実装例
lib/app_tools/agent.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Dict, Any
from datetime import datetime, timezone
from strands import Agent, tool
+ from strands_tools.browser import AgentCoreBrowser
import os
import json
import boto3


app = FastAPI(title="Strands Agent Server", version="1.0.0")

CODE_INTERPRETER_ID = os.environ["CODE_INTERPRETER_ID"]
BROWSER_ID = os.environ["BROWSER_ID"]
S3_BUCKET_NAME = os.environ["S3_BUCKET_NAME"]

REGION = "us-west-2"

# Global code interpreter client
bedrock_agentcore_client = boto3.client(
    "bedrock-agentcore",
    region_name=REGION,
    endpoint_url=f"https://bedrock-agentcore.{REGION}.amazonaws.com",
)

# Validation-focused system prompt
SYSTEM_PROMPT = """You are a helpful assistant"""


class InvocationRequest(BaseModel):
    input: Dict[str, Any]


class InvocationResponse(BaseModel):
    output: Dict[str, Any]


@tool
def execute_command():
    """Create sample csv to Amazon S3 Bucket."""
    session_response = bedrock_agentcore_client.start_code_interpreter_session(
        codeInterpreterIdentifier=CODE_INTERPRETER_ID,
        name="combined-test-session",
        sessionTimeoutSeconds=1800,
    )
    session_id = session_response["sessionId"]
    print(f"Created session ID: {session_id}")

    print(f"Downloading CSV generation script from S3")
    command_to_execute = f"aws s3 cp s3://{S3_BUCKET_NAME}/generate_csv.py ."
    response = bedrock_agentcore_client.invoke_code_interpreter(
        codeInterpreterIdentifier=CODE_INTERPRETER_ID,
        sessionId=session_id,
        name="executeCommand",
        arguments={"command": command_to_execute},
    )

    for event in response["stream"]:
        print(json.dumps(event["result"], default=str, indent=2))

    print(f"Executing the CSV generation script")
    response = bedrock_agentcore_client.invoke_code_interpreter(
        codeInterpreterIdentifier=CODE_INTERPRETER_ID,
        sessionId=session_id,
        name="executeCommand",
        arguments={"command": "python generate_csv.py 5 10"},
    )

    for event in response["stream"]:
        print(json.dumps(event["result"], default=str, indent=2))

    print(f"Uploading generated artifact to S3")
    command_to_execute = (
        f"aws s3 cp generated_data.csv s3://{S3_BUCKET_NAME}/output_artifacts/"
    )
    response = bedrock_agentcore_client.invoke_code_interpreter(
        codeInterpreterIdentifier=CODE_INTERPRETER_ID,
        sessionId=session_id,
        name="executeCommand",
        arguments={"command": command_to_execute},
    )

    for event in response["stream"]:
        print(json.dumps(event["result"], default=str, indent=2))

    print(f"Stopping the code interpreter session")
    stop_response = bedrock_agentcore_client.stop_code_interpreter_session(
        codeInterpreterIdentifier=CODE_INTERPRETER_ID, sessionId=session_id
    )

    return stop_response


@app.post("/invocations", response_model=InvocationResponse)
async def invoke_agent(request: InvocationRequest):
    try:
        user_message = request.input.get("prompt", "")
        if not user_message:
            raise HTTPException(
                status_code=400,
                detail="No prompt found in input. Please provide a 'prompt' key in the input.",
            )

+        agent_core_browser = AgentCoreBrowser(region=REGION, identifier=BROWSER_ID)
        
        agent = Agent(
            tools=[
                execute_command,
+                agent_core_browser.browser,
            ],
            system_prompt=SYSTEM_PROMPT,
        )

        result = agent(user_message)
        response = {
            "message": result.message,
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "model": "strands-agent",
        }

        return InvocationResponse(output=response)

    except Exception as e:
        raise HTTPException(
            status_code=500, detail=f"Agent processing failed: {str(e)}"
        )


@app.get("/ping")
async def ping():
    return {"status": "healthy"}


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=8080)

CDK 実装内容

Stack(lib/cdk-buitin-tools-stack.ts)に実装しています。全量は以下です。差分は前回記事からの追加点です。

CDK 実装内容の全量
lib/cdk-buitin-tools-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment';
import { DockerImageAsset, Platform } from 'aws-cdk-lib/aws-ecr-assets';
import * as path from 'path';
import * as agentcore from 'aws-cdk-lib/aws-bedrockagentcore';

export class CdkBuiltInToolsStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    /**
     * S3 Bucket for script and output
     */
    const bucket = new s3.Bucket(this, 'CodeInterpreterBucket', {
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
    });

    // Deploy script to S3 bucket
    new s3deploy.BucketDeployment(this, 'DeployCodeInterpreterAssets', {
      sources: [s3deploy.Source.asset('./script')],
      destinationBucket: bucket,
    });

    /**
     *  AgentCore Code Interpreter
     */
    const codeInterpreterExecutionRole = new iam.Role(this, 'CodeInterpreterExecutionRole', {
      assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com'),
      description: 'Execution role for AgentCore Code Interpreter',
    });

    bucket.grantReadWrite(codeInterpreterExecutionRole);

    const codeInterpreter = new agentcore.CfnCodeInterpreterCustom(this, 'CodeInterpreter', {
      name: 'MyCodeInterpreter',
      executionRoleArn: codeInterpreterExecutionRole.roleArn,
      networkConfiguration: {
        networkMode: 'SANDBOX', // 'PUBLIC' or 'SANDBOX' を設定
      },
    });

+    /**
+     * S3 Bucket for Browser Tool Recording
+     */
+    const recordingBucket = new s3.Bucket(this, 'RecordingBucket', {
+      removalPolicy: cdk.RemovalPolicy.DESTROY,
+      autoDeleteObjects: true,
+    });

    const region = cdk.Stack.of(this).region;
    const accountId = cdk.Stack.of(this).account;

+    const browserExecutionRole = new iam.Role(this, 'BrowserExecutionRole', {
+      assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com', {
+        conditions: {
+          StringEquals: {
+            'aws:SourceAccount': accountId,
+          },
+          ArnLike: {
+            'aws:SourceArn': `arn:aws:bedrock-agentcore:${region}:${accountId}:*`,
+          },
+        },
+      }),
+      description: 'Execution role for AgentCore Browser',
+    });

+    recordingBucket.grantReadWrite(browserExecutionRole);

+    /**
+     * AgentCore Browser
+     */
+    const browser = new agentcore.CfnBrowserCustom(this, 'Browser', {
+      name: 'MyBrowser',
+      networkConfiguration: {
+        networkMode: 'PUBLIC',
+      },
+      recordingConfig: {
+        enabled: true,
+        s3Location: {
+          bucket: recordingBucket.bucketName,
+          prefix: 'browser-recordings/',
+        },
+      },
+      executionRoleArn: browserExecutionRole.roleArn,
+    });

    /**
     * AgentCore Runtime
     */
    // Create Docker Image Asset
    const dockerImageAsset = new DockerImageAsset(this, 'DockerImageAsset', {
      directory: path.join(__dirname, 'app_tools'),
      platform: Platform.LINUX_ARM64,
      file: 'Dockerfile',
    });

    // Create IAM role for Bedrock AgentCore with required policies
    const agentCoreRole = new iam.Role(this, 'BedrockAgentCoreRole', {
      assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com'),
      description: 'IAM role for Bedrock AgentCore Runtime',
    });

    agentCoreRole.addToPolicy(
      new iam.PolicyStatement({
        sid: 'ECRImageAccess',
        effect: iam.Effect.ALLOW,
        actions: ['ecr:BatchGetImage', 'ecr:GetDownloadUrlForLayer'],
        resources: [`arn:aws:ecr:${region}:${accountId}:repository/*`],
      }),
    );

    agentCoreRole.addToPolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: ['logs:DescribeLogStreams', 'logs:CreateLogGroup'],
        resources: [`arn:aws:logs:${region}:${accountId}:log-group:/aws/bedrock-agentcore/runtimes/*`],
      }),
    );

    agentCoreRole.addToPolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: ['logs:DescribeLogGroups'],
        resources: [`arn:aws:logs:${region}:${accountId}:log-group:*`],
      }),
    );

    agentCoreRole.addToPolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: ['logs:CreateLogStream', 'logs:PutLogEvents'],
        resources: [`arn:aws:logs:${region}:${accountId}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*`],
      }),
    );

    agentCoreRole.addToPolicy(
      new iam.PolicyStatement({
        sid: 'ECRTokenAccess',
        effect: iam.Effect.ALLOW,
        actions: ['ecr:GetAuthorizationToken'],
        resources: ['*'],
      }),
    );

    agentCoreRole.addToPolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: [
          'xray:PutTraceSegments',
          'xray:PutTelemetryRecords',
          'xray:GetSamplingRules',
          'xray:GetSamplingTargets',
        ],
        resources: ['*'],
      }),
    );

    agentCoreRole.addToPolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: ['cloudwatch:PutMetricData'],
        resources: ['*'],
        conditions: {
          StringEquals: {
            'cloudwatch:namespace': 'bedrock-agentcore',
          },
        },
      }),
    );

    agentCoreRole.addToPolicy(
      new iam.PolicyStatement({
        sid: 'GetAgentAccessToken',
        effect: iam.Effect.ALLOW,
        actions: [
          'bedrock-agentcore:GetWorkloadAccessToken',
          'bedrock-agentcore:GetWorkloadAccessTokenForJWT',
          'bedrock-agentcore:GetWorkloadAccessTokenForUserId',
        ],
        resources: [
          `arn:aws:bedrock-agentcore:${region}:${accountId}:workload-identity-directory/default`,
          `arn:aws:bedrock-agentcore:${region}:${accountId}:workload-identity-directory/default/workload-identity/agentName-*`,
        ],
      }),
    );

    agentCoreRole.addToPolicy(
      new iam.PolicyStatement({
        sid: 'BedrockModelInvocation',
        effect: iam.Effect.ALLOW,
        actions: ['bedrock:InvokeModel', 'bedrock:InvokeModelWithResponseStream'],
        resources: ['arn:aws:bedrock:*::foundation-model/*', `arn:aws:bedrock:${region}:${accountId}:*`],
      }),
    );

    // AgentCore Code Interpreter へのアクセス許可
    agentCoreRole.addToPolicy(
      new iam.PolicyStatement({
        sid: 'InvokeAgentCoreCodeInterpreter',
        effect: iam.Effect.ALLOW,
        actions: [
          'bedrock-agentcore:StartCodeInterpreterSession',
          'bedrock-agentcore:InvokeCodeInterpreter',
          'bedrock-agentcore:StopCodeInterpreterSession',
        ],
        resources: [codeInterpreter.attrCodeInterpreterArn],
      }),
    );

+    // AgentCore Code Browser へのアクセス許可
+    agentCoreRole.addToPolicy(
+      new iam.PolicyStatement({
+        sid: 'BrowserToolAccess',
+        effect: iam.Effect.ALLOW,
+        actions: [
+          'bedrock-agentcore:CreateBrowser',
+          'bedrock-agentcore:ListBrowsers',
+          'bedrock-agentcore:GetBrowser',
+          'bedrock-agentcore:DeleteBrowser',
+          'bedrock-agentcore:StartBrowserSession',
+          'bedrock-agentcore:ListBrowserSessions',
+          'bedrock-agentcore:GetBrowserSession',
+          'bedrock-agentcore:StopBrowserSession',
+          'bedrock-agentcore:UpdateBrowserStream',
+          'bedrock-agentcore:ConnectBrowserAutomationStream',
+          'bedrock-agentcore:ConnectBrowserLiveViewStream',
+        ],
+        resources: [`${browser.attrBrowserArn}`],
+      }),
+    );

    // Bedrock AgentCore Runtime
    const runtime = new agentcore.CfnRuntime(this, 'AgentCoreRuntimeWithBuiltInTools', {
      agentRuntimeName: 'MyAgentRuntimWithBuiltInTools',
      agentRuntimeArtifact: {
        containerConfiguration: {
          containerUri: dockerImageAsset.imageUri,
        },
      },
      networkConfiguration: {
        networkMode: 'PUBLIC',
      },
      roleArn: agentCoreRole.roleArn,
      protocolConfiguration: 'HTTP',
      environmentVariables: {
        CODE_INTERPRETER_ID: codeInterpreter.attrCodeInterpreterId,
+        BROWSER_ID: browser.attrBrowserId,
        S3_BUCKET_NAME: bucket.bucketName,
      },
    });

    // 依存関係を定義
    runtime.node.addDependency(agentCoreRole);

    // Runtime Endpoint
    new agentcore.CfnRuntimeEndpoint(this, 'RuntimeWithBuiltInToolsEndpoint', {
      agentRuntimeId: runtime.attrAgentRuntimeId,
      agentRuntimeVersion: runtime.attrAgentRuntimeVersion,
      name: 'MyAgentRuntimeEndpoint',
    });
  }
}

以下詳細を見ていきます。

S3 バケット

AgentCore Browser の Recording 情報を格納する S3 バケットを作成します。

lib/cdk-buitin-tools-stack.ts

/**
 * S3 Bucket for Browser Tool Recording
 */
const recordingBucket = new s3.Bucket(this, 'RecordingBucket', {
  removalPolicy: cdk.RemovalPolicy.DESTROY,
  autoDeleteObjects: true,
});

AgentCore Browser

AgentCore Browser を作成します。Execution Role を作成し、S3 バケットの読み取り/書き込み権限を付与した上で定義します。

権限付与は簡略化のため grantReadWrite を使用していますが、AWS docs で必要とされている Action よりも若干広いものが許可されます。そのため必要に応じて権限設定は行ってください

lib/cdk-buitin-tools-stack.ts
/**
 * AgentCore Browser
 */
const browserExecutionRole = new iam.Role(this, 'BrowserExecutionRole', {
  assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com', {
    conditions: {
      StringEquals: {
        'aws:SourceAccount': accountId,
      },
      ArnLike: {
        'aws:SourceArn': `arn:aws:bedrock-agentcore:${region}:${accountId}:*`,
      },
    },
  }),
  description: 'Execution role for AgentCore Browser',
});

recordingBucket.grantReadWrite(browserExecutionRole);

const browser = new agentcore.CfnBrowserCustom(this, 'Browser', {
  name: 'MyBrowser',
  networkConfiguration: {
    networkMode: 'PUBLIC',  // 現状は PUBLIC 固定値
  },

  // Recording の設定を実施
  recordingConfig: {
    enabled: true,
    s3Location: {
      bucket: recordingBucket.bucketName,
      prefix: 'browser-recordings/',
    },
  },
  executionRoleArn: browserExecutionRole.roleArn,  // 実行ロールを設定
});

AgentCore Runtime

こちらも前回記事からの差分のみ記載します。

まず AgentCore Runtime -> AgentCore Browser へのアクセスが発生するため、追加のポリシーを付与します。こちらは AWS Docs に記載されている権限を付与します。

lib/cdk-buitin-tools-stack.ts
// AgentCore Code Browser へのアクセス許可
agentCoreRole.addToPolicy(
  new iam.PolicyStatement({
    sid: 'BrowserToolAccess',
    effect: iam.Effect.ALLOW,
    actions: [
      'bedrock-agentcore:CreateBrowser',
      'bedrock-agentcore:ListBrowsers',
      'bedrock-agentcore:GetBrowser',
      'bedrock-agentcore:DeleteBrowser',
      'bedrock-agentcore:StartBrowserSession',
      'bedrock-agentcore:ListBrowserSessions',
      'bedrock-agentcore:GetBrowserSession',
      'bedrock-agentcore:StopBrowserSession',
      'bedrock-agentcore:UpdateBrowserStream',
      'bedrock-agentcore:ConnectBrowserAutomationStream',
      'bedrock-agentcore:ConnectBrowserLiveViewStream',
    ],
    resources: [`${browser.attrBrowserArn}`],
  }),
);

あとは環境変数で AgentCore Browser の ID を渡すのみです.

lib/cdk-buitin-tools-stack.ts
// Bedrock AgentCore Runtime
const runtime = new agentcore.CfnRuntime(this, 'AgentCoreRuntimeWithBuiltInTools', {

    //...中略

    // 環境変数を設定
    environmentVariables: {
        BROWSER_ID: browser.attrBrowserId,
    },
});

動作確認

cdk deploy を行い動作確認を行います。

マネコンから AgentCore Runtime 上にデプロイした Agent に対してブラウザアクセスを指示します。応答は正常なものが返ってきました。

マネコンから AgentCore Browser のセッションを覗いてみると、指示通り Web ページにアクセスがされていました。

また S3 バケットにも問題なく Recording 情報が格納され地ました。

おわりに

AgentCore Browser も AWS CDK から簡単にリソース作成が行えます。

AgentCore Code Interpreter と同様に L2 Construct 化も進行中のため、今後さらに使いやすくなるでしょう。

アマゾン ウェブ サービス ジャパン (有志)

Discussion