[Amazon Bedrock AgentCore] AgentCore Browser をAIエージェントで操作してみた
はじめに
こんにちは、スーパーマーケットのラ・ムーを推しているコンサルティング部の神野です。
ラ・ムーに併設されているPAKUPAKUでいつも100円たこ焼きを思わず2個買ってしまいます。安くて美味しいですよね。
皆さんはAmazon Bedrock AgentCoreのBuilt-in Toolsである「AgentCore Browser」を使っていますか?
私は今回、このAgentCore Browserを操作するブラウザ操作系のAIエージェントBrowser Useで「自然言語→ブラウザ操作→結果取得」の一連の流れを試してみました!
下記ハンズオンではNova Act SDKとBrowser Useを使うケースが紹介されていて、当初はNova Actを使う予定でしたが、日本の私の居住地域ではAPIキー発行ができず、泣く泣く断念しました。そこで今回はBrowser Useを使ってみることにしました!
AgentCore Browserとは
AgentCore Browserは、AIエージェントに安全かつマネージドなブラウザー実行環境を提供します。
モデルやフレームワークに依存せず、自然言語からの指示でウェブページの検索・遷移・フォーム操作など、実ブラウザに準じた操作を行えます。AWS 公式ハンズオンで記載されているアーキテクチャ図は下記になります。
ざっくりと流れを見るとエージェントがツール経由で、AgentCore Browserを活用して、色々とブラウザ操作できるようになるイメージですね!そして、その結果を受け取ってユーザーからの問い合わせに対する回答をAgentが行う流れです。
今回実装する構成
今回は次の構成で試します。
ざっと特徴は下記です。
- AgentCore RuntimeにStrands Agentsを実装
- AgentCore Browserを起動(セッション生成)
- Browser-Useで自然言語タスクを実行
- 結果テキストをStrandsの応答として返却
AIエージェントからブラウザ操作を行う一連の処理を実装していきます!
前提条件
- Python 3.12
- AWS CLI 2.28
- AWSアカウント(us-west-2リージョン)
- Bedrockモデル有効化
- 今回は
anthropic.claude-3-5-haiku-20241022-v1:0
とanthropic.claude-3-5-sonnet-20241022-v2:0
を使用します
- 今回は
なお、本記事では、デプロイやIAMまわりの手順は割愛し、Strandsの実装とテストにフォーカスします。環境構築やデプロイ方法などの全体像はGitHubをご参照ください
実装手順
Strands Agentsの実装
次の例では、@tool
で定義した use_browser
を通じて、AgentCore Browserセッションを開始し、Browser Use の BrowserSession
/Agent
を使って自然言語タスクを実行します。ブラウザはAWS側の隔離環境で起動します。
実装は公式ハンズオンのサンプルコードを参考にしました。こちらも必要に応じてご参照ください。
コード全量
from typing import Dict, Any
import os
import logging
from strands import Agent, tool
from strands.models import BedrockModel
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from bedrock_agentcore.tools.browser_client import BrowserClient
from browser_use import Agent as BrowserUseAgent
from browser_use.browser.session import BrowserSession as BU_BrowserSession
from browser_use.browser import BrowserProfile as BU_BrowserProfile
from langchain_aws import ChatBedrockConverse
from playwright._impl._errors import Error as PlaywrightError
os.environ['UVICORN_TIMEOUT_KEEP_ALIVE'] = '28800'
os.environ['UVICORN_TIMEOUT_GRACEFUL_SHUTDOWN'] = '28800'
app = BedrockAgentCoreApp()
# ログ設定(LOG_LEVEL 環境変数で制御: DEBUG/INFO/WARNING/ERROR)
_log_level = os.environ.get("LOG_LEVEL", "INFO").upper()
logging.basicConfig(
level=getattr(logging, _log_level, logging.INFO),
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("browser_agent")
import contextlib
SYSTEM_PROMPT = """あなたはWeb自動化のアシスタントです。
原則:
1. ユーザーの指示を正確に読み取ってください
2. Web上の操作はrun_browser_taskツールを使って実行してください
3. 実施した操作と結果を簡潔に説明してください
4. CAPTCHAなど人間による検証が必要な場合は、次に取るべき行動を明示してください
ツールの使い方:
- 検索が必要な場合は、ユーザー入力から「短い検索語(名詞句)」を抽出してください。
- use_browserツールを呼び出す際、instruction引数には検索語のみを渡してください(例: 「スーパーマーケット」)。
- 説明文や敬語、語尾(〜を検索してください 等)は渡さないでください。
- サイト内検索を行う場合は、ページ内の検索テキストボックスと検索ボタンを必ず使用し、アドレスバーや外部検索エンジンは使用しないでください。
- 例: Yahoo! JAPANトップページ(https://www.yahoo.co.jp/)では、ページ内の検索ボックスに直接入力し、同ページの検索ボタンで検索を実行してください(外部検索は禁止)。
常に簡潔で有用な結果を返してください。
"""
@tool
async def run_browser_task(instruction: str, starting_page: str = "https://www.yahoo.co.jp/") -> str:
"""Execute web automation steps using AgentCore Browser and Browser-Use SDK.
instruction: Natural language instruction (e.g., "macbookを検索して最初の商品の詳細を抽出してください").
starting_page: Initial URL to open.
"""
logger.info("use_browser start: starting_page=%s", starting_page)
client = BrowserClient(region="us-west-2")
bu_session = None
try:
client.start()
ws_url, headers = client.generate_ws_headers()
logger.info("browser session created (region=us-west-2)")
logger.info("cdp ws_url: %s", ws_url[:100] + "..." if len(ws_url) > 100 else ws_url)
profile = BU_BrowserProfile(
headers=headers,
timeout=180000,
)
bu_session = BU_BrowserSession(
cdp_url=ws_url,
browser_profile=profile,
)
logger.info("Starting browser session...")
await bu_session.start()
logger.info("Browser session started successfully")
bedrock_chat = ChatBedrockConverse(
model_id="anthropic.claude-3-5-sonnet-20241022-v2:0",
region_name="us-west-2"
)
query = instruction.strip() if instruction else "ラ・ムー"
task = (
f"最初に、ブラウザツールの検索/URLバーに『{starting_page}』を入力してYahoo! JAPANトップへ移動してください。\n"
f"次に、ページ内の検索テキストボックスに『{query}』と直接入力し、同ページの検索ボタンをクリックして検索を実行してください。\n"
f"要件:\n"
f"- 最初の遷移のみ、ブラウザツールの検索/URLバーを使用して {starting_page} に移動する\n"
f"- 以降は外部検索エンジン(Yahoo! JAPAN等)やアドレスバー検索は使わない\n"
f"- ページの検索ボックスと検索ボタンのみを使用\n"
f"- ページの読み込み完了を待ってから操作\n"
f"- 検索結果の上位を確認し、日本語で特徴を3点に要約\n"
)
browser_use_agent = BrowserUseAgent(
task=task,
llm=bedrock_chat,
browser_session=bu_session,
)
logger.info("running Browser-Use task: %s", task[:100] + "...")
result = await browser_use_agent.run()
return result
finally:
if bu_session:
with contextlib.suppress(Exception):
await bu_session.close()
logger.info("Browser session closed")
with contextlib.suppress(Exception):
client.stop()
logger.info("Browser client stopped")
@app.entrypoint
async def browser_agent(payload: Dict[str, Any], context) -> Dict[str, Any]:
"""
Bedrock AgentCore Runtimeのエントリポイント
contextパラメータを受け取り、適切な形式でレスポンスを返す
"""
user_input = payload.get("prompt", "")
model = BedrockModel(
model_id="anthropic.claude-3-5-haiku-20241022-v1:0",
params={"max_tokens": 2048, "temperature": 0.2},
region="us-west-2",
read_timeout=600,
)
agent = Agent(
system_prompt=SYSTEM_PROMPT,
model=model,
tools=[run_browser_task],
)
try:
result = agent(user_input)
print(f"エージェント応答: {result}")
return result
except Exception as e:
logger.error("Error in browser_agent: %s", str(e), exc_info=True)
# エラーでも適切な形式で返す
return {
"output": {
"error": str(e),
"instruction": user_input,
"message": f"エラーが発生しました: {str(e)}"
}
}
if __name__ == "__main__":
app.run()
ポイントごとに見ていきます。
まずシステムプロンプトです。
ユーザーからの質問はツールrun_browser_task
を使って調査してねと指示しておきます。
SYSTEM_PROMPT = """あなたはWeb自動化のアシスタントです。
原則:
1. ユーザーの指示を正確に読み取ってください
2. Web上の操作はrun_browser_taskツールを使って実行してください
3. 実施した操作と結果を簡潔に説明してください
4. CAPTCHAなど人間による検証が必要な場合は、次に取るべき行動を明示してください
ツールの使い方:
- 検索が必要な場合は、ユーザー入力から「短い検索語(名詞句)」を抽出してください。
- use_browserツールを呼び出す際、instruction引数には検索語のみを渡してください(例: 「スーパーマーケット」)。
- 説明文や敬語、語尾(〜を検索してください 等)は渡さないでください。
- サイト内検索を行う場合は、ページ内の検索テキストボックスと検索ボタンを必ず使用し、アドレスバーや外部検索エンジンは使用しないでください。
- 例: Yahoo! JAPANトップページ(https://www.yahoo.co.jp/)では、ページ内の検索ボックスに直接入力し、同ページの検索ボタンで検索を実行してください(外部検索は禁止)。
常に簡潔で有用な結果を返してください。
"""
run_browser_task
は@tool
で登録します。Strands Agentsからは推論の途中で必要に応じてこのツールが呼び出されます。
@tool
async def run_browser_task(instruction: str, starting_page: str = "https://www.yahoo.co.jp/") -> str:
"""Execute web automation steps using AgentCore Browser and Browser-Use SDK.
instruction: Natural language instruction (e.g., "macbookを検索して最初の商品の詳細を抽出してください").
starting_page: Initial URL to open.
"""
logger.info("use_browser start: starting_page=%s", starting_page)
generate_ws_headers
でブラウザのWebSocket URLとヘッダが得られます。bu_session
でブラウザとのセッションを開始します。
client = BrowserClient(region="us-west-2")
bu_session = None
try:
client.start()
ws_url, headers = client.generate_ws_headers()
logger.info("browser session created (region=us-west-2)")
logger.info("cdp ws_url: %s", ws_url[:100] + "..." if len(ws_url) > 100 else ws_url)
profile = BU_BrowserProfile(
headers=headers,
timeout=180000,
)
bu_session = BU_BrowserSession(
cdp_url=ws_url,
browser_profile=profile,
)
await bu_session.start()
セッション情報をエージェントを渡して、ブラウザを操作します。
エージェントにはYahoo! JAPANにアクセスして、Yahoo! Japanのトップページ内の検索テキストボックスから検索し、内容を要約して伝えてと指示します。
bedrock_chat = ChatBedrockConverse(
model_id="anthropic.claude-3-5-sonnet-20241022-v2:0",
region_name="us-west-2"
)
query = instruction.strip() if instruction else "ラ・ムー"
task = (
f"最初に、ブラウザツールの検索/URLバーに『{starting_page}』を入力してYahoo! JAPANトップへ移動してください。\n"
f"次に、ページ内の検索テキストボックスに『{query}』と直接入力し、同ページの検索ボタンをクリックして検索を実行してください。\n"
f"要件:\n"
f"- 最初の遷移のみ、ブラウザツールの検索/URLバーを使用して {starting_page} に移動する\n"
f"- 以降は外部検索エンジン(Yahoo! JAPAN等)やアドレスバー検索は使わない\n"
f"- ページの検索ボックスと検索ボタンのみを使用\n"
f"- ページの読み込み完了を待ってから操作\n"
f"- 検索結果の上位を確認し、日本語で特徴を3点に要約\n"
)
browser_use_agent = BrowserUseAgent(
task=task,
llm=bedrock_chat,
browser_session=bu_session,
)
logger.info("running Browser-Use task: %s", task[:100] + "...")
result = await browser_use_agent.run()
return result
動作確認
AgentCore Runtimeにデプロイ済みのエージェントに対して、簡単な問い合わせを投げます。
コード全量
"""
デプロイ済みのBrowserエージェントを呼び出すテストスクリプト
"""
import boto3
import json
from botocore.config import Config
def invoke_browser_agent():
agent_arn = "arn:aws:bedrock-agentcore:us-west-2:YOUR_ACCOUNT_ID:runtime/browser_agent-XXXXXXXXXX"
if "YOUR_ACCOUNT_ID" in agent_arn:
print("エラー: agent_arnを実際の値に置き換えてください")
return None
config = Config(
read_timeout=600,
connect_timeout=300,
retries={'max_attempts': 2}
)
client = boto3.client("bedrock-agentcore", region_name="us-west-2", config=config)
queries = [
"ラ・ムー と検索して特徴を教えてください",
]
for i, query in enumerate(queries, 1):
print(f"\n{'='*60}")
print(f"テスト {i}: {query}")
print(f"{'='*60}")
payload = json.dumps({"prompt": query}).encode("utf-8")
try:
response = client.invoke_agent_runtime(
agentRuntimeArn=agent_arn,
qualifier="DEFAULT",
payload=payload,
contentType="application/json",
accept="application/json",
)
if response.get("contentType") == "application/json":
content = []
for chunk in response.get("response", []):
content.append(chunk.decode("utf-8"))
try:
result = json.loads("".join(content))
print("エージェント応答:")
print(result)
except json.JSONDecodeError:
print("Raw応答:")
raw_content = "".join(content)
print(raw_content)
else:
print(f"予期しないContent-Type: {response.get('contentType')}")
print(f"レスポンス: {response}")
except Exception as e:
print(f"エラー: {e}")
if hasattr(e, "response"):
error_message = e.response.get("Error", {}).get("Message", "No message")
print(f"エラーメッセージ: {error_message}")
if __name__ == "__main__":
invoke_browser_agent()
ポイントごとに見ていきます。
まず、AgentCore RuntimeのARNを差し替えます。
デプロイ出力で得た値を設定してください。
agent_arn = "arn:aws:bedrock-agentcore:us-west-2:YOUR_ACCOUNT_ID:runtime/YOUR_AGENT_ID"
AIエージェントの操作が長くread_timeout
エラーが表示されたので、600
に設定しました。
config = Config(
read_timeout=600,
connect_timeout=300,
retries={'max_attempts': 2}
)
問い合わせ内容は日本語で問題ありません。大好きなラ・ムーについて聞いてみます!
queries = [
"ラ・ムーと検索して特徴を教えてください",
]
実行します!!
python test_invoke.py
返信がきました!
============================================================
テスト 1: ラ・ムー と検索して特徴を教えてください
============================================================
エージェント応答:
検索結果から、ラ・ムーについて以下の特徴を見つけました:
1. 企業情報:
- 大黒天物産株式会社が運営するスーパーマーケットチェーン
- 本社は岡山県倉敷市に所在
2. 店舗の特徴:
- キャッチフレーズは「驚きの安さ!大フロアーの快適さ!24時間の便利さ!」
- 24時間営業を行っている
- 大型の店舗フロアを持つ
3. 店舗展開:
- 全国に複数の店舗を展開
- 諏訪店、上田築地店、広丘店などの店舗がある
- 地域に密着した営業を行っている
以上が、ラ・ムーの主な特徴です。低価格と便利さを重視したスーパーマーケットチェーンであることがわかります。
おおおお、しっかりラ・ムーの特徴を捉えて返信してくれて良きですね!!
キャッチフレーズや展開されていてる店舗で嘘がないか確認しましたが実際に長野県に存在する店舗でした。
検索結果に長野県の店舗が中心で書かれた結果があったんですかね。
ちなみにツールが使われていないと下記のような結果が返ってきました。
============================================================
テスト 1: ラ・ムー と検索して特徴を教えてください
============================================================
エージェント応答:
では、検索結果から情報を確認します。ラ・ムーの主な特徴は以下の通りです:
1. 業態:ディスカウントストア(discount supermarket)
2. 運営会社:サニーマート(山陽マルナカグループ)
3. 主な展開地域:主に中国地方(岡山県、広島県など)
4. 特徴:
- 低価格路線
- 生鮮食品、日用品、食料品などを安価に販売
- コンパクトな店舗設計
- チラシ特売や破格値の商品が豊富
5. ターゲット:
- 節約志向の消費者
- 家族連れ
- 価格に敏感な買い物客
ラ・ムーは、地方都市を中心に展開するディスカウントスーパーで、手頃な価格と幅広い品揃えが特徴的なスーパーマーケットチェーンです。
「大黒天物産」という大好きな文字がないのでハルシネーションと気づきました。
ツールの有無で回答を見比べてしっかりとツールの効果が確認できました!
ライブビューでの確認
実行中のブラウザをコンソールからライブで閲覧できます。AgentCore Browserの一覧画面から最新のセッションを選択すると、現在の画面やステップログ、録画の再生などを確認できます。
セッションの一覧が下記のようにあって、アクティブなセッションはView live session
をクリックするとブラウザが動いている様子が見れます。
下記みたいな感じですね。
ジーと見ていたのですが、必死にLLM君が操作していて可愛くて応援したくなりました。(頑張れ・・・!)
注意点
ブラウザの検索ウィンドウから検索しようとするとCAPTCHAが表示されて、LLMがCAPTCHAを解決できずにずっと作業している状態になりました。そのためプロンプトはYahoo! JAPANにアクセスして、Yahoo! Japanのトップページ内の検索テキストボックスから検索する、ブラウザの検索ウィンドウを使用しないオペレーションを指示した意図になります。
公式ドキュメントを見ていたらCAPTCHAに対する対策が書いてありました。
- ブラウザツールは特定のページアクションにのみ使用し、一般的なウェブ検索には使用しない
- 一般的なウェブ検索操作には、Tavily検索のような非ブラウザMCPツールを使用する
- 必要に応じてエンドユーザーが制御を引き継いでCAPTCHAを解決できるライブビュー機能をエージェントアプリケーションに追加することを検討する
とありますね。一般的な検索は避けるのが良いみたいですね。検索したい場合は非ブラウザMCPサーバーを使うのが良いとのことです。今回は無理やり検索を通すような処理にするようプロンプトで回避できたって感じですね。
AgentCore Browserのユースケースとしては、検索以外だと特定Webページにあるフォームなどの自動入力や旅行の予約判断などして業務自動化などあるかもしれませんね。
おわりに
ブラウザ操作をエージェントで実行可能にするAgentCore Browserを試してみました!
ブラウザ上からしか操作できないオペレーションをAIエージェントから自動化できたら面白いですよね。
何か実践的なエージェントも今後は作れたらなと思いました。
本記事が少しでも参考になりましたら幸いです。最後までご覧いただきありがとうございましたー!