はじめに
以前、Amazon Bedrock AgentCoreのPayments機能を使った決済エージェントのアプリを作りました。
その際、フロントエンドには「assistant-ui」というライブラリを使い、エージェントとのやり取りにはAG-UIを使用しました。
assistant-uiは、AIチャットボットのようなUIを簡単に作れるフロントエンドライブラリです。
AG-UIはフロントエンドとAIエージェントを繋ぐためのプロトコルです。
今回は、assistant-uiのAG-UIを使って、AG-UI対応エージェントをどのようにして作ったかの話をします。
1. AG-UIの概要
AIエージェントの構成をどうするか
AIエージェントを作るとき、「ユーザーとエージェントが会話しながらタスクを進める」「途中でユーザーの承認を挟む」といった要件がある場合は、フロントエンドを用意する必要があります。
アンビエントエージェントという選択肢
バックグラウンドで自律的に動く「アンビエントエージェント」のように、ユーザーとGUI上の対話が不要なパターンもあります。
フロントエンドとAIエージェントを繋ぐ時の課題
フロントエンドとAIエージェントの間では、リアルタイムのストリーミングでやりとりするのが主流です。(ChatGPTなどでよく見る、徐々にエージェントの応答が表示されるあれです)
こうしたリアルタイムなストリーミング通信は、SSE(Server-Sent Events)やWebSocketで実現できます。
data: "今日の天気は"
data: "晴れです"
↓ SSEについては以下の記事が参考になります
ただ、SSEは「dataにデータを載せて流す」というところまではできますが、dataの中身としてどんな種類のイベントをどんな形式で流すかは、実装次第になります。(表現力に難あり)
そうなってくると、以下のような課題が発生します。
| 課題 | 内容 |
|---|---|
| SSEイベントの 設計・デバッグが大変 |
AIエージェントはテキスト以外にもツールの実行結果やHuman-in-the-loopなど様々な情報を返します。 SSEは、これらの情報が同じ dataとして流れてくるため、テキスト以外のイベントが混在すると区別するのがしんどくなります。 |
| エージェントSDKの 差し替えなど変化に弱い |
SSE通信部分の実装はエージェントSDKごとに必要です。 フロントエンド側もSDKごとに個別のSSEを解釈する処理を用意する必要があり、SDKを差し替えるとなると作り直しの可能性があります。 |
AG-UIを使ってエージェントとのやり取りを共通化しよう
AG-UI(Agent-User Interaction Protocol)は、AIエージェントとフロントエンドとの接続を標準化したイベントベースのプロトコルです。
AG-UIはSSEやWebSocketといった既存の通信方式の上で動作し、前述したような複雑なやり取りを、標準化されたイベントとして定義してくれています。
data: {"type": "TEXT_MESSAGE_CONTENT", "messageId": "msg_1", "delta": "今日の天気は"}
data: {"type": "TEXT_MESSAGE_CONTENT", "messageId": "msg_1", "delta": "晴れです"}
data: {"type": "TOOL_CALL_RESULT", "toolCallId": "call_1", "content": "..."}
フロントエンドは、SSEで流れてくるAG-UIイベントを解釈するだけで、エージェントの動きを柔軟にUIへ反映できます。
AIエージェントをAG-UIに対応させるには
AIエージェント側に求められるのは、実行中の出来事(応答テキスト・ツール呼び出しなど)をAG-UIイベントとしてフロントエンドに返すことです。
こうしたAG-UI対応エージェントを作るには、公式から提供されている「AG-UIアダプタ」を使用します。
フロントエンド側でもAG-UIイベントを受け取るための「AG-UIクライアント」を使用します。
「AG-UIアダプタ」は、使用するエージェントごとに用意されています。
作ったエージェントでは、AWSが提供するStrands Agentsを利用しており、専用のアダプタを使用しました。
AG-UIに対応したフロントエンドの選択肢
エージェント側をAG-UIに対応させたら、次はフロントエンドです。
正直、AG-UIに対応したフロントエンドライブラリは多くありません。Webアプリに対応しているものだと、現状は以下の2つが選択肢になるかと思います。
今回はこの中からassistant-uiを選びました。
| ライブラリ | 特徴 |
|---|---|
| CopilotKit | チャットUI・状態管理・Generative UIを事前構築済みUIとして提供 リッチなフロントエンドを作れるが、仕組みはちょっと複雑 |
| assistant-ui | ReactのチャットUIライブラリ 部品を組み合わせて、サクッと綺麗なUIを構築するスタイル |
2. assistant-uiの概要
assistant-uiはAIチャットを作れるUIライブラリ
assistant-uiは、ChatGPTのような綺麗なチャットUIをReactアプリに組み込むためのライブラリです。
既存のテーマを使わず、自分のアプリに合わせて一からUIを組み立てることもできます。
中身的には、コアランタイム、プロトコルレイヤー、フレームワークアダプタの3層から構成されています。
assistant-uiはAG-UIにも対応している
assistant-uiは、AG-UIにも対応しており、フレームワークアダプタとして「react-ag-ui」を提供します。
react-ag-uiのuseAgUiRuntimeフックでAG-UIイベントをassistant-uiが読める形に変換します。
エージェントと直接やりとりするのは、AG-UI公式が提供するAG-UIクライアントです。
@ag-ui/clientのHttpAgentでエージェントへの接続を定義して、エージェントとフロントエンドをAG-UIで繋ぎます。
3. 今回の実装内容について
ここからは、実際に作った内容を見ていきます。
全体アーキテクチャ
作ったアプリの全体構成は以下になります。AWS上にエージェントやフロントエンドを構築しています。
今回は図の左にある「フロントエンド」の実装について説明します。
インフラやエージェント周りは以下を参照ください。
フロントエンドのフォルダ構成
フロントエンド(Next.js)の主なフォルダ構成は以下です。
src/app
├── api
│ ├── agent
│ │ └── route.ts ← AgentCore RuntimeへのSSEプロキシ
│ └── paid/weather
│ └── route.ts ← x402対応のモックAPI(前回記事を参照)
├── components
│ ├── AssistantRuntimeProvider.tsx ← ランタイム接続
│ ├── AgentThread.tsx ← Thread UI
│ ├── AgentAssistant.tsx ← 上記2つをまとめて配置するコンポーネント
│ ├── paymentToolkit.ts ← Human-in-the-loopツールの定義
│ └── payment-tools/ ← ツールが表示するUIコンポーネント
├── providers/ ← Amplify認証・Privyのプロバイダー
├── layout.tsx
└── page.tsx
フロントエンドのパッケージインストール
assistant-uiのAG-UIランタイムを使うには、以下の3パッケージをインストールします。
| パッケージ | 役割 |
|---|---|
@assistant-ui/react |
assistant-ui本体。プロバイダーやUIプリミティブを提供する |
@assistant-ui/react-ag-ui |
AG-UIランタイムアダプター。useAgUiRuntimeを提供する |
@ag-ui/client |
AG-UIクライアント。HttpAgentを提供する |
AIエージェントとの接続(HttpAgent + useAgUiRuntime)
エージェントへの接続を設定して、assistant-uiのプロバイダーに渡します。
HttpAgentで接続先を指定し、useAgUiRuntimeでランタイムに変換します。
| 部品 | 役割 |
|---|---|
HttpAgent |
AG-UIエンドポイントにHTTP接続する |
useAgUiRuntime |
AG-UIイベントをassistant-uiのランタイムに変換する |
AssistantRuntimeProvider |
ランタイムをUIコンポーネント群に配る |
ThreadPrimitive / ComposerPrimitive
|
実際のチャットUIを描画する |
AG-UIが提供するHttpAgentを使って、接続先のエージェントを設定します。
今回はNext.jsのAPI Routeを使ってAPIを作っているので、そのAPIをurlに指定します。
fetchでリクエスト送信処理を差し替えられるので、Cognitoのjwtとユーザー情報をカスタムヘッダーに付与しています。
const agent = useMemo(
() =>
new HttpAgent({
url: "/api/agent",
fetch: async (url, requestInit) => {
const session = await fetchAuthSession();
const accessToken = session.tokens?.accessToken?.toString();
const userSub =
session.tokens?.idToken?.payload.sub ??
session.tokens?.accessToken?.payload.sub;
const userEmail = session.tokens?.idToken?.payload.email;
const headers = new Headers(requestInit?.headers);
if (accessToken) {
headers.set("Authorization", `Bearer ${accessToken}`);
}
if (typeof userSub === "string") {
headers.set(AGENTCORE_USER_SUB_HEADER, userSub);
}
if (typeof userEmail === "string") {
headers.set(AGENTCORE_USER_EMAIL_HEADER, userEmail);
}
return window.fetch(url, {
...requestInit,
headers,
});
},
}),
[],
);
次に、assistant-uiが提供するuseAgUiRuntimeを使ってランタイムを作ります。
このとき、先ほど作成したagentを渡します。
これでAG-UIイベントをassistant-uiが解釈できる形に変換します。
const runtime = useAgUiRuntime({ agent, showThinking: false });
あとは、assistant-uiが提供するAssistantRuntimeProviderのruntimeに渡すだけです。
return (
<AssistantRuntimeProvider aui={aui} runtime={runtime}>
{children}
</AssistantRuntimeProvider>
);
最終的に以下のような形になりました。
接続先となるAPI Routeの実装は以下のとおりです。
受け取ったリクエストをAgentCore Runtimeの呼び出しURLに転送し、返ってきたSSEストリームをそのままフロントエンドへ流します。
まとめ
今回は、AG-UI対応のエージェントのフロントエンドにassistant-uiを使ってみました。
CopilotKitは、バックエンド層が必要ですが、AG-UIやGenerative UIの共通プロトコル(A2UIなど)の最新を追従しているので、多機能なフロントエンドを構築できます。
一方、assistant-uiは、バックエンドが必須ではないため、バックエンドを挟まずにフロントエンドをサクッと作りたい場合に向いていると思います。
| 比較観点 | CopilotKit | assistant-ui | 評価理由 |
|---|---|---|---|
| 機能面 | ○ | △ | CopilotKitは主要機能を標準で提供している。 assistant-uiは一部(会話処理の中断・再開、複数の会話管理)が試験提供にとどまる |
| Generative UIの対応力 | ○ | △ | CopilotKitはA2UI、MCP Appsなど共通仕様に対応。assistant-uiはMCP Appsに対応しているが、A2UIには未対応(独自実装はある) |
| 導入しやすさ | △ | ○ | CopilotKitは基本的にバックエンドを挟む必要がある。assistant-uiはフロントからAgentへ直接接続できる |
| AG-UI追従度 | ○ | △ | CopilotKitはAG-UI公式が1st-partyとして扱っている。 assistant-uiは独自の公式AdapterでAG-UIに対応している。対応イベントの詳細は公式ドキュメントを参照 |
○:標準機能として対応
△:一部未対応、または試験提供中
×:未対応





