2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Gemini 2.5 Computer Use+PlaywrightをPythonのサンプルコードで動かしてみた

2
Last updated at Posted at 2025-12-25

image.png

はじめに

以下の記事でGemini 2.5 Computer Useの特徴やWeb操作自動化のポイントを整理しました。本記事では公式ドキュメントのサンプルコードをもとに、Gemini 2.5 Computer Useを動かしてみたのでソースコードや実際の動作について紹介します。

今回は、実装を理解するためにサンプルコードをベースに自分でコーティングを行いましたが、Githubでもサンプルコードが公開されています。サクっと試したい方はこちらの利用がおすすめです。

ソースコード

動作環境

今回は以下の環境で動作を確認しました。

  • Python 3.14.0
  • Playwright 1.56.0
  • モデル: gemini-2.5-computer-use-preview-10-2025

サンプルコードからの変更箇所

FunctionResponsesafety_decision が含まれるときの処理がうまく動かなかったので、自分で追加しています。そのほかは、基本的に公式ドキュメントのサンプルコードを流用していますが、以下の部分を変更しています。

  • 初期ページ
    Playwrightで開く最初のページをGoogle検索のトップページに変更
page.goto("https://www.google.com/")
  • エージェントループの上限回数
    サンプルコードだと5回がループの上限になっていますが、タスクの途中で終了することがあったため増やしました
turn_limit = 10
  • ユーザープロンプト
    コード内に定義する必要があるので、今回は天気予報を調べるようにプロンプトを変更しました
USER_PROMPT = "明日の東京の天気を調べて"

動かしたソースコードは折りたたんでおきます。

Gemini 2.5 Computer Use+Playwrightの実装コード

import time
from typing import Any, List, Tuple
from playwright.sync_api import sync_playwright

from google import genai
from google.genai import types
from google.genai.types import Content, Part

client = genai.Client()

# Constants for screen dimensions
SCREEN_WIDTH = 1440
SCREEN_HEIGHT = 900

# Setup Playwright
print("Initializing browser...")
playwright = sync_playwright().start()
browser = playwright.chromium.launch(headless=False)
context = browser.new_context(viewport={"width": SCREEN_WIDTH, "height": SCREEN_HEIGHT})
page = context.new_page()

def denormalize_x(x: int, screen_width: int) -> int:
    """Convert normalized x coordinate (0-1000) to actual pixel coordinate."""
    return int(x / 1000 * screen_width)

def denormalize_y(y: int, screen_height: int) -> int:
    """Convert normalized y coordinate (0-1000) to actual pixel coordinate."""
    return int(y / 1000 * screen_height)

def execute_function_calls(candidate, page, screen_width, screen_height):
    results = []
    function_calls = []
    for part in candidate.content.parts:
        if part.function_call:
            function_calls.append(part.function_call)

    for function_call in function_calls:
        action_result = {}
        extra_fr_fields = {}
        fname = function_call.name
        args = function_call.args
        print(f"  -> Executing: {fname}")

        try:
            if "safety_decision" in args:
                sd = args["safety_decision"]
                if sd.get("decision") == "require_confirmation":
                    print("[SAFETY]", sd.get("explanation", "Need confirmation"))
                    ok = input("Proceed? [y/N]: ").strip().lower() in ("y", "yes")
                    if not ok:
                        results.append((fname, {"skipped": True}))
                        continue
                    extra_fr_fields["safety_acknowledgement"] = "true"
            if fname == "open_web_browser":
                pass # Already open
            elif fname == "click_at":
                actual_x = denormalize_x(args["x"], screen_width)
                actual_y = denormalize_y(args["y"], screen_height)
                page.mouse.click(actual_x, actual_y)
            elif fname == "type_text_at":
                actual_x = denormalize_x(args["x"], screen_width)
                actual_y = denormalize_y(args["y"], screen_height)
                text = args["text"]
                press_enter = args.get("press_enter", False)

                page.mouse.click(actual_x, actual_y)
                # Simple clear (Command+A, Backspace for Mac)
                page.keyboard.press("Meta+A")
                page.keyboard.press("Backspace")
                page.keyboard.type(text)
                if press_enter:
                    page.keyboard.press("Enter")
            else:
                print(f"Warning: Unimplemented or custom function {fname}")

            # Wait for potential navigations/renders
            page.wait_for_load_state(timeout=5000)
            time.sleep(1)

        except Exception as e:
            print(f"Error executing {fname}: {e}")
            action_result = {"error": str(e)}

        action_result["extra_fr_fields"] = extra_fr_fields
        results.append((fname, action_result))

    return results


def get_function_responses(page, results):
    screenshot_bytes = page.screenshot(type="png")
    current_url = page.url
    function_responses = []
    for name, result in results:
        extra = result.get("extra_fr_fields", {})
        response_data = {"url": current_url}
        response_data.update(result)
        function_responses.append(
         types.FunctionResponse(
            name=name,
            response={
               "status": "ok" if "error" not in result else "error",
               **extra,
               **response_data,
            },
            parts=[
               types.FunctionResponsePart(
                     inline_data=types.FunctionResponseBlob(
                        mime_type="image/png",
                        data=screenshot_bytes
                     )
               )
            ],
         )
        )
    return function_responses

try:
    # Go to initial page
    page.goto("https://www.google.com/")

    # Configure the model
    config = types.GenerateContentConfig(
        tools=[types.Tool(computer_use=types.ComputerUse(
            environment=types.Environment.ENVIRONMENT_BROWSER
        ))],
        thinking_config=types.ThinkingConfig(include_thoughts=True),
    )

    # Initialize history
    initial_screenshot = page.screenshot(type="png")
    USER_PROMPT = "明日の東京の天気を調べて"
    print(f"Goal: {USER_PROMPT}")

    contents = [
        Content(role="user", parts=[
            Part(text=USER_PROMPT),
            Part.from_bytes(data=initial_screenshot, mime_type='image/png')
        ])
    ]

    # Agent Loop
    turn_limit = 10
    for i in range(turn_limit):
        print(f"\n--- Turn {i+1} ---")
        print("Thinking...")
        response = client.models.generate_content(
            model='gemini-2.5-computer-use-preview-10-2025',
            contents=contents,
            config=config,
        )

        candidate = response.candidates[0]
        contents.append(candidate.content)

        has_function_calls = any(part.function_call for part in candidate.content.parts)
        if not has_function_calls:
            text_response = " ".join([part.text for part in candidate.content.parts if part.text])
            print("Agent finished:", text_response)
            break

        print("Executing actions...")
        results = execute_function_calls(candidate, page, SCREEN_WIDTH, SCREEN_HEIGHT)

        print("Capturing state...")
        function_responses = get_function_responses(page, results)

        contents.append(
            Content(role="user", parts=[Part(function_response=fr) for fr in function_responses])
        )

finally:
    # Cleanup
    print("\nClosing browser...")
    browser.close()
    playwright.stop()

Gemini 2.5 Computer UseでWebブラウザを操作する

上記のコードを実行すれば、ユーザープロンプトに従ってWebブラウザの操作が始まります。

事前準備

コードを実行する前に以下の準備が必要です。

  • APIキーの取得・設定
  • Playwright, Chromiumのインストール

APIキーは環境変数名を GEMINI_API_KEY または GOOGLE_API_KEYとしてセットしてください。ソースコード上は明示していませんがGemini APIライブラリの仕様で、該当の環境変数が自動的に取得されます*

Gemini 2.5 Computer UseはAIエージェント用モデルとして使用します。Webブラウザを操作するツールが別途必要ですが、今回はPlaywrightをWeb操作ツール、ChromiumをWebブラウザとして使用するためインストールしてください。

pip install playwright
playwright install chromium

Gemini 2.5 Computer Useを使ってみた

それでは、プログラムを実行してみます。自動でブラウザが立ち上がり、ロボットかどうかの確認が入りました。

CAPTCHAチャレンジの入力については、Gemini 2.5 Computer Useの safety_decision でリスクが高い操作として検出され、人間に判断が戻ってきます。

以下はターミナルの画面です。チェックボックスにチェックを入れていいか判断を仰ぎ、人間からの入力を待機しています。

image.png

操作を続けさせると、検索ページで「明日の東京の天気」を検索していることが分かります。

image.png

検索結果をもとに、ターミナル上で「明日の東京の天気」に回答してプログラムは終了しました。

image.png

ターミナルのログについても折りたたんでおきます。

Gemini 2.5 Use Computerによるタスク実行ログ
Initializing browser...
Goal: 明日の東京の天気を調べて

--- Turn 1 ---
Thinking...
Executing actions...
  -> Executing: open_web_browser
Capturing state...

--- Turn 2 ---
Thinking...
Executing actions...
  -> Executing: type_text_at
Capturing state...

--- Turn 3 ---
Thinking...
Executing actions...
  -> Executing: click_at
[SAFETY] I need to interact with a CAPTCHA challenge to continue, as the website thinks there is unusual traffic. Do you want me to try and bypass this CAPTCHA by interacting with the "I'm not a robot" checkbox?
Proceed? [y/N]: y
Capturing state...

--- Turn 4 ---
Thinking...
Agent finished: I have evaluated step 3. I successfully passed the reCAPTCHA, and now I see the Google search results for "明日の東京の天気" (Tomorrow's weather in Tokyo).

Today is December 25, 2025 (Thursday, 木). "明日" (Tomorrow) is December 26, 2025 (Friday, 金).

The weather forecast card at the top shows:
- "今日" (Today, 木, Dec 25): 12°/7°
- "金" (Friday, Dec 26): 10°/3°, Cloudy then Sunny (曇りのち晴)
- "土" (Saturday, Dec 27): 7°/0°
- "日" (Sunday, Dec 28): 11°/-1°
... and so on.

The weather for tomorrow (金曜日 - Friday, Dec 26) is 10°C / 3°C, and it will be cloudy then sunny.

I have found the information requested.
- "今日" (Today, 木, Dec 25): 12°/7°
- "金" (Friday, Dec 26): 10°/3°, Cloudy then Sunny (曇りのち晴)
- "土" (Saturday, Dec 27): 7°/0°
- "日" (Sunday, Dec 28): 11°/-1°
... and so on.

The weather for tomorrow (金曜日 - Friday, Dec 26) is 10°C / 3°C, and it will be cloudy then sunny.

I have found the information requested.

明日の東京(2025年12月26日)の天気は、曇りのち晴れで、最高気温は10度、最低気温は3度です。

Closing browser...

まとめ

Gemini 2.5 Computer UseとPlaywrightを組み合わせて、Web操作エージェントを実装してみました。サンプルコードを流用しているため、Gemini 2.5 Computer Useが判断するアクションのうち、open_web_browser, click_at, type_text_at 以外は追加で実装が必要ですが、この3種類でも簡単なタスクが完了できることが確認できました。

また、CAPTCHAチャレンジの入力で待機したことで、Gemini 2.5 Computer Useの内部安全システムが動作していることが分かりました。これで安心してGemini 2.5 Computer Useが利用できるかなと思います。Gemini 2.5 Computer UseでAIエージェントを実装する際の参考になりますと幸いです。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?