Режим фокусировки

Создание адаптера для ИИ-ассистента

В статье используются следующие термины:

В статье описаны принципы работы, пошаговая инструкция и API модуля для создания собственного адаптера ИИ-ассистента. Как настроить работу с ИИ-ассистентом в интерфейсе платформы см. в статье Работа с ИИ-ассистентом.  

Что такое адаптер

Адаптер — модуль, который связывает чат с внешним LLM-провайдером (OpenAI-совместимый API, Yandex GPT, собственный сервис и т.п.). Сервер чата не обращается к провайдеру напрямую: он передаёт запрос зарегистрированному адаптеру, а адаптер возвращает ответ в едином внутреннем формате.

Возможности механизма адаптеров:

  • подключение произвольного LLM без изменения кода сервера;
  • использование нескольких адаптеров в одном экземпляре чата. Например, для разных провайдеров можно использовать различные адаптеры;
  • поддержка потоковой (SSE) и стандартной генерации ответа;
  • управление моделями: адаптер может опционально передавать список поддерживаемых моделей для выбора в настройках BILLmanager;
  • реализация собственной логики работы с LLM. Например, вызов функций (function calling), фильтрация запросов и т.д;
  • передача API-ключа, URL-адреса и системного промпта из настроек проекта.

Собственные адаптеры загружаются автоматически при старте сервера чата из каталога /app/server/adapters / внутри Docker-контейнера. Пересборка образа не требуется — достаточно разместить файл адаптера в контейнере и перезапустить контейнер.

Устройство работы чата
Устройство работы чата

Встроенные адаптеры

После установки модуля "ИИ-ассистент" в платформе доступны следующие адаптеры:

ИмяОписание
openaiАдаптер для OpenAI-совместимых LLM. Например, YandexGPT, GigaChat.
deep-seekАдаптер для общения с DeepSeek.

Создание адаптера

Ниже приведена инструкция по созданию адаптера для общения с LLM через пакет OpenAI. В примере использовано название адаптера my-llm. Вместо my-llm вы можете указать другое имя.
Чтобы создать адаптер для общения с LLM через пакет OpenAI:

  1. Создайте файл адаптера [имя-адаптера].adapter.js. Например, my-llm.adapter.js.
  2. Создайте класс адаптера и реализуйте необходимые методы:
    • getName — получение имени адаптера;
    • getCompletion — получение ответа от ИИ без SSE;
    • getCompletionStream — получение ответа от ИИ с SSE.
    Подробное описание API для входных и выходных параметров см. ниже;
    Пример создания адаптера
  3. Скопируйте файл адаптера в каталог /app/server/adapters работающего контейнера чата. Чтобы сохранить добавленные адаптеры после пересоздания образа, создайте свой образ на базе Docker-образа чата.

    Пример создания образа с помощью Dockerfile
    FROM docker-registry.ispsystem.com/ispsystem5/chat:1.0.0
    
    WORKDIR /app
    COPY ./my-llm.adapter.js ./server/adapters/my-llm.adapter.js
    Где:
    • docker-registry.ispsystem.com/ispsystem5/chat:1.0.0 — адрес к публичному образу контейнера чата. 
  4. Создайте образ из Dockerfile:
     docker build . -t my-llm 
  5. Укажите тег созданного образа в скрипте для управления образами чата /usr/local/mgr5/etc/docker/services/aichat.sh. Замените переменную SERVICE_IMAGE на тег нового образа SERVICE_IMAGE="my-llm"
  6. Перезапустите контейнер чата:
    sh etc/docker/services/aichat.sh restart 
  7. В веб-интерфейсе BILLmanager перейдите в раздел ИИ-ассистентНастройки → выберите ИИ-ассистента → кнопка Изменить.
  8. В блоке Основное в поле Адаптер выберите новый адаптер.
  9. Задайте необходимые настройки: API URL, API-ключ. Подробнее см. статью Работа с ИИ-ассистентом.
  10. Сохраните изменения.

После настройки чат готов к использованию. Запросы будут отправляться через созданный адаптер.

Написание адаптера с внешними зависимостями

Сервис чата импортирует файлы, оканчивающиеся на  .adapter.js . Если в этом файле нужно использовать зависимости (например, директорию node_modules) или пакеты, их нужно также перенести внутрь контейнера в директорию /app/server/adapters/.
При перемещении каталога node_modules внутрь контейнера зависимости становятся доступны через импорт require.

Пример импорта
// Библиотека OpenAI доступна для импорта по умолчанию.
const OpenAI = require('openai');
// Пример импорта зависимости при переносе node_modules с необходимыми зависимостями в директорию /app/server/adapters/
const customPackage = require('custom-package');

class MyLLMAdapter {
  ...
}

module.exports = { MyLLMAdapter };

API адаптера

Описание функций

getName()

Возвращает уникальное строковое имя адаптера.

ПринимаетНет
Возвращаетstring — уникальный идентификатор адаптера. Сервис чата использует по умолчанию адаптеры deep-seek и openai.
ОбязательныйДа

getCompletion(params)

Синхронная генерация ответа без потока.

ПринимаетAdapterCompletionParams
Возвращает

Promise<ModelResponse>

ОбязательныйДа

getCompletionStream(params)

Потоковая генерация ответа для режима SSE.

ПринимаетAdapterCompletionParams
Возвращает

AsyncIterable<ModelStreamEvent> (функция должна быть асинхронным генератором )

ОбязательныйДа

getModels()

Возвращает список моделей, доступных для выбора в настройках.

ПринимаетНет
Возвращает

string[]

ОбязательныйНет

Описание интерфейсов

ModelStreamEvent

Событие потока (для SSE) — один из следующих типов:

text_delta — промежуточный фрагмент текста.

ПолеТипОбязательноеОписание
typetext_deltaДа

Тип события.

deltastringДаДобавленный фрагмент текста.

completed — завершение генерации (обязательное финальное событие).

ПолеТипОбязательноеОписание
typecompletedДа

Тип события.

responseModelResponseДаИтоговый ответ.

tool_call — уведомление о вызове инструмента.

ПолеТипОбязательноеОписание
typetool_callДа

Тип события.

toolstringДаИдентификатор инструмента для отображения в интерфейсе.

failed — уведомление об ошибке. Используйте это событие для обработки ошибочных сценариев. Например, когда инструмент вызывается большее количество раз, чем разрешено для адаптера.

ПолеТипОбязательноеОписание
typefailedДа

Тип события.

AdapterCompletionParams

ПолеТипОбязательноеОписание
apiKeystringДа

API-ключ провайдера из настроек.

inputModelMessageДаИстория сообщений для LLM.
abortSignalAbortSignalДаСигнал для отмены запросов при разрыве соединения с клиентом.
apiUrlstringНетБазовый URL-адрес API из настроек.
modelstringНетИмя модели из настроек чата.
systemPromptstringНетСистемный промпт из настроек.

ModelMessage

ПолеТипОбязательноеОписание
roleuser | assistant | systemДа

Роль автора сообщения.

contentstringДаТекст сообщения.

ModelResponse

ПолеТипОбязательноеОписание
outputTextstringДаПолный текст ответа ассистента.
output

ModelResponseOutputMessage[]

Да

Структурированный вывод.

relatedContent

ModelResponseRelatedContent

Нет

Дополнительный контент, относящийся к ответу.

totalTokens

number

Нет

Суммарное количество токенов, израсходованных при формировании ответа. Если модель вызывается несколько раз (например, при обращении к инструментам), значение накапливается по всем вызовам. 

additionalPrice

number

Нет

Дополнительная стоимость запроса сверх стоимости токенов. В это поле суммируются затраты на вызовы внешних сервисов. Например, поиск по базе знаний. Если модель вызывается несколько раз, значение накапливается по всем вызовам.

tag

string

Нет

Метка ответа модели. Используйте, чтобы добавить дополнительный признак для ответа. Например, указать сценарий обработки или источник, из которого получен результат. 


Поля totalTokens, additionalPrice и tag не отображаются в интерфейсе и используются только в аналитических целях — для учёта расхода токенов и стоимости запросов. Заполнять их не обязательно, но рекомендуется, если вы планируете собирать статистику по работе адаптера.
ModelResponseOutputMessage
ПолеТипОбязательноеОписание
roleassistant | userДаРоль автора сообщения.
content

{type: 'text', text: string}[]

Да

Фрагменты текста.

status

string

Нет

Статус генерации (например, completed ).

ModelResponseRelatedContent
ПолеТипОбязательноеОписание
links{ title: string, url: string }[]НетИсточники, которые LLM использовала при генерации ответа. Выводите их в виде ссылок под текстом сообщения.

Пример адаптера с вызовом функций (function calling)


Пример адаптера