AIKitで構築されたライトテーマのチャット例

すべてを統べる一つのチャット:Gravity UIをベースにしたAIアシスタント向けライブラリを構築
すべてを統べる一つのチャット:Gravity UIをベースにしたAIアシスタント向けライブラリを構築
AIKitライブラリのリリースについての記事:開発で注目したこと、なぜ必要なのか、そしてプロジェクトでの活用方法。
昨年、AIアシスタントのブームを目の当たりにしました。これはYandex Cloudのインターフェースにも影響を与えました:テクニカルサポートにモデル付きのチャットボットが登場し、コンソールには作業操作用のエージェントが登場しました。チームはモデルを接続し、ダイアログロジックを考え、デザインを描き、チャットを組み立てていました — そしてこれらすべてを別々に行っていました。
さまざまなチームが共通のフレームワークGravity UIでインターフェースを構築していましたが、そこには非常に多くのバリエーションが生まれ、統一されたユーザー体験を維持することが困難になりました。また、同僚たちは同じ解決策に時間を費やしていることに気づき始めました。
毎回車輪の再発明をやめるために、蓄積されたプラクティスを統一されたアプローチにまとめ、AIを使ったチャットボット用のツール — @gravity‑ui/aikitを作りました。これにより、数日で本格的なアシスタントインターフェースを作成し、さまざまなシナリオに簡単に適応できます。

私はイリヤ・ロムテフ、Yandex CloudのFoundation Servicesチームのシニア開発者です。この記事では、AIKitを作ることにした理由、その構造、将来の計画、そして自分で試せることについてお話しします。
AIKitを作った理由
昨年、Yandex CloudではAIアシスタントを持つサービスの数が増加しました。例えば:
-
SourceCraftのCode Assistant Chat — アシスタントは開発者がコードを書くのを助け、AIエージェントモードではリポジトリを作成・設定し、CI/CDプロセスを起動し、ドキュメントに関する質問に答え、タスクを自動化します。また、issue、プルリクエストを管理し、コードを操作:説明、ファイルの作成と編集ができます。
-
クラウドコンソールのAIアシスタント — Yandex Cloudのリソース管理用に開発されたアシスタントです。主なタスクは、APIやツールとのインタラクションの複雑さを隠しながら、クラウドインフラストラクチャを迅速かつ安全に設定、変更、管理することです。
エコシステムには数十のチャットが登場し、それぞれが独自のロジック、メッセージフォーマット、コーナーケースのセットを持っていました。
チームは大体同じタスクセットに行き着くことがわかりました。ほとんどが必要としているもの:
-
ユーザーとアシスタントのメッセージをきれいに表示する
-
レスポンスのストリーミングを正しく整理する
-
「アシスタントが入力中」インジケーターを表示する
-
接続切断やリトライなどのエラーを処理する
タスクは本質的に同じですが、解決策は多く、UXも異なります。例えば、チャット履歴の配置と表示方法:メニューとして開く別の画面でも、ポップアップのチャットリストでも可能です。
問題が明らかになりました:異なるチャットでの体験が大きく異なっていました。あるところではアシスタントがレスポンスをストリーミングし、別のところでは完成したテキストをすぐに表示していました。あるインターフェースではメッセージがグループ化され、別のところでは連続したフィードでした。これが共通のUXを壊していました — ユーザーは同じエコシステムの製品間を移動しているのに、アシスタントからの感覚がまったく異なっていました。

また、モデルに新機能をロールアウトすることがますます困難になっていることに気づきました。ツール、マルチモーダリティ、ツールの構造化されたレスポンスなどをユーザーに届けるには、コントラクトを合意し、バックエンドを改善し、各チームで別々にUIを更新する必要がありました。このような状況では、変更に時間がかかり、スケールしにくかったのです。
この変動の成長を止め、予測可能性を取り戻したいと思いました。そのためには、データモデルと作業パターンを統一し、チームがゼロから始める必要がないように既製のコンポーネントとフックを提供し、シナリオは人それぞれ異なるためカスタマイズの余地を残す必要がありました。
こうして、別ライブラリ@gravity‑ui/aikitのアイデアに至りました — これはGravity UIの拡張で、同じ原則に従いながら、モダンなAIシナリオ:ダイアログ、アシスタント、マルチモーダリティに焦点を当てています。
AIKitのアーキテクチャ:何を基盤としたか
AIKitを設計する際、AI SDKの経験といくつかの基本原則を参考にしました。
基盤としてのAtomic Design:ライブラリ全体がアトムからページへと構築されています。このような構造は明確な階層を提供し、コンポーネントの再利用を可能にし、必要に応じてどのレベルでも動作を変更できます。

完全にSDK非依存:AIKitは特定のAIプロバイダーに依存しません。OpenAI、Alice AI LLM、または独自のバックエンドを使用できます — UIはpropsを通じてデータを受け取り、状態とリクエストは製品側に残ります。
複雑なシナリオ用の2つの使用レベル:「箱から出してすぐ使える」既製コンポーネントと、UIを完全にコントロールできるロジック付きフックがあります。例えば、PromptInputを使用するか、usePromptInputをベースに独自の入力フィールドを構築できます。これにより、基盤を書き直す必要なく柔軟性が得られます。
拡張可能な型システム。統一性と型安全性を確保するために、拡張可能なデータモデルを収集しました。メッセージは統一された型付き構造で表現されます:ユーザーメッセージ、アシスタントメッセージ、およびいくつかの基本コンテンツタイプ — テキスト(text)、モデルの思考(thinking)、ツール(tool)。また、MessageRendererRegistryを通じて独自のタイプを追加できます。
これはすべてTypeScriptで型付けされており、複雑なシナリオをより早く組み立て、開発段階でエラーを回避するのに役立ちます。
// 1. データ型を定義
type ChartMessageContent = TMessageContent<
'chart',
{
chartData: number[];
chartType: 'bar' | 'line';
}
>;
// 2. 表示コンポーネントを作成
const ChartRenderer = ({part}: MessageContentComponentProps<ChartMessageContent>) => {
return <div>チャートの可視化: {part.data.chartType}</div>;
};
// 3. レンダラーを登録
const customRegistry = registerMessageRenderer(createMessageRendererRegistry(), 'chart', {
component: ChartRenderer,
});
// 4. AssistantMessageで使用
<AssistantMessage message={message} messageRendererRegistry={customRegistry} />;
最後に、CSS変数によるテーマ設定、i18n(RU/EN)の追加、アクセシビリティ(ARIA、キーボードナビゲーション)の確保、DockerでのPlaywright Component Testingによる視覚的回帰テストの設定を行い、ライブラリは本番環境での使用準備が整いました。
内部構造
AIKitの基盤は、統一されたダイアログモデルです。これを作成するために、まずメッセージの階層を理解する必要がありました。
メッセージ自体はかなり多面的なエンティティです。LLMからの最初のメッセージは1つのストリームです。しかし、その中には多くの異なるネストされたメッセージがあり得ます:本質的に、これらは1つの質問を解決するための推論、提案、ツール呼び出しです。これらすべての異なるサブメッセージは、実際にはバックエンドからの1つのメッセージです。しかし、それぞれがシンプルなLLM使用では別個のメッセージになり得ます。
そのため、チャットを両方の方法で使用できるようにしました:メッセージは互いにネストすることも、フラットにすることもできます — ここではすべてニーズ次第です。
状態管理はサービス側に残ります。AIKitは自身でデータを保存しません — 外部から受け取ります。チームはReact State、Redux、Zustand、Reatomなど、便利なものを使用できます。私たちは典型的なUIロジックをカプセル化するフックを提供するだけです。例えば:
-
useSmartScrollによるスマートスクロール -
日付の操作、例えばロケールを考慮した日付フォーマット
useDateFormatter -
ツールメッセージの処理
useToolMessage -
ダイアログ構築に必要なその他すべて
これに加えて、AIKitは拡張可能なままです。任意のモデルを接続し、独自のコンテンツタイプを作成し、タスクに完全に合わせてUIを構築できます — フックのロジックを使用するか、既製のコンポーネントをベースとして使用します。アーキテクチャにより、共通の原則を壊すことなく実験できます。
自分のチャットを構築する方法
最初のチャットを作成するには、準備されたChatContainerコンポーネントを使用します:
import React, { useState } from 'react';
import { ChatContainer } from 'aikit';
import type { ChatType, MessageType } from 'aikit';
function App() {
const [messages, setMessages] = useState<MessageType[]>([]);
const [chats, setChats] = useState<ChatType[]>([]);
const [activeChat, setActiveChat] = useState<ChatType | null>(null);
const handleSendMessage = async (content: string) => {
// メッセージ送信ロジック
const response = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ message: content })
});
const data = await response.json();
// 状態を更新
setMessages(prev => [...prev, data]);
};
return (
<ChatContainer
messages={[]}
onSendMessage={() => {}}
welcomeConfig={{
description: 'メッセージを入力するか、提案を選択して会話を開始してください。',
image: <Icon data={() => {}} size={48}/>,
suggestionTitle: '試してみてください:',
suggestions: [
{
id: '1',
title: '量子コンピューティングを簡単に説明してください'
},
{
id: '2',
title: '自然についての詩を書いてください'
},
{
id: '3',
title: 'JavaScriptコードのデバッグを手伝ってください'
},
{
id: '4',
title: '最近のAI開発を要約してください'
}
],
title: 'AIチャットへようこそ'
}}
/>
);
}
「箱から出して」すべてがこのように見えます:

少し楽しさを加えましょう:
-
初期状態を修正します。
より細かい設定のために、個別のコンポーネントからチャットを組み立てます:
Header、MessageList、PromptBox。import { Header, MessageList, PromptBox } from 'aikit'; function CustomChat() { return ( <div className="custom-chat"> <Header title="AIアシスタント" onNewChat={() => {}} /> <MessageList messages={messages} showTimestamp /> <PromptBox onSend={handleSend} placeholder="何でも質問してください..." /> </div> ); } -
MessageTypeを通じてインポートされた異なる組み込みメッセージタイプを適用します。-
thinking— AIの思考プロセスを表示します(これにより、ユーザーはアシスタントが回答を準備するロジックを調べることができます)。 -
tool— インタラクティブなレスポンスブロックの表示に適しています。私たちのケースでは、構文ハイライトが正しく機能し、編集とクリップボードへのコピー操作がサポートされているコードブロックです。
また、独自のタイプを追加できます。例えば、画像付きメッセージ:
type ImageMessage = BaseMessage<ImageMessageData> & { type: 'image' }; const ImageMessageView = ({ message }: { message: ImageMessage }) => ( <div> <img src={message.data.imageUrl} /> {message.data.caption && <p>{message.data.caption}</p>} </div> ); const customTypes: MessageTypeRegistry = { image: { component: ImageMessageView, validator: (msg) => msg.type === 'image' } }; <ChatContainer messages={messages} messageTypeRegistry={customTypes} /> -
-
CSSでスタイリングを追加します…
…そしてサンタクロースとのチャットができあがります :)

個々の要素を完全にカスタマイズするには、フックを使用できます — 記事へのコメントであなたのスタイリングバリエーションを見せていただけると嬉しいです!
AIKitがサービスに与えた影響
Yandex CloudでAIKitを使用した結果はすぐに現れました。すべてのサービスでアシスタントが同じように動作するようになりました:同じようにレスポンスをストリーミングし、同じようにエラーを表示し、同じようにメッセージをグループ化します。UXが統一され、エコシステム全体でのインタラクションがより簡単になり、動作がより期待通りで予測可能になりました。
-
UX言語が統一されました — 異なる製品のアシスタントチャットが同じエコシステムの一部として感じられるようになりました。ユーザーは予測可能な動作を見ます:同一のストリーミング、エラー処理、インタラクションパターン。
-
チャットUIの開発速度が大幅に向上しました。
-
集中化された開発 — thinkingコンテンツタイプやツールとの改善された連携などの新機能は一度追加され、自動的にすべてで利用可能になります。
-
ライブラリはエコシステム内のAIインターフェース標準を形成する基盤となりました。
今後の計画
計画についてお話しします。いくつかの方向性を特定しました:
-
非常に大きなチャット履歴を扱うための仮想化によるパフォーマンス向上。
-
積極的に発展しているAIエージェントの新機能に対応する基本シナリオの拡張。
-
人気のあるAIモデルのデータをチャットデータモデルにマッピングするのを簡素化するユーティリティの追加。
さらに、ドキュメントと例を発展させていきます。そしてもちろん、コミュニティの発展 — ライブラリが社内だけでなく外部の開発者にも役立つことを望んでいます。
AIKitを試す方法
当サイトのライブラリセクションをご覧ください。独自のAIアシスタントを作成していて、高速で予測可能なチャットインターフェースが必要で、すでにGravity UIを使用している(または試す準備ができている)場合は、READMEと例をご覧ください。フィードバックをいただけると嬉しいです — issueを作成し、PRを送り、あなたのシナリオに他に何が必要か教えてください!

イリヤ・ロムテフ
フロントエンド開発者