デュアルモニターでコードを確認しながら作業するエンジニア
技術解説

LLMエージェントの出力を「UIイベント」として設計する発想

目次を見る

ローカルLLMエージェントのインターフェースとして、テキストチャット窓の代わりにアバターを置く——それがGhost Vesselというオープンソースプロジェクトの試みです。見た目の話に見えて、その実装には「LLMの出力をどう構造化するか」という設計上の問いが埋め込まれています。

出力をテキストではなく「契約」として扱う

Ghost Vesselが面白いのは、LLMの返答を「文字列」として受け取るのをやめた点です。代わりに「出力コントラクト(output contract)」と呼ぶ構造を定義し、エージェントの返答を3つの層に分けています。

最初の層は「dialogue(対話)」で、音声合成(TTS)で読み上げる会話文です。次が「data」で、コードやログ、ファイルなどをカード形式でレンダリングする非音声情報です。そして3番目が「action」で、アバターの表情や動作を制御する感情ビート(emotion beat)です。

感情ビートとは、LLMの返答テキストにインラインで埋め込まれたタグのことを指します。たとえば `[working]` というタグが含まれていれば、アバターは作業中の表情に切り替わります。`[confirm] deploy to prod?` であれば、デプロイ前に人間の承認を求めるモーダルが表示され、エージェントはキー入力を待ってから処理を続けます。テキストを読み飛ばしがちな場面で、視覚的なシグナルとして機能するわけです。

プロンプト設計の観点から見た感情ビート

この設計をプロンプト設計の観点で読み解くと、LLMに「特定のフォーマットで出力させる」という典型的な構造化出力の応用であることが分かります。JSON出力を強制したり、関数呼び出し形式で返答を整形させる手法と本質的には同じ発想です。

異なるのは、その出力が別のシステム(UIやTTS)を駆動するイベントとして機能している点です。LLMの返答が「読まれるテキスト」ではなく「実行されるスクリプト」として扱われています。

実際の実装ではアダプターを経由してエージェント側に接続しており、コネクターとして登録されたGhost VesselのアプリがローカルWebSocketを立ち上げ、エージェントがそこにダイヤルアウトする形になっています。エージェント側からはTelegramやSlackと同じ「もう一つのチャンネル」にしか見えないため、エージェント本体のコードを変更する必要がありません。

GPUをモデルに集中させるアーキテクチャ判断

ローカルでLLMを動かす環境では、GPUリソースの配分が常に問題になります。リアルタイムに表情を生成しようとすれば、それだけでGPUの帯域を消費してしまいます。Ghost Vesselはこの問題を、事前レンダリングされた約30本の短い動画クリップで解決しています。

感情ビートが来たとき、アバターは新たな推論を走らせるのではなく、該当する表情のクリップを再生するだけです。GPUへの負荷は動画再生と等価になり、実行中のLLMに影響を与えません。これはリアルタイムのリップシンクを諦める代わりに得たトレードオフで、作者は「電話越しの人物らしさ」を優先する判断をしています。

アーキテクチャはTauri v2(Rustシェル、Pythonサーバー、JSプレイヤー)で構成されており、常時最前面に表示されるフレームレスな2枚のウィンドウ(アバターとチャット)がモニター上に常駐します。TTS出力にはEdge TTSをデフォルトとしつつ、Qwen3-TTSやMeloTTS、Piperへの差し替えも想定されています。音声入力側はSilero VAD(音声区間検出)とfaster-whisperを組み合わせています。

このパターンが示す開発ワークフローへの示唆

Ghost Vesselの技術的な核心は、LLMの出力をUIのイベントソースとして設計し直した点にあります。この発想は、開発ツールとしてのAIエージェントを組み込む際にも応用できます。

  • エージェントの出力に `[needs_review]` や `[test_failed]` のようなビートを定義し、CIパイプラインのUIに状態変化を伝える
  • `[confirm]` パターンを使って、デストラクティブな操作(本番DBの変更、強制プッシュなど)の前に必ず人間の介入を挟む
  • 長い処理の進捗を `[working]` → `[done]` のビートで通知し、ターミナルのpollをなくす

要するに「エージェントに何かさせる」のではなく「エージェントの状態を観測可能にする」という設計の問いです。構造化出力の既存ノウハウ(JSONスキーマ強制、ツール呼び出しフォーマット)を使いつつ、その出力先をチャット以外のシステムに向ける実装パターンとして参照できます。

LLMエージェントをワークフローに組み込む際、返答をどこに「着地させるか」を意識的に設計することが、使い勝手の差を生む分岐点になります。

参考

I replaced the chat window for my local AI agent with a face

この記事について: 本記事は AI を活用して作成し、forva AI 編集部が内容を確認・監修しています。

AI 駆動開発のご相談は forva AI へ。まずはお気軽にどうぞ。