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 ステップのピクセル数 |