Архитектура ИИ-ассистента BILLmanager: адаптеры LLM, SSE через POST и рендеринг Markdown
Habr AI · оригинал
Материал подготовлен автоматизированной редакционной системой. Факты можно сверить по указанному первоисточнику.

Команда ISPsystem описала техническую реализацию встроенного ИИ-консультанта в платформе BILLmanager. Решение использует паттерн адаптеров для интеграции различных LLM, передачу потоковых данных через SSE на базе POST-запросов и инкрементальный рендеринг Markdown в Angular.
Материал демонстрирует практический подход к решению типичных проблем интеграции LLM: необходимость поддержки разных провайдеров, ограничения браузерного API EventSource для POST-запросов и сложность контроля над отображением Markdown в веб-приложениях.
Фронтенд-разработчик ISPsystem Денис представил технический разбор создания ИИ-ассистента для платформы BILLmanager. Инструмент предназначен для автоматизации поиска ответов в документации и снижения нагрузки на службу поддержки. Ключевой архитектурной задачей стала поддержка различных языковых моделей от разных провайдеров, что потребовало внедрения паттерна адаптера. Разработчики определили минимальный интерфейс AiAdapter, включающий методы для получения обычного и потокового ответа. Это позволяет клиентам подключать собственные LLM или использовать встроенные решения, сохраняя при этом возможность гибкой настройки промптов и дополнительных параметров генерации.
Для передачи потоковых данных от модели к клиенту команда отказалась от WebSocket в пользу Server-Sent Events (SSE). Однако стандартный браузерный API EventSource оказался непригодным, так как он поддерживает только GET-запросы. Это создавало риск превышения лимита длины URL при передаче длинных промптов и приводило к нежелательному повторению запросов при обрыве соединения, что лишним образом тратило токены. В качестве решения была реализована передача SSE через POST-запрос. На серверной стороне, работающей на NestJS, это описывается декоратором @Sse с указанием метода RequestMethod.POST. Клиентская часть использует fetch или XMLHttpRequest для чтения потока данных, самостоятельно разбирая формат событий и накапливая текст ответа.
Особое внимание уделено отображению Markdown-ответов в интерфейсе Angular. Вместо простого преобразования в HTML через innerHTML, что ограничивает возможности интерактивности, разработчики используют парсинг текста в AST-дерево с помощью библиотек unified и remark-parse. Каждому типу узла дерева (заголовок, список, код) соответствует отдельный компонент Angular. Это достигается через кастомную директиву markdownTemplate и сервис, который хранит маппинг типов узлов на шаблоны. Такой подход обеспечивает полный контроль над рендерингом, позволяя добавлять функциональность вроде подсветки синтаксиса или кнопок копирования в блоках кода.
Для управления расходами администраторы могут устанавливать лимиты на количество сообщений в сутки, максимальную длину запроса и глубину истории диалога. Также ограничена параллельность: у одного пользователя одновременно может выполняться только один запрос к ИИ. Доступ к чату предоставляется только авторизованным пользователям платформы. Пользовательские адаптеры загружаются при старте сервера из специальной директории, что позволяет расширять функциональность без изменения основного кода приложения.