Friendlyを使ってトークン消費を抑えるてWindowsアプリケーションを操作する

トークン代がばかにならない

6月からGitHub Copilotの料金体系が変わり、トークン消費に応じた課金が始まりました。

GitHub Copilot is moving to usage-based billing - The GitHub Blog

以前まではトークン消費とは関係なくタスクを渡した回数で課金されていました。まじでボーナスタイムだった。残念ながらすでにトークン消費量に応じた課金に変更されています。実質的な値上げですね。最近だとAmazonなどもAIの使用を抑えるような方向にシフトしているようです。背景の1つにはコスト急増があるようです。

「消費トークンを以下に抑えられるか」というのはこれまで以上に重要なポイントの1つになっていきそうです。

汎用ツールはトークンをそれなりに使う

WindowsアプリケーションをAIエージェントに操作させたいと思い以前FlaUIをベースとしたCLIツールを作ったことがあります。

Windowsアプリケーションを操作するCLIツール(flaui-cli)を作ってみた - zzzkan.me

playwright-cliのWindowsアプリケーション版みたいな感じです。仕組みとしては、まず画面から以下のような「スナップショット」を得ます。

- window "電卓" [ref=e1]:
    - window "電卓" [ref=e2] [readonly]:
        - menubar "システム" [ref=e3]:
            - menuitem "システム" [ref=e4] [collapsed]
        - button "電卓 の最小化" [ref=e5]
        - button "電卓 を最大化する" [ref=e6]
        - button "電卓 を閉じる" [ref=e7]
...

これをAIエージェントに渡して、操作したい要素(ref)を見つけてもらい、要素を指定して操作(クリックやテキスト入力など)を実行する、という流れです。

これはこれで便利で、有用なスナップショットが得られさえすれば、どんなアプリケーションでも操作できそうです。ただ、この方式はどうしてもトークンを使います。画面が複雑になるほどスナップショットは大きくなるし、操作のたびにスナップショットを取り直していくことになるので。

たとえば、単に「ファイルを開く」という操作をしたいだけでも、

  1. メニューを見つける
  2. メニューの中から「ファイル」を見つける
  3. 「ファイル」の中から「開く」を見つける
  4. ...

みたいな一連の流れをAIエージェントに都度判断してもらう必要があります。

WindowsアプリケーションにもAPIがあるといいのかも?

たとえば、GitHubのようなWebサービスであれば、RESTなりGraphQLなりなAPIがあり、CLIもあり、それを呼び出せば「Issueを立てる」みたいない操作を直接行えます。

gh issue create --title "Title" --body "Body"

こういうことがWindowsアプリケーションでもできるとよさそうです。

ここで個人的なポイントなのですが、WindowsアプリケーションというGUIメインのアプリケーションを選択している以上、ユーザーは「画面を見ながら操作する」ことを前提にしていると思います。少なくとも現時点では(将来的には変わるのかもしれません)。なので、AIエージェントが操作した(している)結果も「画面で見える」と嬉しいはずです。

FriendlyでAPIを外付けしてみる

Windowsアプリケーションのテスト自動化について調べていくとCodeer.Friendlyという面白いライブラリが見つかります。

これは別プロセスのWindowsアプリケーションにアタッチしてその中のメソッドなりプロパティなりを呼び出せるというライブラリです。すごい。なんでも呼び出せるので中身が分かっていればなんでもできるということです。ただし、対象はWinForms、WPFまたはWin32とのことです。UWPなんてなかったんや…。

たとえば、以下のようなアプリケーションがあったとします。

テスト用WPFアプリケーション

テキストファイルを開いて、編集、保存ができるだけの簡単なアプリケーションです。テストファイルを開くには、画面ベースでは「1. Openボタンをクリック」「2. ファイルダイアログが立ち上がるのでファイルを選択する」というステップが必要です。ならこの操作をあらかじめ処理としてまとめて書いておけばいいわけです。

static void Open(string path)
{
    var process = Process.GetProcessesByName("TestApp").FirstOrDefault();
    var app = new WindowsAppFriend(process);
    var mainWindow = app.WaitForIdentifyFromTypeFullName("TestApp.MainWindow");

    // Openボタンを取り出して、クリックまでする。
    mainWindow.Dynamic().OpenButton.EmulateClick();

    // 同様にファイルダイアログを操作してファイルを開く
    // ここでは省略
}

これで「ファイルを開く」という操作を1つのメソッドとしてまとめられますね。これをCLIなりで呼び出せるようにすれば、AIエージェントは「ファイルを開く」という操作をするのに、画面のスナップショットを見て要素を探して…みたいなことをする必要がなくなります。AIエージェントに丸投げしていたタスクを肩代わりしているわけですね。トークン消費がかなり抑えられるはず!

で、これはもっと簡単に書ける可能性があるのがFriendlyの面白いところで、たとえばOpenボタンのイベントハンドラが以下のようなになっていとします。

private void OpenButton_Click(object sender, RoutedEventArgs e)
{
    var dialog = new OpenFileDialog
    {
        CheckFileExists = true,
        Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*"
    };

    if (dialog.ShowDialog() == true)
    {
        OpenTextFile(dialog.FileName);
    }
}

OpenTextFileというメソッドが実質的に「ファイルを開く」を行っていることが分かりますね。じゃあもうこれでよくない?

static void Open(string path)
{
    var process = Process.GetProcessesByName("TestApp").FirstOrDefault();
    var app = new WindowsAppFriend(process);
    var mainWindow = app.WaitForIdentifyFromTypeFullName("TestApp.MainWindow");

    // そもそもOpenTextFileを直接呼び出してしまえばいいじゃない
    mainWindow.Dynamic().OpenTextFile(path);
}

単純にOpenTextFileを呼び出すだけで完結します。シンプル!この操作はmainWindowのスレッドで実行されるので、つまりちゃんとUIに反映される形で操作が行われることになります。すごい。

このFriendlyのいいところは既存アプリケーションの中身を変えなくていいところです。なので、古いWinFormsアプリケーションとかでも、ちょっとしたブリッジコードを書くだけでAIエージェントから操作できるようになるかもしれません。

さいごに

これは要はflaui-cliのような汎用的な方法ではなく、各アプリケーション専用のCLIツール(API)を作るという泥臭い方法なんですが、これで十分な場合も結構あるのではとおも思っています。それこそ、こういうものをAIエージェントに書かせればいいし。AIエージェントによる操作も確実性が増すし、トークン消費も抑えられるし、結構いいことづくめな気がします。

コードの中身が分かっていないとこの方法は使えないという点に目をつぶれば。悲しいね。

ただ、今後は「このアプリケーションをエージェントに操作させたときのトークン数」というのがアプリケーションのスペックの1つになっていくのかもとも思ったりもします。そうなると、こういうCLIツールが合わせて提供されるようになっていくこともあり得そうです。