Vision AI とマウス操作

Vision AI エージェントがスクリーンショットから座標を読み取り、 人間のようにカーソルを動かしてクリックする仕組みを解説します。

概要

Paprika には 2 つのブラウザ操作方式があります:

方式ターゲット指定使用場面
CSS セレクタ方式 page.click("button#submit") SDK / codegen-loop。DOM にアクセスできるとき
Vision AI 方式(座標指定) click_at(x=432, y=218) Vision エージェント。スクリーンショットから座標を特定

CSS セレクタ方式は DOM の element.click() を呼ぶため、マウスの物理的な移動は発生しません。 一方 Vision AI 方式は、人間と同じように 画面上のピクセル座標にマウスを動かしてクリック します。 Paprika では「人間が実際にブラウザを触っているような動かし方」を再現することを大事にしており、 本ページではその仕組みを解説します。

Vision AI パイプライン

Vision エージェント(CogAgent / Qwen-VL 等)の 1 ステップ:

1. スクリーンショットを撮影
2. VLM に送信:「ログインボタンをクリックして」
3. VLM が応答:CLICK(box=[400, 200, 464, 236])
4. box の中心座標を計算:(432, 218)
5. CDP でマウスを (432, 218) に移動してクリック  ← ここが本題

CDP Input API

Paprika は Chrome DevTools Protocol (CDP) の Input.dispatchMouseEvent でマウスを制御します。 3 種類のイベントを順番に発行します:

# 1. カーソルを移動(:hover やマウスリスナーが発火)
Input.dispatchMouseEvent(type="mouseMoved", x=432, y=218)

# 2. ボタンを押下
Input.dispatchMouseEvent(type="mousePressed", x=432, y=218, button="left")

# 3. ボタンを離す
Input.dispatchMouseEvent(type="mouseReleased", x=432, y=218, button="left")

これは実際の OS レベルのマウスイベントと同等のものが Chrome 内部で生成されるため、 JavaScript の mousedown / mouseup / click イベントが正しく発火します。

瞬間移動の問題

「瞬間移動」は人間の操作ではない 従来の実装では mouseMoved を 1 回だけ発行してターゲットに瞬間移動していました。 人間が実際にブラウザを触るときのマウスは必ず曲線を描いて動き、加減速も伴います。 瞬間移動はその振る舞いから乖離しており、人間的な操作ではありません。

人間がブラウザを操作したときのマウス移動と、瞬間移動の振る舞いを比較すると次のような違いがあります:

特徴人間の操作瞬間移動
移動軌跡曲線(微妙な S 字カーブ)直線 or 瞬間移動
速度変化加速 → 巡航 → 減速一定速度 or 0ms
座標のブレ±1-2px の微小な揺れ完全に正確
mouseMoved 数数十〜数百イベント0〜1 イベント

Paprika ではブラウザ操作の振る舞いを「人間が実際に画面を見て触っているような動かし方」に 寄せるため、後述の Bezier 曲線軌跡 + 加減速 + マイクロジッターを採用しています。

Bezier 曲線による軌跡生成

Paprika は 3 次 Bezier 曲線(cubic Bezier)でマウスの軌跡を生成します。

アルゴリズム

始点 P0 = 前回のマウス位置
終点 P3 = クリック先の座標

制御点:
  P1 = P0 から 1/3 の位置 + ランダムな横方向オフセット
  P2 = P0 から 2/3 の位置 + ランダムな横方向オフセット

   P0
    \          P1(ランダムにずれる)
     \       /
      ●───●
           \
            ●───●
           /       \
  P2(ランダム)    P3(クリック先)

この曲線上に 30 個の中間点(waypoint) を等間隔に生成し、 各点で mouseMoved イベントを発行します。

カーブの大きさ

制御点の横方向オフセット(曲がり具合)は移動距離に比例します:

spread = min(120px, 移動距離 × 0.3)

短い移動(50px)  → spread = 15px  → ほぼ直線
中程度(300px)   → spread = 90px  → 自然な S 字
長い移動(500px+)→ spread = 120px → 大きなカーブ(上限)

加減速とジッター

sinusoidal ease-in-out

人間のマウス操作はターゲットに向かって加速し、到達直前で減速します。 Paprika はこれを正弦波ベースのイージング関数で再現します:

def ease_in_out(t):
    return 0.5 × (1 - cos(π × t))

t=0.0 → 0.00  (静止)
t=0.1 → 0.02  (ゆっくり加速)
t=0.3 → 0.21  (加速中)
t=0.5 → 0.50  (最高速)
t=0.7 → 0.79  (減速中)
t=0.9 → 0.98  (ゆっくり減速)
t=1.0 → 1.00  (到着)

マイクロジッター

各中間点に ±1.5 px のランダムノイズ を加えます。 始点と終点はブレません(正確に元の位置から出発し、正確にターゲットに到着する)。

# 中間点のみ(始点・終点はブレなし)
if 0 < i < steps:
    x += random.uniform(-1.5, 1.5)
    y += random.uniform(-1.5, 1.5)

対応アクション一覧

人間的マウス移動が適用されるアクション:

アクション関数マウス移動説明
click click_at() Bezier 軌跡 → press → release 左クリック
double_click click_at(click_count=2) Bezier 軌跡 → press×2 → release×2 ダブルクリック
right_click click_at(button="right") Bezier 軌跡 → press → release 右クリック
hover hover_at() Bezier 軌跡のみ(クリックなし) ホバーメニュー展開等
type type_at() Bezier 軌跡 → click → insertText フィールドをクリックして入力
scroll wheel_at() なし(ホイールイベントのみ) ページスクロール

設定

環境変数デフォルト説明
PAPRIKA_HUMAN_MOUSE 1 0 で無効化(瞬間移動に戻す)。デバッグ・速度優先時に
PAPRIKA_MOUSE_STEPS 30 1 回の移動で生成する waypoint 数。多い = より滑らか
PAPRIKA_MOUSE_DURATION_MS 250 移動にかかる時間 (ms)。30 steps ÷ 250ms ≈ 8ms/step
VISION_WHEEL_STEP_PX 100 スクロール 1 ステップのピクセル数
速度と自然さのトレードオフ steps を減らすか duration を短くすると速くなりますが、軌跡が荒く・動きが不自然になります。 デフォルト値(30 steps / 250ms)は人間のマウス移動速度の研究に基づいた妥当な値です。