ミツモア Tech blog

「ミツモア」を運営する株式会社ミツモアの技術ブログです

既存のReactアプリをAIエージェント化する ― 画面操作をAIエージェントのツールにする仕組み

ミツモアの Agentic AI Lab でAIエージェント関連の開発を担当している井藤です。

ミツモアでは、現場向け業務SaaS「プロワン」にAIエージェントを組み込む取り組みを進めています。その中で、AIがブラウザ上のUIを直接操作できる仕組み「クライアントサイドツール」を設計・実装しました。

本記事では、クライアントサイドツールの背景・アーキテクチャ・実装方法を、見積もり明細の操作デモを題材に紹介します。

背景:AIエージェントに「手足」を与える

LLM単体ではテキストを生成することしかできませんが、「ツール」を渡すことで、AIは外部のシステムを操作できるようになります。ツールは「AIが呼び出せる関数」のようなもので、これによってAIはデータ検索、API呼び出し、ファイル操作などを行える「エージェント」になります。

ミツモアでも、サーバーサイドのデータ操作にはMCP(Model Context Protocol)ツールを使っています。MCPツールはサーバー上で実行され、DBやAPIなどに直接アクセスします。

しかし、業務SaaSでAIが実際に「仕事」をするには、データの取得や更新だけでなく、ユーザーが普段行っている画面操作もできる必要があります。MCPツールだけでは解決できない課題がありました。

  • フォームへの入力や画面遷移など、ブラウザ上のUI操作ができない
  • サーバーサイドでデータを直接書き換えると、フロントエンドのバリデーションやカスケード更新(あるフィールドの変更に連動して他のフィールドも更新される処理)をバイパスしてしまい、データの不整合が起きうる
  • ユーザーが見ている画面の状態(どのフォームが開いているか、どんな選択肢があるか)をサーバーからは把握できない

そこで、ブラウザ上で実行される「クライアントサイドツール」 という仕組みを作りました。

MCPツール:  UseAI Server → ProOne Server(DB / OpenSearch)

クライアントサイドツール:   UseAI Server → ブラウザ(DOM操作 / フォーム操作)

MCPツールがサーバーサイドの手足なら、クライアントサイドツールはブラウザサイドの手足です。この2つを組み合わせることで、AIエージェントはサーバーのデータにもブラウザのUIにもアクセスできるようになります。

use-ai とは

クライアントサイドツールの仕組みは、ミツモアが公開しているライブラリ use-ai によって実現されています。ここでは要点だけ紹介します。

use-aiは、Reactアプリケーションにチャット型AIエージェントを統合するためのフレームワークで、クライアント(@meetsmore-oss/use-ai-client)とサーバー(@meetsmore-oss/use-ai-server)の2パッケージで構成されています。主な特徴は以下の通りです。

  • useAI() フックで、コンポーネントからAIにツールとコンテキスト(状態)を宣言的に登録できる
  • UseAIProviderがマウント中のコンポーネントのツール・コンテキストを自動集約し、Socket.IO経由でAIサーバーに送信する
  • AIがツールを呼ぶと、ブラウザ側のハンドラがそのまま実行される
  • サーバーサイドMCP・ファイルアップロード・Langfuse連携・チャットUI・レート制限なども組み込み済み
UseAIProvider(接続管理、認証、チャットUI、FileTransformer)

└── useAI({ id: 'estimate-form', tools, prompt })   ← 見積もりヘッダー

└── useAI({ id: 'line-table', tools, prompt })       ← 明細テーブル

今レンダリングされているコンポーネントの登録だけが集約されるため、ページ遷移やコンポーネントのアンマウント時には自動的に登録解除されます。AIが見えるツールは常に「ユーザーが今見ているページで実際に操作できるもの」だけです。

use-aiの全体像(アーキテクチャ、機能一覧、AG-UIプロトコル対応など)については、以下の記事で詳しく解説されています。

👉 use-ai: Unlocking AI power for any app 💪🏼(英語)

本記事ではuse-ai自体の深掘りはここまでにして、クライアントサイドツールの設計と実装にフォーカスします。

デモ:自然言語で見積もり明細を操作

デモ動画: AIにチャットで指示して、既存の見積もり明細行の一部の単価を2倍に変更しています。

明細業を2倍するdemo
見積もり画面にクライアントサイドツールを登録し、AIとチャットするだけでこのようなUIを介した柔軟な操作が実行できます。この後のセクションで、これがどのように実現されているかを見ていきます。

通信の仕組み

ブラウザとAIサーバー間の通信はSocket.IOで行われます。

データフロー図

一般的なAIエージェントではツール実行はサーバーサイドで完結しますが、クライアントサイドツールではAIサーバーからブラウザに「このツールを実行して」とリクエストが飛び、ブラウザが結果を返すという往復が発生します。この往復をSocket.IOのリアルタイム通信で実現しています。

実装方法:3ステップでツールを登録する

クライアントサイドツールの実装は、ツール定義コンテキスト構築useAI呼び出しの3ステップです。

ステップ1:defineToolでツールを定義する

defineToolで「AIが呼べるツール」をひとつ定義します。引数は、説明文(AIがいつ使うか判断するためのテキスト)、Zodスキーマ(パラメータの型)、ハンドラ(ブラウザ上で実行される関数)の3つです。

import { defineTool, z } from '@meetsmore-oss/use-ai-client'

const changeFieldTool = defineTool(
  // AIが読む説明文
  'Updates an estimate field.',
  // パラメータのスキーマ
  z.object({
    field: z.enum(['title', 'taxType', 'note']),
    value: z.string(),
  }),
  // ブラウザ上で実行されるハンドラ
  (params) => {
    setValue(params.field, params.value)  // react-hook-form等の既存関数をそのまま呼ぶ
    return { success: true, message: `Updated ${params.field}` }
  },
)

ポイントは、ハンドラの中で既存のコンポーネントの操作関数をそのまま呼べることです。react-hook-formのsetValueでもDOM操作でもAPIコールでも、ブラウザ上で動くものなら何でも書けます。

ステップ2:コンテキストを組み立てる

AIに「今この画面がどういう状態か」を伝えるテキストを作ります。

const context = `
## Estimate Form
Current values: ${JSON.stringify({ title, taxType })}
Available tags: ${JSON.stringify(tags.map(t => t.name))}
`

フォームの値が変わるとコンテキストも更新されるため、AIは常に最新の状態を把握できます。選択肢(タグ一覧など)を含めると、AIが有効な値を選びやすくなります。

ステップ3:useAIで登録する

ツールとコンテキストをuseAI()に渡すと、そのコンポーネントがマウントされている間だけAIからツールが使えるようになります。

useAI({
  id: 'estimate-form',
  tools: { changeField: changeFieldTool, save: saveTool },
  prompt: context,
})

これだけで、AIチャットから estimate-form_changeFieldestimate-form_save が呼べるようになります。コンポーネントがアンマウントされると自動的に登録解除されるので、AIが見えるツールは常に「ユーザーが今操作できるもの」だけに保たれます。

実装で気をつけたこと

破壊的な操作にはユーザー確認を入れる

defineTool の第4引数で annotations を設定すると、AIがそのツールを実行する前にユーザーの承認ダイアログが表示されます。保存や削除のような操作には必ず設定しています。

const saveTool = defineTool(
  'Saves the current estimate.',
  z.object({}),
  async () => {
    await save()
    return { success: true }
  },
  { annotations: { destructiveHint: true } },
)

コンテキストは必要最小限にする

データをそのままコンテキストに流し込むとトークン使用量が膨張し、AIの精度も落ちます。例えばタグ情報はidとnameだけに絞るなど、AIが判断に必要な情報だけを渡すようにしています。ツール数も同様で、多すぎるとAIが適切なツールを選びにくくなるため、コンポーネントごとに必要最小限に留めています。

Zodスキーマでパラメータを制約する

AIは想定外の値を渡してくることがあります。z.string()だけでは緩すぎるので、z.enum()で有効な値を限定したり、z.string().max()で長さを制限したりしています。スキーマを厳しくするほど、AIの操作精度が上がります。

まとめ

本記事では、業務SaaS「プロワン」にAIエージェントを統合するにあたって設計・実装した「クライアントサイドツール」を、見積もり明細の操作デモを題材に紹介しました。

  • MCPツール(サーバーサイド)とクライアントサイドツール(ブラウザサイド)の組み合わせで、AIがデータもUIも操作できるようにした
  • use-aiのuseAI()フックでツールを宣言的に登録し、コンポーネントのライフサイクルと連動した自動スコープ管理を実現した
  • defineToolでツール定義、コンテキスト構築でAIに状態を伝え、useAIで登録する、という3ステップで実装できる
  • 破壊的操作のユーザー確認、コンテキストの最小化、Zodスキーマによるパラメータ制約など、実装で気をつけたポイントも紹介した

今後の記事では、AIをコードからプログラマティックに呼び出す仕組みchat.sendMessage + FileTransformer)を使って、OCRで読み取ったPDFから見積もり明細を自動入力するデモを紹介します。

本記事で紹介したクライアントサイドツールの仕組みは、ミツモアが公開しているライブラリ use-ai で実現しています。ライセンスは Business Source License 1.1 です。ぜひ使ってみてください! 👉 GitHub: meetsmore/use-ai

ミツモアで一緒に働きませんか?

ミツモアでは、生成AIを活用して圧倒的な生産性を生み出し、日本のGDPを向上させるという目標に向けて、一緒に働く仲間を募集しています。今回ご紹介したように、プロダクトへのAIエージェント組み込みを積極的に進めています。

少しでも興味をお持ちの方は、カジュアル面談からでも大歓迎です。ぜひお気軽にご応募ください!

ミツモア採用ページをチェックする!