OpenAI Agents SDKを使い、必要なToolだけを持つブラウザエージェントを作る
X-tech推進本部 小林以前、「Python + Cursor CLI + Playwright CLIで、ブラウザ操作の一部をAIに任せる」という記事で、自動化の一部にAI操作を導入する方法を紹介しました。この方法では、従来のルールベースの自動化では難しい、柔軟な判断が期待できます。一方で、AIエージェントにどこまで操作を許可するか、権限を慎重に設定する必要があります。
CursorやClaude Codeなどの汎用的なAIエージェントでは、豊富な能力を持つエージェントの権限を設定によって制御しますが、その設定を誤ってしまうと思わぬ操作を引き起こす可能性があります。それに対して自作エージェントでは、特定の機能に限定して実装できるので、エージェントが実行可能な操作範囲自体を絞れます。今回は、OpenAI Agents SDKを使って自作エージェントによるブラウザ操作の自動化を試します。
OpenAI Agents SDK
OpenAI Agents SDKは、LLMにToolを使わせながら処理を進めるAIエージェントをPythonで作るためのSDKです。単にLLMへ質問するだけでなく、必要に応じてPython関数や外部Toolを呼び出し、その結果を見て次に何をするか判断させる、といった処理を比較的シンプルに実装できます。
導入方法
Python環境でopenai-agentsをpip installするだけで導入できます。
pip install openai-agents
環境変数のOPENAI_API_KEYにOpenAIのAPIキーを設定します。
$env:OPENAI_API_KEY = "sk-..."
基本的な使い方
Agentでエージェントを定義し、Runnerで実行します。モデルは特に指定しない場合、gpt-5.6-lunaが使われます(2026年11月現在)。例えば下記のコードのような最小実装ができます。
from agents import Agent, Runner
agent = Agent(
name="Assistant",
instructions="簡潔に回答してください。",
)
result = Runner.run_sync(
agent,
"AI Agentとは何ですか?",
)
print(result.final_output)
また、@function_toolというデコレーターを使うことで、自作のPython関数をTool化できます。Tool化した関数は、エージェント定義の際にtoolsに登録します。toolsには複数の関数を登録でき、LLMはユーザーからの指示と各Toolの名前・説明・引数などをもとに、必要なToolを選択して呼び出します。この例では「12と8を足してください」という指示に対してaddが選択されます。
from agents import Agent, Runner, function_tool
@function_tool
def add(a: int, b: int) -> int:
"""2つの整数を足す。"""
return a + b
@function_tool
def subtract(a: int, b: int) -> int:
"""1つ目の整数から2つ目の整数を引く。"""
return a - b
agent = Agent(
name="Calculator",
instructions="計算には適切なToolを使ってください。",
tools=[add, subtract],
)
result = Runner.run_sync(
agent,
"12と8を足してください。",
)
print(result.final_output)
このように、LLMは、どのToolを使うか・どの引数を渡すかを判断しますが、Toolの中で実行される処理そのものは通常のPythonコードとして実装できます。そのため、計算やデータ取得、ファイル操作など、正確性や再現性が重要な処理はTool側に任せ、LLMには「いつ・どのToolを使うか」という判断を担当させられます。
自作エージェントでブラウザを操作する
ここまでは、簡単な計算処理をToolとして登録し、LLMが必要なToolを選んで実行する基本的な仕組みを確認しました。実際のエージェントでは、このToolとして計算関数だけでなく、ファイル操作やAPI呼び出し、ブラウザ操作なども登録できます。
前回の記事でもAIによるブラウザ操作を扱いましたが、先述のとおりCursorはブラウザ操作以外にもさまざまな機能を持つ汎用的なエージェントであり、権限の設定は慎重に行う必要があります。
一方で今回紹介する自作エージェントでは、Playwright CLIを利用する点は前回と同じですが、snapshot、fill、clickなどの操作をPython関数として定義し、その関数だけをToolとして登録します。これにより、エージェントが外部に対して実行できる操作を、コード上で明示的に限定できます。
例えば、ブラウザ操作Toolを次のコードのように定義します。ここで、run_playwright_command()は、Playwright CLIのコマンドと引数を受け取り、CLIを実行して、その結果をエージェントが利用できる文字列として返す共通関数です。戻り値は成功時にはSUCCESS、失敗時にはFAILEDから始まる形式としています。snapshotが生成された場合は、その内容も読み込み、要素のref(snapshotが各操作対象に付与する参照ID)などを一緒に返します。本記事では説明を簡潔にするため、run_playwright_command() の実装は省略します。そのため、掲載コードのみではそのまま実行できないことにご注意ください。
@function_tool
def browser_open(url: str) -> str:
"""指定したURLをブラウザで開く。"""
print(f"[TOOL] browser_open({url})")
return run_playwright_command("open", url, "--headed")
@function_tool
def browser_snapshot() -> str:
"""現在のページの構造を取得する。操作可能な要素のrefも含まれる。"""
print("[TOOL] browser_snapshot()")
return run_playwright_command("snapshot")
@function_tool
def browser_click(ref: str) -> str:
"""snapshotに表示されたrefを指定して要素をクリックする。"""
print(f"[TOOL] browser_click({ref})")
return run_playwright_command("click", ref)
@function_tool
def browser_fill(ref: str, text: str) -> str:
"""snapshotに表示されたrefを指定して入力欄へ文字列を入力する。"""
print(f"[TOOL] browser_fill({ref}, {text})")
return run_playwright_command("fill", ref, text)
@function_tool
def browser_press(key: str) -> str:
"""現在フォーカスされている要素でキーを押す。"""
print(f"[TOOL] browser_press({key})")
return run_playwright_command("press", key)
Toolができたら、初めに紹介した計算の例と同様に各Toolをエージェントに登録し、Runnerで実行できます。
agent = Agent(
name="Browser Agent",
instructions=(
"あなたはPlaywright CLIを使ってWebブラウザを操作するエージェントです。"
"ツールの戻り値がSUCCESSなら、その操作は成功しています。"
"戻り値にはsnapshotの本文が含まれます。refは e8 のような表記です。"
"ページを開いた後は、そのsnapshotで現在のページを確認してください。"
"要素を操作するときはsnapshotから得られたrefを使用してください。"
"refを推測してはいけません。"
"操作後は戻り値のsnapshotで操作結果を確認してください。"
"確認できない場合だけ browser_snapshot を呼んでください。"
),
tools=[
browser_open,
browser_snapshot,
browser_click,
browser_fill,
browser_press,
],
)
result = Runner.run_sync(
agent,
(
"https://demo.playwright.dev/todomvc/ を開いてください。"
"Todoとして「Agents SDKを試す」を追加してください。"
"追加できたことを確認してから終了してください。"
),
)
print("\n最終回答:")
print(result.final_output)
実行すると、エージェントはページの状態を確認したうえで、入力欄への文字入力、Enterキーの送信という順にToolを選択しました。実行ログの一部を抜粋すると、次のようになります。
[TOOL] browser_open(https://demo.playwright.dev/todomvc/)
...
- textbox "What needs to be done?" [active] [ref=e8]
[TOOL] browser_fill(e8, Agents SDKを試す)
[TOOL] browser_press(Enter)
...
- generic [ref=e22]: Agents SDKを試す
最終回答:
Todo「Agents SDKを試す」を追加し、表示されていることを確認しました。
このように、まずsnapshotから入力欄のrefであるe8を確認し、その結果をもとにbrowser_fill()、続いてbrowser_press()が呼び出されています。最後のsnapshotでは「Agents SDKを試す」というTodoが追加されたことも確認できました。
※ 今回の実行では、操作指示に加えて、Toolの戻り値に含まれるsnapshotの本文もOpenAIのAPIへ送信されます。画面に表示された文字列や入力した内容がモデルに渡るため、対象ページや入力値にはテスト用のデータを使うようにしましょう。
また、Tool内部で実行条件を制限するという使い方もできます。例えば、上記のbrowser_open()を次のように変更すればアクセス可能なURLを制限できます。
from urllib.parse import urlparse
ALLOWED_HOSTS = {
"demo.playwright.dev",
}
@function_tool
def browser_open(url: str) -> str:
"""許可されたWebサイトをブラウザで開く。"""
host = urlparse(url).hostname
if host not in ALLOWED_HOSTS:
return f"FAILED: 許可されていないURLです: {url}"
return run_playwright_command("open", url, "--headed")
このようにすれば、LLMへの指示だけに頼るのではなく、browser_open()で直接開けるURLをプログラム側で制限できます。
まとめ
今回は、OpenAI Agents SDKを使って、Playwright CLIを操作するブラウザエージェントを作成しました。
自作したエージェントでは、登録するToolを必要なものだけに限定できます。さらに、今回のようにbrowser_open()の内部で直接開けるURLを制限すれば、「ブラウザを開く」というToolを与えつつ、その実行範囲をさらにコード側で制御することもできます。必要な能力や制約をコードとして明示できるため、特定の業務や用途に合わせた構成を作りやすいというメリットがあります。
一方で、自作する場合はToolの設計やエラー処理、Playwright CLIとの接続部分などを自分で実装する必要があります。また、Toolを限定してもAIエージェント利用時のリスクがなくなるわけではありません。画面の内容や入力値はモデルに渡るため、扱うデータに応じた制御が必要です。Cursorなどの汎用的なエージェントと自作エージェントのどちらが適しているかは用途によりますが、今回の実装を通して、必要な能力だけを持つ専用エージェントを比較的シンプルに構築できることを確認できました。