約 4,379 字 · 読むのに 7 分ほど
前回の記事で、文章を作らずに確率で答える AI「jev」を紹介しました。今回はその続きで、もう少し実用に寄せたものを作りました。WordPress の管理画面を、声だけで操作するプラグインです。
まず動画
73 秒、途中カットなしです。iPad mini のキーボードの音声入力で喋っています。
https://x.com/super_miyamaru/status/2102044163093188716
動画の中で起きていることは次のとおりです。
- 「プラグインの画面を開いて」→ 91% でプラグイン画面へ移動
- 「ハロードーリーを有効化して」→ 確認が出るので「はい」と答えて有効化
- 「コメントの画面を開いて」→ 100% で移動
- 「承認待ちだけ表示して」→ 音声入力が「承認町だけ表示して」と誤変換。確率が 27% と低かったので、実行せずに候補を出して聞き返してきた。「1番」と答えて絞り込み
- 「カジノのコメントはスパム」→ 音声入力の結果は「金なのコメントはスパム」。それでも BestCasino のコメントが選ばれ、確認のあとスパムに指定
- 「田中さんのコメントを承認」→ 95%。確認のあと承認
誤変換の 2 回は狙ったものではありません。撮ってみたらそうなっていました。結果的に、ここがいちばんの見どころになりました。
仕組み:画面にあるものを jev に選ばせる
音声を文字にするのは iPad に任せています。プラグインがやっているのは、次の 3 つだけです。
- いまの画面で押せるものを集める。左メニューの項目、画面上のボタンやリンク、あらかじめ用意した操作(公開する、スパムにする、など 15 個)。一覧の行にある「承認」や「ゴミ箱へ移動」には、「田中」の 承認、のように行の名前を添えます。
- 発話と選択肢を jev に渡して、どれに当たるかを選ばせる。前回紹介した choice 型の質問です。
- 返ってきた確率で動きを変える。80% 以上ならそのまま実行、40% 以上なら確認してから実行、それ未満なら候補を出して聞き返します。
jev に投げている質問は、だいたいこんな形です。
state: { utterance: '金なのコメントはスパム', current_screen: 'コメント' }
questions: {
action: {
type: 'choice',
instructions: '発話の意図に最も合う操作を1つだけ選ぶ',
criteria: {
n8: '別のページへ移動: コメント',
e22: '今の画面の要素を押す: 「BestCasino」の スパム',
c_spam_comment: 'データを直接操作: コメントをスパムにする',
none: 'どれにも当てはまらない',
// …画面にあるものが、そのまま選択肢として並ぶ
},
},
gen: { type: 'boolean', instructions: '新しい文章の生成を求めているか' },
}
ポイントは、言い回しを 1 つも登録していないことです。「消して」「捨てて」「ゴミ箱に入れて」を同じ操作に寄せるのは jev の仕事で、こちらは選択肢を並べるだけです。プラグインを入れて左メニューが増えれば、その項目も勝手に声で開けるようになります。
choice の選択肢は 255 個までなので、メニュー、用意した操作、画面上の要素の順に枠を割り当てて、この数に収めています。
選ばれた結果を、クリックにつなげる
jev は画面の構造を知りません。見ているのはラベルの文字列だけです。それでも実際のボタンを押せるのは、選択肢を集めるときに表を 2 つ作っているからです。
elements.forEach(function (e, i) {
// jev に送る表:キー → ラベルの文字列
criteria['e' + i] = '今の画面の要素を押す: ' + e.label;
// ブラウザに残す表:キー → ボタン本体への参照
actions['e' + i] = { kind: 'click', label: e.label, el: e.el };
});
jev に送るのは criteria だけで、actions はブラウザの外に出しません。jev からはキーごとの確率が返ってくるので、いちばん高いキーで手元の表を引き、出てきた要素をクリックします。
// jev の答え:{ e22: 0.95, c_approve_comment: 0.04, none: 0.01 }
var top = ranked(res.answers.action)[0]; // { key: 'e22', p: 0.95 }
var action = actions[top.key]; // el は <a>承認</a>
highlight(action.el); // 枠を付けて、見える位置までスクロール
action.el.click(); // 指でタップしたのと同じイベントが起きる
el.click() を呼ぶと、その要素に付いている処理がそのまま動きます。リンクならページを移動し、コメントの「承認」のように WordPress が JavaScript で処理している操作なら、行がその場で消える動きまで含めて普段どおりに走ります。プラグイン側は、各ボタンが何をするのかを知らなくて済みます。
キーの頭文字で、実行の仕方を変えています。
n…:左メニューの項目。手元に持っているのは URL で、そこへ移動するe…:画面上の要素。参照を持っていて、el.click()するc_…:用意した操作。REST API を呼ぶ
選択肢は発話のたびに集め直すので、表の中身はいつも現在の画面と一致します。できないこともあって、ドラッグや、マウスを乗せたときだけ反応する操作には対応できません。
jev に任せたこと、任せなかったこと
作ってみて、jev に向く仕事と向かない仕事がはっきり分かれました。
任せたこと
- 発話が、画面上のどの操作に当たるか
- 「カジノのコメント」が、コメント一覧のどれを指しているか
- 確認への返事があいまいなとき、それが同意なのか拒否なのか
- 「記事を書いて」のような、文章の生成が必要な依頼かどうか(該当したら、対応できないと断る)
任せなかったこと
- タイトルや本文などの自由な文字列。jev は文章を作れないので、喋った言葉をそのまま使います。
- 「はい」「1番」のような決まった返事。コード側で処理し、jev は呼びません。
- 削除、公開、有効化の最終判断。確率がどれだけ高くても、必ず人に確認します。jev は自信を持ったまま間違えることがあるからです。
動画の 73 秒のあいだに jev を呼んだのは 7 回でした。操作の判定が 6 回と、コメントを選ぶ判定が 1 回です。
つまずいたところ
jev まわりはすんなり動きました。時間がかかったのは、ほとんどが iPad の音声入力とのつき合い方です。
画面を移動すると、音声入力が切れる
ページを移動すると入力欄ごと作り直されるので、キーボードが閉じて音声入力も終わります。iOS はページ側から音声入力を再開させてくれないので、移動のたびにマイクのキーを押し直すことになります。
そこで、入力バーだけを持つ外枠のページを用意し、管理画面はその内側の iframe に表示するようにしました。移動するのは内側だけなので、バーと音声入力はそのまま残ります。メニューやボタンの読み取りとクリックは、内側の枠に対して行います。
iOS の音声入力は、消した文字を入れ直してくる
送信のあとに入力欄を空にしても、iOS の音声入力は自分が聞き取った文を覚えていて、丸ごと入れ直してきます。そのままだと、同じコマンドが何度も送られてしまいます。
解決策は、音声入力中は欄を消さないことでした。文字はそのまま溜めていき、「どこまで送信済みか」だけを覚えて、その先を新しい発話として読みます。溜まった文字は透明にして、新しい発話だけを上に重ねて見せています。
「承認待ち」の絞り込みに、確認が出てしまう
危険な操作かどうかを、最初はラベルの語(削除、公開、承認など)だけで判定していました。すると、一覧を絞り込むだけの「承認待ち (3)」にも確認が出ます。WordPress では、データを変えるリンクには必ず _wpnonce か action= が付いているので、リンクについてはそれがあるものだけを確認の対象にしました。
画面収録に、自分の声が入らない
iPad の画面収録と音声入力は、マイクを同時に使えませんでした。声は別の機器で録り、音声入力を始めたときのチャイム音を目印にして、あとから位置を合わせています。
使ってみた所感
- 判定は速いです。サーバー側で計った jev の応答は 0.5〜1 秒でした。体感の待ち時間は、音声入力の文字が出るまで(約 1.4 秒)と、喋り終わりを待つ時間(1.8 秒に設定)のほうが長いです。
- 確率が返ってくるのが効きます。「承認町」の 27% のように、自信がないことが数字で分かるので、実行せずに聞き返すという動きを素直に書けました。
- まだ試作です。ブロックエディタの中の操作や、ほかのプラグインの設定画面では十分に試せていません。jev の API キーはブラウザに出さず、サーバー側で中継しています。
なお、実装は Claude と対話しながら進めました。
まとめ
音声操作というと、コマンドと言い回しを登録していく作業を想像しますが、jev を使うとそこがまるごと要らなくなります。画面にあるものを並べて「どれのこと?」と選ばせるだけです。誤変換に強く、自信がないときは聞き返せるので、管理画面のように「間違えて押されると困る」場所とも相性がよいと感じました。
同じやり方は WordPress に限らず、ボタンとメニューでできている画面なら、あと付けで使えそうです。
参考:What is Jev, TypeSafe AI’s System One model?(Vercel) / Jev モデルページ(Vercel AI Gateway)
