Un chat para gobernarlos a todos: construimos una biblioteca de asistente de IA basada en Gravity UI

Un artículo sobre el lanzamiento de la biblioteca AIKit: en qué nos enfocamos durante el desarrollo, por qué es necesaria y cómo usarla en tus propios proyectos.

Durante el último año hemos visto un boom de asistentes de IA, y esto también ha llegado a las interfaces de Yandex Cloud: a veces aparece un chatbot con un modelo en el soporte técnico, y otras — en la consola — un agente para operaciones de trabajo. Los equipos conectaban modelos, diseñaban la lógica conversacional, dibujaban el diseño y montaban chats — y lo hacían todo por separado.

Distintos equipos construían interfaces sobre el framework común Gravity UI, pero poco a poco surgieron tantas variaciones que se volvió difícil mantener una experiencia de usuario unificada. Y además, los compañeros cada vez se encontraban más con que perdían tiempo resolviendo una y otra vez lo mismo.

Para dejar de reinventar la rueda cada vez, reunimos las prácticas acumuladas en un enfoque único e hicimos una herramienta para chatbots con IA — @gravity‑ui/aikit. Permite crear una interfaz completa de asistente en pocos días y, al mismo tiempo, adaptarla fácilmente a distintos escenarios.

Full screen image

Me llamo Ilia Lomtev, soy desarrollador senior en el equipo Foundation Services de Yandex Cloud, y en este artículo contaré por qué decidimos crear AIKit, cómo está organizado, un poco sobre los planes a futuro — y sobre lo que podéis probar por vuestra cuenta.

Cómo y por qué creamos AIKit

Durante el último año, en Yandex Cloud creció el número de servicios con asistentes de IA, por ejemplo:

  • Code Assistant Chat en SourceCraft — el asistente ayuda a los desarrolladores a escribir código y, en modo de agente de IA, crea y configura repositorios, lanza procesos CI/CD, responde preguntas sobre la documentación y automatiza tareas. También sabe gestionar issues y pull requests, y trabajar con el código: explicar, crear y editar archivos.

  • Asistente de IA en la consola de la nube — un asistente diseñado para gestionar recursos en Yandex Cloud. La tarea principal es ayudar a configurar, modificar y administrar la infraestructura cloud de forma rápida y segura, ocultando la complejidad de la interacción con la API y las herramientas.

En el ecosistema aparecieron una decena de chats, cada uno con su propia lógica, su formato de mensajes y su conjunto de casos borde.

Descubrimos que los equipos llegan a un conjunto de tareas más o menos similar. Lo que la mayoría necesita:

  • mostrar con cuidado los mensajes del usuario y del asistente,

  • organizar correctamente el streaming de respuestas,

  • mostrar el indicador “el asistente está escribiendo”,

  • manejar errores como una conexión cortada o reintentos.

Las tareas son esencialmente las mismas, pero hay muchas formas de resolverlas, y el UX difiere. Por ejemplo, la ubicación y la forma de mostrar el historial de chats: puede ser una pantalla aparte que se abre como un menú, o una lista de chats en un popup.

Se manifestó un problema: la experiencia entre distintos chats difería de manera significativa. En algunos lugares el asistente hacía streaming de la respuesta, y en otros mostraba de inmediato el texto ya listo. En una interfaz los mensajes se agrupaban, y en otra iban en una cinta continua. Esto rompía el UX común: el usuario pasa de un producto a otro dentro del mismo ecosistema, pero las sensaciones con el asistente son completamente distintas.

Full screen image

Ejemplos de chats construidos con AIKit, en tema claro

Además, se hizo evidente que desplegar nuevas funcionalidades en los modelos se volvía cada vez más difícil. Para comunicar a los usuarios, por ejemplo, capacidades de herramientas, multimodalidad o respuestas estructuradas de las tools, había que acordar el contrato, mejorar los backends y luego actualizar el UI en cada equipo por separado. En esas condiciones, cualquier cambio llevaba mucho tiempo y escalaba mal.

Queríamos frenar este crecimiento de variabilidad y recuperar la previsibilidad. Para ello, era necesario unificar el modelo de datos y los patrones de trabajo, ofrecer componentes y hooks listos para usar para que los equipos no tuvieran que empezar desde cero, y dejar espacio para la personalización — porque los escenarios de cada uno son diferentes.

Así llegamos a la idea de una biblioteca separada @gravity‑ui/aikit — una extensión de Gravity UI que sigue los mismos principios, pero está orientada a escenarios modernos de IA: diálogos, asistentes, multimodalidad.

Arquitectura de AIKit: en qué nos basamos

Al diseñar AIKkit, nos orientamos por la experiencia de AI SDK y varios principios fundamentales.

Atomic Design como base: toda la biblioteca se construye desde átomos hasta páginas. Esta estructura aporta una jerarquía clara, permite reutilizar componentes y, si es necesario, cambiar el comportamiento en cualquier nivel.

Full screen image

Totalmente agnóstico al SDK: AIKit no depende de un proveedor de IA concreto. Se puede usar OpenAI, Alice AI LLM o tu propio backend — el UI recibe los datos mediante props, y el estado y las peticiones se quedan del lado del producto.

Dos niveles de uso para escenarios complejos: hay un componente listo que funciona “out of the box”, y hay un hook con lógica que permite controlar completamente el UI. Por ejemplo, se puede usar PromptInput o construir tu propio campo de entrada sobre usePromptInput. Esto da flexibilidad sin necesidad de reescribir los cimientos.

Sistema de tipos extensible. Para garantizar uniformidad y seguridad de tipos, reunimos un modelo de datos extensible. Los mensajes se representan con una estructura tipada única: hay mensajes del usuario, mensajes del asistente y varios tipos básicos de contenido — texto (text), razonamientos del modelo (thinking), herramientas (tool). Al mismo tiempo, se pueden añadir tipos propios mediante MessageRendererRegistry.

Todo está tipado en TypeScript, lo que ayuda a construir escenarios complejos más rápido y evitar errores en la fase de desarrollo.

// 1. Definimos el tipo de datos
type ChartMessageContent = TMessageContent<
    'chart',
    {
        chartData: number[];
        chartType: 'bar' | 'line';
    }
>;
// 2. Creamos el componente de renderizado
const ChartRenderer = ({part}: MessageContentComponentProps<ChartMessageContent>) => {
    return <div>Visualización del gráfico: {part.data.chartType}</div>;
};
// 3. Registramos el renderer
const customRegistry = registerMessageRenderer(createMessageRendererRegistry(), 'chart', {
    component: ChartRenderer,
});
// 4. Lo usamos en AssistantMessage
<AssistantMessage message={message} messageRendererRegistry={customRegistry} />;

Por último, contemplamos tematización mediante variables CSS, añadimos i18n (RU/EN), garantizamos accesibilidad (ARIA, navegación por teclado) y configuramos pruebas de regresión visual con Playwright Component Testing en Docker — y la biblioteca quedó lista para uso en producción.

Qué hay bajo el capó

En la base de AIKit hay un modelo de diálogo unificado. Para crearlo, primero hubo que entender la jerarquía de los mensajes.

Los mensajes son entidades bastante poliédricas. Existe el primer mensaje del LLM — es un stream. Pero dentro de él puede haber muchos mensajes anidados diferentes: en esencia son razonamientos, propuestas, llamadas a tools para resolver una pregunta. Todos esos submensajes diferentes son, de hecho, un solo mensaje del backend. Pero también cada uno de ellos puede perfectamente ser un mensaje separado en un uso simple de un LLM.

Por eso dejamos la posibilidad de usar el chat de ambas maneras: los mensajes pueden estar anidados unos dentro de otros, o pueden ser planos — aquí todo depende de la necesidad.

La gestión de estado, al mismo tiempo, se queda del lado del servicio. AIKit no almacena los datos por sí mismo — los recibe desde fuera. Los equipos pueden usar React State, Redux, Zustand, Reatom — lo que sea conveniente. Nosotros solo damos hooks que encapsulan la lógica típica de UI, por ejemplo:

  • scroll inteligente con useSmartScroll;

  • trabajo con fechas, por ejemplo, formateo de fechas teniendo en cuenta la locale con useDateFormatter;

  • manejo de mensajes de tools con useToolMessage;

  • y todo lo demás que se necesita para construir un diálogo.

Además de esto, AIKit sigue siendo extensible. Se pueden conectar cualquier modelo, crear tipos de contenido propios y construir el UI totalmente a medida — usando la lógica de los hooks o utilizando componentes listos como base. La arquitectura permite experimentar sin romper los principios comunes.

Cómo crear tu propio chat

Para crear nuestro primer chat, usaremos el componente preparado 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” todo se ve así:

Full screen image

Añadamos un poco de ambiente festivo:

  1. Ajustemos el estado inicial.

    Para una configuración más fina, montaremos el chat a partir de componentes separados: 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="Pregunta lo que sea..." />
            </div>
        );
    }
    
  2. Usemos distintos tipos de mensajes integrados, importados mediante MessageType.

    • thinking — mostrará el proceso de razonamiento de la IA (así el usuario puede estudiar la lógica con la que el asistente prepara la respuesta).

    • tool — sirve para mostrar bloques interactivos de la respuesta; en nuestro caso, es un bloque con código, donde el resaltado de sintaxis funciona correctamente y se soportan operaciones de edición y copia al portapapeles.

    También se pueden añadir tipos propios, por ejemplo, mensajes con imágenes:

    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. Añadamos estilización mediante CSS…

    …y obtendremos un chat con Ded Moroz:)

Full screen image

Para una personalización completa de elementos individuales se pueden usar hooks — ¡nos encantará ver vuestras variantes de estilo en los comentarios bajo el artículo!

Cómo AIKit influyó en los servicios

El resultado del uso de AIKit en Yandex Cloud se notó rápidamente. En todos los servicios, los asistentes empezaron a comportarse igual: hacer streaming de respuestas de la misma manera, mostrar errores de la misma manera, agrupar mensajes de la misma manera. El UX se volvió uniforme; ahora es más fácil interactuar con él en todo el ecosistema, el comportamiento es más esperado y predecible.

  • El lenguaje de UX se volvió unificado — los chats de asistentes en distintos productos ahora se sienten como parte de un mismo ecosistema. Los usuarios ven un comportamiento predecible: el mismo streaming, el mismo manejo de errores, patrones de interacción comunes.

  • La velocidad de desarrollo del UI del chat es mucho mayor.

  • Evolución centralizada — nuevas funcionalidades como el tipo de contenido thinking o un mejor trabajo con tools se añaden una sola vez y quedan disponibles automáticamente para todos.

  • La biblioteca se convirtió en la base para la formación de estándares de interfaces de IA en el ecosistema.

Qué sigue

Ahora, sobre los planes. Identificamos varias direcciones:

  • Mejorar el rendimiento mediante virtualización para trabajar con historiales de chat muy grandes.

  • Ampliar los escenarios base para nuevas capacidades de los agentes de IA, que se están desarrollando activamente.

  • Añadir utilidades para simplificar el mapeo de datos de modelos de IA populares a nuestro modelo de datos del chat.

Además, desarrollaremos la documentación y los ejemplos. Y, por supuesto, el crecimiento de la comunidad — queremos que la biblioteca sea útil no solo dentro de la compañía, sino también para desarrolladores externos.

Cómo probar AIKit

Entrad en la sección de la biblioteca en nuestro sitio web. Si estáis creando vuestro propio asistente de IA, queréis una interfaz de chat rápida y predecible y ya usáis Gravity UI (o estáis dispuestos a probarlo), echad un vistazo al README y a los ejemplos. Y también agradeceremos el feedback — abrid issues, enviad PRs y contadnos qué más necesitáis para vuestros escenarios.

Si le gusta nuestro proyecto, estaremos encantados de recibir ⭐️ en AIKitUIKit!

Un chat para gobernarlos a todos: construimos una biblioteca de asistente de IA basada en Gravity UI

Sign in to save this post