Un chat pour tous les gouverner: nous avons créé une bibliothèque d’assistant IA basée sur Gravity UI

Un article sur le lancement de la bibliothèque AIKit: sur quoi nous nous sommes concentrés pendant le développement, pourquoi elle est nécessaire et comment l’utiliser dans vos propres projets.

Au cours de l’année écoulée, nous observons un boom des assistants IA, et cela a aussi touché les interfaces de Yandex Cloud: tantôt un chatbot avec un modèle est apparu au support, tantôt — dans la console — un agent pour les opérations courantes. Les équipes connectaient des modèles, réfléchissaient à la logique conversationnelle, concevaient le design et assemblaient des chats — et elles faisaient tout cela chacune de leur côté.

Différentes équipes construisaient des interfaces sur le framework commun Gravity UI, mais peu à peu il est apparu tellement de variantes qu’il est devenu difficile de maintenir une expérience utilisateur cohérente. Et les collègues se retrouvaient de plus en plus souvent à perdre du temps à résoudre les mêmes problèmes.

Pour arrêter de réinventer la roue à chaque fois, nous avons rassemblé les pratiques accumulées dans une approche unique et créé un outil pour les chatbots IA — @gravity‑ui/aikit. Il permet de créer une interface d’assistant complète en quelques jours et, en même temps, de l’adapter facilement à différents scénarios.

Full screen image

Je m’appelle Ilia Lomtev, je suis développeur senior dans l’équipe Foundation Services de Yandex Cloud, et dans cet article je vais expliquer pourquoi nous avons décidé de construire AIKit, comment il est conçu, un peu de nos plans pour l’avenir — et ce que vous pouvez essayer de votre côté.

Comment et pourquoi nous avons créé AIKit

Au cours de l’année écoulée, le nombre de services avec des assistants IA dans Yandex Cloud a augmenté, par exemple:

  • Code Assistant Chat dans SourceCraft — l’assistant aide les développeurs à écrire du code et, en mode agent IA, crée et configure des dépôts, lance des processus CI/CD, répond aux questions sur la documentation et automatise des tâches. Il sait aussi gérer les issues et les pull requests, et travailler avec le code: expliquer, créer et éditer des fichiers.

  • Assistant IA dans la console cloud — un assistant conçu pour gérer les ressources dans Yandex Cloud. L’objectif principal est d’aider à configurer, modifier et administrer l’infrastructure cloud rapidement et en toute sécurité, en masquant la complexité de l’interaction avec l’API et les outils.

Dans l’écosystème, une dizaine de chats sont apparus, chacun avec sa logique, son format de messages et son ensemble de cas limites.

Nous avons constaté que les équipes aboutissent à un ensemble de tâches à peu près similaire. Ce dont la majorité a besoin:

  • afficher proprement les messages de l’utilisateur et de l’assistant,

  • organiser correctement le streaming des réponses,

  • afficher l’indicateur « l’assistant écrit “,

  • gérer des erreurs comme une connexion interrompue ou des retries.

Les tâches sont essentiellement les mêmes, mais il existe de nombreuses façons de les résoudre, et l’UX diffère. Par exemple, l’emplacement et la manière d’afficher l’historique des chats: cela peut être un écran séparé qui s’ouvre comme un menu, ou une liste de chats dans un popup.

Un problème est apparu: l’expérience dans les différents chats différait sensiblement. Ici l’assistant streamait la réponse, là il affichait immédiatement le texte final. Dans une interface, les messages étaient regroupés, dans une autre — ils défilaient en un flux continu. Cela cassait l’UX global — l’utilisateur passe d’un produit à l’autre au sein du même écosystème, mais les sensations avec l’assistant sont complètement différentes.

Full screen image

Exemples de chats construits avec AIKit, en thème clair

Il est également devenu évident que le déploiement de nouvelles fonctionnalités dans les modèles devenait de plus en plus difficile. Pour apporter aux utilisateurs, par exemple, les capacités d’outils, la multimodalité ou des réponses d’outils structurées, il fallait aligner le contrat, faire évoluer les backends, puis mettre à jour l’UI dans chaque équipe séparément. Dans ces conditions, toute modification prenait beaucoup de temps et se prêtait mal au passage à l’échelle.

Nous voulions arrêter cette croissance de la variabilité et retrouver de la prévisibilité. Pour cela, il fallait unifier le modèle de données et les patterns de travail, fournir des composants et des hooks prêts à l’emploi afin que les équipes n’aient pas à repartir de zéro, et laisser de la place à la personnalisation — car les scénarios diffèrent pour chacun.

C’est ainsi que nous en sommes arrivés à l’idée d’une bibliothèque séparée @gravity‑ui/aikit — une extension de Gravity UI qui suit les mêmes principes, mais qui est orientée vers les scénarios IA modernes: dialogues, assistants, multimodalité.

Architecture d’AIKit: sur quoi nous nous sommes appuyés

En concevant AIKkit, nous nous sommes appuyés sur l’expérience de AI SDK ainsi que sur plusieurs principes fondamentaux.

Atomic Design comme fondation: toute la bibliothèque est construite des atomes aux pages. Cette structure donne une hiérarchie claire, permet de réutiliser les composants et, si besoin, de modifier le comportement à n’importe quel niveau.

Full screen image

Entièrement agnostique du SDK: AIKit ne dépend pas d’un fournisseur d’IA particulier. On peut utiliser OpenAI, Alice AI LLM ou son propre backend — l’UI reçoit les données via des props, tandis que l’état et les requêtes restent du côté du produit.

Deux niveaux d’utilisation pour des scénarios complexes: il existe un composant prêt à l’emploi qui fonctionne « out of the box “, et un hook contenant la logique, qui permet de contrôler entièrement l’UI. Par exemple, on peut utiliser PromptInput ou construire son propre champ de saisie sur la base de usePromptInput. Cela apporte de la flexibilité sans devoir réécrire les fondations.

Système de types extensible. Pour assurer l’uniformité et la sécurité de typage, nous avons construit un modèle de données extensible. Les messages sont représentés par une structure unique typée: il y a des messages utilisateur, des messages assistant et plusieurs types de contenu de base — texte (text), raisonnement du modèle (thinking), outils (tool). En même temps, on peut ajouter ses propres types via MessageRendererRegistry.

Tout est typé en TypeScript, ce qui aide à construire plus vite des scénarios complexes et à éviter des erreurs dès la phase de développement.

// 1. Définir le type de données
type ChartMessageContent = TMessageContent<
    'chart',
    {
        chartData: number[];
        chartType: 'bar' | 'line';
    }
>;
// 2. Créer le composant de rendu
const ChartRenderer = ({part}: MessageContentComponentProps<ChartMessageContent>) => {
    return <div>Visualisation du graphique : {part.data.chartType}</div>;
};
// 3. Enregistrer le renderer
const customRegistry = registerMessageRenderer(createMessageRendererRegistry(), 'chart', {
    component: ChartRenderer,
});
// 4. Utiliser dans AssistantMessage
<AssistantMessage message={message} messageRendererRegistry={customRegistry} />;

Enfin, nous avons prévu la thématisation via des variables CSS, ajouté l’i18n (RU/EN), assuré l’accessibilité (ARIA, navigation au clavier) et configuré des tests de régression visuelle via Playwright Component Testing dans Docker — et la bibliothèque était prête pour un usage en production.

Ce qu’il y a sous le capot

Au cœur d’AIKit se trouve un modèle de dialogue unique. Pour le créer, il a d’abord fallu comprendre la hiérarchie des messages.

Les messages sont des entités assez polyvalentes. Il y a le premier message du LLM — c’est un stream. Mais à l’intérieur, il peut y avoir beaucoup de messages imbriqués différents: en substance, ce sont des raisonnements, des propositions, des appels à des outils pour résoudre une question. Tous ces sous-messages différents constituent en réalité un seul message provenant du backend. Mais chacun d’entre eux peut aussi très bien être un message distinct dans une utilisation simple d’un LLM.

C’est pourquoi nous avons laissé la possibilité d’utiliser le chat des deux manières: les messages peuvent être imbriqués les uns dans les autres, ou être plats — tout dépend du besoin.

La gestion d’état, quant à elle, reste du côté du service. AIKit ne stocke pas les données lui-même — il les reçoit de l’extérieur. Les équipes peuvent utiliser React State, Redux, Zustand, Reatom — ce qui leur convient. Nous fournissons seulement des hooks qui encapsulent la logique UI typique, par exemple:

  • un défilement intelligent avec useSmartScroll;

  • le travail avec les dates, par exemple le formatage des dates selon la locale avec useDateFormatter;

  • le traitement des messages d’outils avec useToolMessage;

  • et tout le reste nécessaire à la construction d’un dialogue.

En plus de cela, AIKit reste extensible. On peut connecter n’importe quels modèles, créer ses propres types de contenu et construire une UI entièrement adaptée à ses tâches — en s’appuyant sur la logique des hooks ou en utilisant des composants prêts à l’emploi comme base. L’architecture permet d’expérimenter sans violer les principes communs.

Comment construire son propre chat

Pour créer notre premier chat, utilisons le composant préparé 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) => {
        // Your message sending logic
        const response = await fetch('/api/chat', {
            method: 'POST',
            body: JSON.stringify({ message: content })
        });
        const data = await response.json();

        // Update state
        setMessages(prev => [...prev, data]);
    };

    return (
  <ChatContainer
    messages={[]}
    onSendMessage={() => {}}
    welcomeConfig={{
      description: 'Start a conversation by typing a message or selecting a suggestion.',
      image: <Icon data={() => {}} size={48}/>,
      suggestionTitle: 'Try asking:',
      suggestions: [
        {
          id: '1',
          title: 'Explain quantum computing in simple terms'
        },
        {
          id: '2',
          title: 'Write a poem about nature'
        },
        {
          id: '3',
          title: 'Help me debug my JavaScript code'
        },
        {
          id: '4',
          title: 'Summarize recent AI developments'
        }
      ],
      title: 'Welcome to AI Chat'
    }}
/>
        
    );
}

“ Out of the box ‘, tout ressemble à ceci:

Full screen image

Ajoutons un peu d’ambiance festive:

  1. Corrigeons l’état initial.

    Pour un réglage plus fin, assemblons le chat à partir de composants séparés: Header, MessageList, PromptBox.

    import { Header, MessageList, PromptBox } from 'aikit';
    function CustomChat() {
        return (
            <div className="custom-chat">
                <Header title="AI Assistant" onNewChat={() => {}} />
                <MessageList messages={messages} showTimestamp />
                <PromptBox onSend={handleSend} placeholder="Demandez n’importe quoi..." />
            </div>
        );
    }
    
  2. Utilisons différents types de messages intégrés, importés via MessageType.

    • thinking — affichera le processus de réflexion de l’IA (l’utilisateur peut ainsi étudier la logique selon laquelle l’assistant prépare la réponse).

    • tool — convient pour afficher des blocs de réponse interactifs; dans notre cas, c’est un bloc de code où la coloration syntaxique fonctionne correctement et où les opérations d’édition et de copie dans le presse-papiers sont prises en charge.

    On peut aussi ajouter ses propres types, par exemple des messages avec des images:

    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} />
    
  3. Ajoutons une stylisation via CSS…

    …et nous obtiendrons un chat avec Ded Moroz :)

Full screen image

Pour une personnalisation complète d’éléments individuels, vous pouvez utiliser des hooks — nous serons ravis de voir vos variantes de style dans les commentaires sous l’article!

Comment AIKit a influencé les services

Le résultat de l’utilisation d’AIKit dans Yandex Cloud s’est fait sentir rapidement. Dans tous les services, les assistants ont commencé à se comporter de la même manière: streamer les réponses de façon identique, afficher les erreurs de façon identique, regrouper les messages de façon identique. L’UX est devenu uniforme; il est désormais plus simple d’interagir avec lui dans l’ensemble de l’écosystème, le comportement est plus attendu et plus prévisible.

  • Le langage UX est devenu unique — les chats d’assistants dans différents produits sont désormais perçus comme faisant partie d’un même écosystème. Les utilisateurs voient un comportement prévisible: même streaming, même gestion des erreurs, mêmes patterns d’interaction.

  • La vitesse de développement de l’UI du chat est nettement plus élevée.

  • Développement centralisé — de nouvelles fonctionnalités comme le type de contenu thinking ou une meilleure gestion des outils sont ajoutées une seule fois et deviennent automatiquement disponibles pour tous.

  • La bibliothèque est devenue une base pour la formation de standards d’interfaces IA dans l’écosystème.

Et ensuite

Parlons maintenant des plans. Nous avons identifié plusieurs axes:

  • Amélioration des performances via la virtualisation pour travailler avec des historiques de chat très volumineux.

  • Extension des scénarios de base pour les nouvelles capacités des agents IA, qui se développent activement.

  • Ajout d’utilitaires afin de simplifier le mapping des données de modèles IA populaires vers notre modèle de données de chat.

En plus, nous développerons la documentation et les exemples. Et bien sûr, le développement de la communauté — nous voulons que la bibliothèque soit utile non seulement en interne, mais aussi aux développeurs externes.

Comment essayer AIKit

Rendez-vous dans la section bibliothèque sur notre site. Si vous créez votre propre assistant IA, que vous voulez une interface de chat rapide et prévisible et que vous utilisez déjà Gravity UI (ou êtes prêt à l’essayer), jetez un œil au README et aux exemples. Et nous vous serons aussi reconnaissants pour vos retours — ouvrez des issues, envoyez des PR, dites-nous ce dont vous avez encore besoin pour vos scénarios!

Si vous aimez notre projet, nous serons ravis d’avoir une ⭐️ sur AIKit et UIKit!

Un chat pour tous les gouverner: nous avons créé une bibliothèque d’assistant IA basée sur Gravity UI

Sign in to save this post