Skip to content
AIこの記事はAIによって生成されたコンテンツです。

Generative UIとは何か ― AG-UIとA2UIで読み解くエージェント時代のUI

LLMアプリのUIといえば、長らく「チャット欄にテキストがストリーミングされてくる」だった。だが2025〜2026年にかけて、エージェントがUIそのものを生成して返す「Generative UI(生成UI)」という考え方が一気に主流になりつつある。

この記事では、Generative UIとは何かを整理したうえで、それを支える2つのプロトコル ― AG-UIA2UI ― を取り上げる。名前が似ていて混同されがちだが、両者はまったく別のレイヤーを担っている。むしろ組み合わせて使うものだ、という点を最終的に理解してもらえれば嬉しい。

Generative UIとは

Generative UIを一言でいうと、**「エージェントの出力がテキストではなくUIになる」**ことだ。

従来のLLMアプリの出力はこうだった。

text
今週の売上は以下の通りです。
- 月: 120万円
- 火: 98万円
...

これをマークダウンでレンダリングするのが関の山だった。Generative UIでは、エージェントが「売上テーブルコンポーネント」や「インタラクティブなチャート」「承認ボタン付きのフォーム」といったリッチなUI部品を返す。ユーザーはそれをクリックしたり入力したりでき、その操作がまたエージェントに返っていく。

なぜ今これが盛り上がっているのか。理由はシンプルで、エージェントが「会話の相手」から「アプリの中で実際に作業する協働者」へと役割を変えたからだ。タスクの進捗、ツール呼び出しの途中経過、人間の承認を要するステップ(human-in-the-loop)── こうしたものはテキストの羅列より、UIで表現したほうが圧倒的に分かりやすい。

ただし、これを実現しようとすると2つの異なる問題にぶつかる。

  1. どうやってバックエンドのエージェントとフロントエンドを繋ぐか(通信・ストリーミング・状態同期の問題)
  2. エージェントが返すUIを、どんな形式で記述するか(UIの表現フォーマットの問題)

この1番を担うのが AG-UI、2番を担うのが A2UI だ。順番に見ていく。

AG-UI ― エージェントとフロントエンドを繋ぐプロトコル

AG-UI(Agent-User Interaction Protocol)は、CopilotKitが主導するオープンで軽量なイベントベースのプロトコルだ。ユーザー向けアプリケーションと、任意のエージェントバックエンドとの間の双方向の接続レイヤーを標準化する。

ポイントは、AG-UIはGenerative UIの「仕様」ではないということ。あくまでエージェントとUIの間を流れるランタイムの通信規約であり、その上にGenerative UIを含むさまざまなものを載せられる、という位置づけだ。

イベント駆動という設計

AG-UIの中心にあるのは約16種類の標準イベントで、おおむね5つのカテゴリに分類される。

カテゴリ代表的なイベント役割
ライフサイクルRUN_STARTED / RUN_FINISHED / RUN_ERRORエージェント実行の開始・終了・エラー
テキストメッセージTEXT_MESSAGE_START / TEXT_MESSAGE_CONTENT / TEXT_MESSAGE_ENDトークン単位のストリーミング
ツール呼び出しTOOL_CALL_START / TOOL_CALL_ARGS / TOOL_CALL_ENDツール実行の進行
状態管理STATE_SNAPSHOT / STATE_DELTAフロントとバックの状態同期
特殊CUSTOM / RAW など拡張・独自用途

イメージとしては、エージェントの実行が始まるとRUN_STARTEDが飛び、テキストがTEXT_MESSAGE_CONTENTで逐次流れてきて、ツールを叩けばTOOL_CALL_*が、状態が変わればSTATE_DELTAが飛ぶ。フロントエンドはこのイベントストリームを購読して、UIをリアルタイムに更新する。

SSE(Server-Sent Events)で流す場合、概念的にはこんなストリームになる。

text
event: RUN_STARTED
data: {"threadId":"t_1","runId":"r_1"}

event: TEXT_MESSAGE_START
data: {"messageId":"m_1","role":"assistant"}

event: TEXT_MESSAGE_CONTENT
data: {"messageId":"m_1","delta":"今週の売上を集計します"}

event: TOOL_CALL_START
data: {"toolCallId":"tc_1","toolCallName":"query_sales"}

event: STATE_DELTA
data: {"delta":[{"op":"add","path":"/sales/mon","value":1200000}]}

event: RUN_FINISHED
data: {"threadId":"t_1","runId":"r_1"}

STATE_DELTAJSON Patch風の差分になっているのが面白いところで、ダッシュボードのような大きなオブジェクト全体を毎回送り直すのではなく、変わった部分だけを送って同期コストを抑えられる。

トランスポートと中間層

AG-UIはトランスポートを固定しない。SSE / WebSocket / Webhook のいずれにも対応し、イベント形式の差異を吸収する中間層(ミドルウェア)を挟むことで、多様な環境との互換性を保つ設計になっている。CORS・認証トークン・監査ログといったエンタープライズ要件も視野に入っており、フルオープンソースでセルフホスト可能だ。

何ができるのか

AG-UIが標準で面倒を見てくれるのは、ざっくり以下だ。

  • ストリーミングチャット
  • フロントエンド/バックエンド双方でのツール呼び出し
  • human-in-the-loop(人間の承認を挟むフロー)
  • Generative UI
  • 状態の共有(shared state)

そして採用の広がりが速い。Google、LangChain(LangGraph)、AWS(Strands Agents / Bedrock AgentCore)、Microsoft Agent Framework、Mastra、Pydantic AI、CrewAI、Agno、LlamaIndex などが対応を表明しており、コミュニティベースではClaude Agent SDKのサポートもある。

ここまでで「繋ぐ」問題は解けた。では、その上を流れるUIの中身はどう記述するのか。ここで A2UI が出てくる。

A2UI ― UIそのものを宣言的に記述するプロトコル

A2UI(Agent-to-UI Protocol)は、Google発のオープンプロジェクトとして始まった宣言的なGenerative UIの仕様だ(CopilotKitなどがパートナーとして関わっている)。2026年時点で v0.9 が現行、v0.8 はレガシー扱いになっている。

A2UIが解くのは「エージェントが返すUIを、特定のフレームワークに依存しない形で、安全に、ストリーミングしながら記述する」という問題だ。

核となる3つの設計思想

1. 宣言的(declarative)= 任意コードを実行しない

A2UIでは、エージェントはJSONで「どんなUIか」を記述するだけで、JavaScriptのような実行コードは一切送らない。クライアントはそのJSONを自分の知っているネイティブ部品にマッピングしてレンダリングする。LLMが生成したコードをそのまま実行する怖さがないので、セキュリティ面で素直だ。

2. フレームワーク非依存(framework-agnostic)

エージェントは抽象的なコンポーネントツリーを送るだけ。それをWebではReactコンポーネントに、モバイルではネイティブウィジェットに、と各クライアントが自分のプラットフォームの部品に変換する。同じエージェントの出力が、Web・モバイル・デスクトップで動く。

3. ストリーミング前提・LLMフレンドリー

コンポーネントはID参照を持つフラットなリストとして表現される。ネストの深いツリーをそのまま生成させるのではなく、フラットな要素を逐次追加していく形にすることで、LLMが少しずつ生成し、途中で訂正し、ストリーミングしやすい。さらに、不完全なJSONを逐次パースして「治しながら(heal)」描画していく耐性も持つ。

メッセージ構造

A2UIは**JSONL(1行1JSON)**ベースのストリーミング形式で、典型的には3種類のメッセージを順に送ることで1つのUI「サーフェス」を組み立てる。

  1. surfaceUpdate ― コンポーネントの構造と階層を定義する(どんな要素があり、どういうIDで、どう入れ子になっているか)
  2. dataModelUpdate ― 各コンポーネントに実データを束ねる(表示する中身を流し込む)
  3. beginRendering ― 定義が揃ったので描画を始める合図(ルートコンポーネントと全体スタイルを指定)

ここで効いてくるのが、UIの構造とデータをきれいに分離している点だ。骨組み(surfaceUpdate)と中身(dataModelUpdate)を別メッセージに分けることで、構造はそのままにデータだけ差し替える、といったことがやりやすい。

概念的には、以下のようなJSONLが流れてくるイメージになる(説明用に簡略化したもの)。

jsonl
{"surfaceUpdate":{"surfaceId":"sales","components":[
  {"id":"root","card":{"children":["title","table"]}},
  {"id":"title","text":{"text":"今週の売上"}},
  {"id":"table","table":{"rowsRef":"/sales/rows"}}
]}}
{"dataModelUpdate":{"surfaceId":"sales","contents":{
  "sales":{"rows":[{"day":"月","amount":1200000},{"day":"火","amount":980000}]}
}}}
{"beginRendering":{"surfaceId":"sales","root":"root"}}

tableコンポーネントがrowsRefでデータモデル上のパス(/sales/rows)を参照しているところがポイントで、コンポーネントは「どこのデータを見るか」だけを持ち、実体はdataModelUpdate側にある。この間接参照のおかげで、構造とデータの分離が成立している。

注意: 上のフィールド名は仕様の考え方を伝えるための簡略例だ。正確なスキーマはA2UI v0.9の仕様を参照してほしい。

AG-UIとA2UIはどう関係するのか

ここまで読めば、両者が競合ではなく補完関係にあることが見えてくるはずだ。改めて整理する。

  • AG-UI = エージェントとフロントエンドを繋ぐランタイムの通信レイヤー(どう運ぶか)
  • A2UI = エージェントが返すUIを記述する宣言的なペイロード仕様(何を運ぶか)

つまり、A2UIで記述されたUIを、AG-UIのイベントストリームに載せて運ぶ、という組み合わせが成立する。実際CopilotKitは、AG-UIがA2UIをネイティブにサポートし、さらに開発者が独自のGenerative UI標準を定義することもできる、と説明している(AG-UI and A2UI)。

エージェントを取り巻くプロトコル全体の中に置くと、役割分担がさらにくっきりする。

プロトコル繋ぐ相手主導
MCPエージェント ↔ ツール・データAnthropic
A2Aエージェント ↔ エージェントGoogle
AG-UIエージェント ↔ フロントエンドCopilotKit
A2UIエージェント → UIの記述(ペイロード)Google

MCPがツール接続を、A2Aがエージェント間連携を担うのに対し、AG-UIは「エージェントと人間の接点」を、A2UIは「その接点に流すUIの中身」を標準化する。レイヤーがそれぞれ違う。

まとめ

  • Generative UIは、エージェントの出力をテキストからインタラクティブなUIへと拡張する流れ。チャットの先にある「アプリの中で働くエージェント」を実現する鍵になっている。
  • AG-UIは、エージェントとフロントエンドを繋ぐイベントベースの通信プロトコル。約16種のイベントでストリーミング・ツール呼び出し・状態同期・human-in-the-loopを標準化し、主要なエージェントフレームワークが続々と対応している。
  • A2UIは、Google発の宣言的なGenerative UI仕様。surfaceUpdate / dataModelUpdate / beginRenderingの3メッセージで、構造とデータを分離しつつフレームワーク非依存・ストリーミング前提でUIを記述する。
  • 名前は似ているが、AG-UIは「運ぶ」、A2UIは「中身」。両者は組み合わせて使うものだ。

エージェントが本当にアプリの一員になっていくなら、UIの生成と受け渡しの標準化は避けて通れない。AG-UIとA2UIは、その土台を別々のレイヤーから固めにいっている ── そう捉えると、この界隈の動きが少し見通しよくなるはずだ。

参考リンク