AIエージェントに管理画面を操作させる:任せる範囲の決め方と、実務で踏んだ4箇所
スクリーンショットを人が撮って見せる往復をやめ、ブラウザを直接読ませる。ただし任せる範囲を決めないと、見ていない画面を「確認しました」と報告してくる。線引きと、実際に詰まった4箇所を書く。
外部サービスの管理画面での設定を、人がスクリーンショットを撮ってAIに見せ、返ってきた指示を人が実行する形でやると、異常に遅い。ブラウザを直接読ませれば往復は消える。ただし任せる範囲を決めておかないと、今度は見ていない画面を「確認しました」と報告してくる。
1. 線引きは「不可逆かどうか」で切る
事象
どこまでAIにやらせるかを決めずに始めると、毎回その場で迷う。全部止めれば往復が残って遅いままだし、全部任せると取り返しのつかない操作まで走る。
原因
「危なそうかどうか」で切ろうとするのが原因だ。危なさは主観なので毎回ぶれるし、判断のたびに手が止まる。
解決策
人間に残すのは、ログインと、取り消せない操作の承認だけにする。 それ以外(画面を読む・値を入力する・保存する)はAIがやる。「不可逆かどうか」で切ると迷わない。
- 止める:IDや識別子の確定、公開、送信、削除、課金の実行
- 進める:あとから変えられる設定の保存、表示の確認、値の読み取り
認証情報そのものをAIに触らせないのは別枠で固定する。ログインは人が済ませた状態から始める。
2. 「見ていないのに見た」と報告してくる
事象
スクリーンショットを取得するツールが、途中で切れたデータや壊れた画像を返す環境があった。その状態でエージェントに操作を任せると、画面の中身・コマンドの結果・タップの結果を実際には確認しないまま、それらしい報告を書いてくる。
実害として出たのは入力値の取り違えだった。別の項目を選んでいたのに「正しく入りました」と報告された。
原因
ツールが黙って壊れると、成功の「形」だけが返る。中身が空でも、呼び出し自体はエラーにならないので先へ進める。人が横で画面を見ていないと気づけない。
解決策
見る手段を、壊れないものに固定して閉ループにする。
まず、画面のキャプチャはアプリ側のUIツールではなくOS標準のコマンドでファイルに保存し、画像として読み直す。ツール内での表示に依存させない。
xcrun simctl io booted screenshot /tmp/shot.png # 保存してから読む
次に、画面の状態判定は見た目ではなく公式APIで照会する。「登録できたか」を管理画面のスクリーンショットで判断せず、APIでレコードの一覧を引いて確認する。
シェルの出力は区切りの目印(sentinel)で挟み、その内側だけを事実として扱う。前のコマンドの出力が混ざったまま読まれる事故がこれで消える。
echo "S_START"; <実行したいコマンド>; echo "S_END:$?"
そのうえで、状態が確実に読めないうちは不可逆な操作をエージェントにやらせない。1の線引きと同じ結論に着く。
3. 押しても何も起きないリンクの正体は window.open
事象
管理画面のメニュー項目をクリックしても何も起きない。href は javascript:void(0) かハッシュだけで、遷移先が読めない。クリック自体は成功している。
原因
正体は window.open によるポップアップだった。別ウィンドウとして開くので、自動化ツールのタブ一覧に現れないことがある。画面上も何も変わらないので、「クリックが効いていない」ようにしか見えない。
解決策
window.open を一時的に差し替えて、渡されるURLを捕まえる。関数の中身を読むより速い(そもそも読めないこともある)。
let captured = null
const orig = window.open
window.open = (u) => { captured = u; return { focus(){}, close(){} } }
// ここで対象をクリック
window.open = orig
console.log(captured)
捕まえたURLは、クエリを落とさず丸ごと location.assign で開く。OAuth の state がクエリに入っていることがあり、削ると認証に失敗する。
遷移が数秒かけて別ホストへリダイレクトすることもあるので、直後のキャプチャでは「変わっていない」ように見える。タブのURLを少し後で見直すと着地している。着地したら本体のURLを控えて、次回からは直接そこへ行く。
4. どのウィンドウを操作しているか確定させる
事象
ブラウザやプロファイルを複数つないでいると、接続名が Browser 1 Browser 2 のように汎用的で、どれがどれか判別できない。用途を分けている場合、取り違えると別のアカウントで操作してしまう。
原因
自動化ツール側の接続名は、ブラウザの表示名やプロファイル名と一致しない。
解決策
各ブラウザで myaccount.google.com を開き、ログイン中のアカウントを読めば特定できる。接続時に名前を付けられる仕組みなら、その場で付けておくと次回から一発で選べる。
macOS なら、そもそも狙ったプロファイルだけを開くランチャーを作っておくほうが早い。osacompile で .app にすれば1クリックで直接開く。
osacompile -o ~/Applications/ChromeWork.app \
-e 'do shell script "open -na \"Google Chrome\" --args --profile-directory=\"Profile 3\""'
--profile-directory に渡すのはディレクトリ名(Default / Profile 2 …)で、画面に出ている表示名ではない。対応は Local State の profile.info_cache を読めばわかる。
jq -r '.profile.info_cache | to_entries[] | "\(.key)\t\(.value.name)"' \
~/Library/Application\ Support/Google/Chrome/Local\ State
細かい詰まりどころ
画面を実際に運転させると、記事にしにくい細かさで詰まる。繰り返し出たものだけ挙げる。
- フォームの値を直接注入しても反映されないことがある(React 等では成功したように見えて空のまま)。打鍵に切り替える
- 入力のたびにレイアウトがずれる。エラー文の出現と消滅で数十px動くので、座標は毎回取り直す
- APIキーのような文字列はカーソルが文字に被る。拡大して読む。1文字違えば動かない
- タブを増やさなければ後片付けは最後に1枚閉じるだけで済む
- エラーメッセージの言い分を鵜呑みにしない。 「ファイルの中身が違う」と言われて中身を疑ったら、実際はそのファイルが404だったことがある。画面を読む前に
curlや公開APIで実測するほうが速い
線引きを「不可逆かどうか」に置いてから、この種の設定作業の往復はほぼ消えた。人の判断が要るのはログインと最後の一押しだけで、残りは読んで入れて保存するだけの単純作業になる。単純作業だからこそ、見たかどうかを本人の自己申告に任せない仕組みのほうが要る。