Создание чат-бота в портале Битрикс24 с помощью AI-агентов

Страницы:  1

Ответить
 

Professor Seleznov


pic
Почти все проекты мы делаем на основе одного репозитория b24-ai-starter, который служит базой для разработки приложений. В нём уже есть все инструкции для ИИ, которые облегчают работу агентов.
Статьи из цикла можно использовать как туториалы при создании локальных приложений для своего бизнес-портала. Их можно показать своим разработчикам или использовать самому, если работаете один и хотите кастомизировать работу дополнительными удобными функциями.
Сегодня — 5-я статья. Мы создадим чат-бота, зарегистрируем его в портале и научим делать что-нибудь полезное. Всю работу будем делать с помощью AI-агентов. Во время работы разберём все возникающие сложности и устройство приложения.
Ссылка на итоговый репозиторий, который уже подключен к порталу:
github.com/igorrosliakov-bitrix24/Bitrix24-ChatBot
pic
Игорь Росляков
Технический писатель
Что было в предыдущих туториалах: Что будет в этой статье:
  • Что нужно установить перед работой
  • Что за боты и где они находятся
  • Регистрируем приложение в портале
  • Проверяем, что чат-бот появился в портале
    • Как работать с ошибками: смотреть логи и передавать их агенту
    • Ошибка 1: неверный формат install payload
    • Ошибка 2: не хватало прав приложения
    • Ошибка 3: записи в локальной базе данных
    • Ошибка 4: новый метод регистрации бота не работал на портале
    • Ошибка 5: разные состояния локальной базы данных и портала
    • PS Откуда в проекте фронтенд
  • Учим чат-бота полезным функциям
  • Запрос статуса задач
  • Запрос во внешние API
  • Что будем делать дальше
  • Содержание цикла статей про создание приложений с AI-агентами
Что нужно установить перед работой
Нам понадобятся:
  • Docker и Docker Compose для контейнеризации.
  • Git для контроля версий и пуша в удалённый репозиторий.
  • make для коротких команд.
  • Публичный HTTPS-туннель через CloudPub. Получить можно через десктопный клиент или инструмент командной строки clo.
Что за боты и где они находятся
В портале есть раздел Мессенджер. Там находятся все диалоги:
pic
Если создать чат-бота, он тоже будет здесь. С ним можно начать диалог, попросить выполнить какие-то действия или запросить информацию.
Регистрируем приложение в портале
Сначала нужно прокинуть туннель через CloudPub, чтобы портал Битрикс24 мог обращаться к нашему приложению по HTTPS. Если не хотите через CloudPub, можно задеплоить на сервере — но это обычно дороже и дольше. Как копировать и настроить проект, прокинуть туннель с CloudPub и зарегистрировать приложение в своём портале, мы рассказали в первой статье в разделах: При регистрации нужно указать путь обработчика и путь первоначальной установки. Для большинства проектов эти пути выглядит как URL от Cloudpub и тот же самый URL с эндпойнтом /install:
pic
Но у чат-бота сценарий немного другой, поэтому оба пути должны быть одинаковыми и заканчиваться на эндпойнт /api/install. Ещё одно отличие: нужно выбрать пункт «Использует только API», потому что у чат-бота нет фронтенда:
pic
Обновление: во время работы над статьёй в стартер внесли правку — теперь для приложений, которые используют только API, достаточно указывать эндпойнт /install. Если вы заметили, что можно исправить в стартер-ките, то тоже можете внести предложения по улучшению, потому что это open-source проект.
Если интересно, в чём разница пути обработчика и установки — объяснение под спойлером:

Путь обработчика, путь установки и почему у чат-бота именно такие параметры

Главное — для API-приложения на этих URL должен быть бэкенд-код, который понимает формат запроса. Одинаковые значения в двух полях допустимы, если один эндпойнт умеет обработать оба запроса.
Путь для первоначальной установки
Для обычного приложения с интерфейсом для установки нужно открывать фронтенд-страницу вида:
https://...cloudpub.ru/install
Но на этот раз наше приложение использует только API. В этой схеме при установке должен отправить данные установки или переустановки на наш бэкенд. Эти данные приходят HTTP-запросом: access token, refresh token, domain, member_id, scope. Бэкенд должен принять эти данные и понять: «Приложение установили в портал. Сейчас нужно сохранить токены, домен портала, member_id и выполнить стартовую настройку».
Путь вашего обработчика
Это более общий URL обработчика приложения. Битрикс24 может использовать его как основной серверный обработчик событий и запросов приложения.
В документации для чат-бота указан один PHP-файл:
https://example.com/chatapi/bot.php
И этот файл умеет обрабатывать разные случаи:
  • Установка приложения.
  • Сообщение боту.
  • Удаление бота.
То есть «путь обработчика» — это адрес, где находится серверная логика приложения.
Деплой на VPS мы разбирали это в отдельной статье: «Что даёт воспроизводимая среда разработки и как развернуть контейнеры на VPS».
Права приложения зависят от того, что должен уметь делать ваш чат-бот. Но минимальный набор прав для начала такой:
  • Веб-мессенджер (im) необходим для передачи сообщений пользователю.
  • Создание и управление Чат-ботами (imbot) для регистрации чат-бота.
Проверяем, что чат-бот появился в портале
После успешной регистрации чат-бота можно найти в мессенджере, если начать вводить его имя:
pic
Но в моём случае от момента регистрации до появления чат-бота в портале прошло довольно много времени, потому что во время интеграции всплыло несколько небольших ошибок, которые агент последовательно устранял.
При интеграции редко бывает одна большая ошибка. Чаще это цепочка маленьких несовпадений: несколько маленьких мест, где ожидания документации, портала и нашего проекта расходятся.
Дальше я опишу основные проблемы интеграции, но сначала остановимся на том, как с ними работать.
Как работать с ошибками: смотреть логи и передавать их агенту
Лучшее, что вы можете сделать во время работы с агентом — сразу дать ему доступ к файлу логов Symfony/PHP бэкенда. Другой вариант — мониторить логи самостоятельно такой командой:
tail -f logs/php/symfony/dev.log
Мониторить логи самому полезно и интересно для понимания работы, но если важна скорость, достаточно дать агенту доступ. Он сам найдёт все ошибки и поправит их. 
Команда выше показывает логи PHP-приложения. Через неё мы видим действия нашего портала Битрикс24, которые дошли до нашего PHP-бэкенда. То есть если портал отправил запрос на наш endpoint, мы увидим его в Symfony-логе.
Что есть что в этой команде:
  • tail показывает конец файла.
  • -f означает «следить дальше в реальном времени», то есть команду можно запустить в одном терминале и просто копировать вывод терминала после каждой новой ошибки.
  • logs/php/symfony/dev.log — файл, куда PHP/Symfony пишет dev-логи.
А вот что можно увидеть в логах:
  • Входящие запросы от портала Битрикс24.
  • Какой Symfony route сработал.
  • payload, который прислал портал.
  • Наши debug-сообщения.
  • Ошибки PHP/Symfony.
  • Запросы нашего бэкенда к Битрикс24 REST API.
  • Ответы портала на эти API-запросы.
Например, запись Matched route "b24_install" означает, что Битрикс24 достучался до нашего /api/install. call.start {"apiMethod":"imbot.register"} показывает, что бэкенд вызывает REST API Битрикс24, а chatbotRegistered — что бот зарегистрирован.
Но по этой команде нельзя увидеть всё. Логи фронтенда, базы данных, Cloudpub и системные Docker-логи контейнеров вызываются отдельно. Но в моём конкретном случае нужны были именно логи PHP-приложения.
Ошибка 1: неверный формат install payload
Битрикс24 в режиме «Использует только API» присылал данные установки не в том формате, который ожидал стартер.
В процессе установки участвовал PHP-бэкенд. Но в коде репозитория AI-стартер был класс с названием FrontendPayload, и бэкенд использовал его для разбора данных установки. И вот этот класс ожидал такие поля:
DOMAIN
AUTH_ID
REFRESH_ID
А портал присылал вложенный объект:
auth.domain
auth.access_token
auth.refresh_token
auth.member_id
То есть проблема была в том, что класс ожидал payload из frontend-сценария установки, а портал в API-only режиме присылает другой формат. 
Обратите внимание, что стартер-кит периодически дорабатывается. В случае изменений можно отправить issue в репозиторий и сделать его лучше.
Как решилось: агент расширил FrontendPayload, чтобы он понимал оба варианта: frontend-style payload и direct API-only payload.
Ошибка 2: не хватало прав приложения
Права нужно отслеживать в 2 местах.
При регистрации приложения в портале:
pic
Те же права должны быть прописаны в .env в параметре SCOPE:
SCOPE='im,imbot,task,tasks_extended,imconnector'
Если забыть выдать права, установка может упасть на REST-вызове с такой ошибкой:
insufficient_scope
Ошибка 3: записи в локальной базе данных
После нескольких попыток установки в PostgreSQL остались старые записи. При новой установке база сказала:
duplicate key value violates unique constraint
То есть в базе уже существовала запись для пары b24_user_id и domain_url.
Как решилось:агент просто почистил старые записи установки из локальной базы. Обратите внимание, что это нормально на этапе разработки, но в проде так делать нельзя. Для эксплуатации нужна аккуратная идемпотентная логика установки, когда один и тот же вызов не вызывает изменения дважды.
Ошибка 4: новый метод регистрации бота не работал на портале
Для регистрации предполагается использовать метод imbot.v2.Bot.register.
Но почему-то в моём портале это не сработало — видимо, агент неправильно прочитал документацию. В результате портал ответил:
ERROR_METHOD_NOT_FOUND
Method not found
Как решилось: агент решил испробовать метод, который использовался раньше, и он сработал.
imbot.register
В итоге агент добавил fallback: сначала для установки используется новый метод, а если он не срабатывает, пробуем старый.
Ошибка 5: разные состояния локальной базы данных и портала
В стартере есть обработчик lifecycle-событий AppLifecycleEventController. У него есть route:
#[Route('/api/app-events/', name: 'b24_events', methods: ['POST'])]:
То есть Symfony говорит: «Если придет POST-запрос на /api/app-events/, передай его в AppLifecycleEventController». 
Lifecycle-события— события жизненного цикла приложения: установили, обновили, удалили. И портал может сообщать бэкенду о таких событиях, например ONAPPINSTALL означает «приложение установлено в портале», а ONAPPUNINSTALL — наоборот, «приложение удалено из портала».
Во время установки наш бэкенд сообщил адрес обработчика lifecycle-событий Битрикс24, и после этого портал начал отправлять события вроде ONAPPINSTALL на отдельный endpoint /api/app-events/.
В чём была проблема: после нескольких неудачных попыток установки агент почистил все записи из локальной базы данных — это было на этапе Ошибки №3. Получилось так, что локальная база и состояние портала разошлись: в портале обработчик уже привязан, а в базе записи установки нет. 
В итоге Битрикс24 прислал событие установки, но наш бэкенд не нашел соответствующую запись установки в локальной базе. Появилась новая ошибка:
Application installation not found
Как решилось:обработчик lifecycle-событий должен быть устойчивым: отсутствие локальной записи не должно ронять весь процесс. 
Раньше пайплайн установки выглядел примерно так:
Если пришел ONAPPINSTALL:
обязательно найди установку в базе
если не нашел -- ошибка 500
А стал выглядеть так:
Если пришел ONAPPINSTALL:
попробуй финализировать установку в базе
если записи нет -- запиши warning, но не падай
продолжай обработку
Ещё агент сделал так, что событие ONAPPINSTALL использовалось как второй шанс для регистрации чат-бота, потому что оно содержит полезные данные:
  • auth.access_token
  • auth.domain
  • auth.member_id
  • data.VERSION
То есть пайплайн ещё немного изменился:
Если регистрация бота не завершилась в /api/install:
попробуй зарегистрировать его при обработке ONAPPINSTALL в /api/app-events/.
В итоге помогло именно это. В логе появились такие записи:
AppLifecycleEventController.process.chatbotRegistered
method: imbot.register
result: ["14"]
А сам чат-бот появился в мессенджере.
PS Откуда в проекте фронтенд
В логах и при работе с агентами можно заметить, что периодически встречаются записи и ошибки со словом frontend. Это может сбивать с толку, поэтому на всякий случай дадим краткое объяснение.
В AI-стартере есть отдельная frontend-часть на Nuxt, и для нее есть отдельный Docker-контейнер. Это потому, что стартер рассчитан не только на чат-ботов. Он может использоваться для обычных Битрикс24-приложений, где пользователь открывает интерфейс внутри портала — таких, как наши приложения с дашбордами и роботами.
То есть фронтенд нужен, если приложение имеет экран, кнопки, формы, настройки, виджеты, placement и так далее. Но если приложение использует только API, как чат-бот, фронтенд-контейнер может быть запущен, но всё будет работать и без него.
Учим чат-бота полезным функциям
Сейчас боту можно написать, но он ничего не ответит. Чтобы он мог делать что-то нужное, для него нужно создать возможные команды.
Агента можно попросить добавить функции в свободной форме, например: «Научи бота собирать просроченные задачи и присылать мне отчёт по команде, который будет включать дни просрочки и имена ответственных». Понимать код для добавления этих возможностей необязательно, но мы всё равно разберём, как это работает.
Главный файл для работы — ChatbotEventsController.php.

Общая логика работы с командами

#[Route('/api/chatbot/events', name: 'chatbot_events', methods: ['POST'])]
public function process(Request $request): JsonResponse
{
// 1. Получаем событие от Битрикс24: сообщение, вход в чат и т.д.
$payload = $this->getPayload($request);
// 2. Нас интересуют только события чат-бота.
if (!in_array($payload['event'] ?? '', ['ONIMBOTJOINCHAT', 'ONIMBOTMESSAGEADD'], true)) {
return new JsonResponse(['status' => 'ignored'], 200);
}
// 3. Достаем текст сообщения пользователя.
// 4. Определяем команду.
// 5. Строим ответ.
$reply = 'ONIMBOTJOINCHAT' === ($payload['event'] ?? '')
? $this->buildHelpReply()
: $this->buildReply($this->extractUserMessage($payload), $payload);
// 6. Отправляем ответ обратно в Битрикс24.
$this->sendReply($payload, $reply);
return new JsonResponse(['status' => 'ok'], 200);p
Команды добавляются в методе buildReply.

Общая логика работы бота в зависимости от разных команд

$tokens = $this->extractTokens($message);
$command = mb_strtolower($tokens[0] ?? '');
$arguments = array_slice($tokens, 1);
// Если команда /fx -- идем в валютный функционал.
if (in_array($command, ['/fx', 'fx', 'валюта', 'курс'], true)) {
return $this->buildFxReply($arguments);
}
// Если команда /stock -- идем в функционал акций.
if (in_array($command, ['/stock', 'stock', 'акция', 'тикер'], true)) {
return $this->buildStockReply($arguments);
}
// Если команда /market -- собираем акции + валюты.
if (in_array($command, ['/market', 'market', 'рынок', 'инвестиции'], true)) {
return $this->buildMarketReply($arguments);
}
// Если команда /утро -- идем в Битрикс24 за задачами.
if (in_array($command, ['/morning', 'morning', '/утро', 'утро', 'дайджест'], true)) {
return $this->buildMorningReply($payload);
}
То есть общие правила добавления команд такие:
1. Добавить условие в buildReply().
2. Создать метод buildSomethingReply().
3. Если нужна сложная логика — вынести ее в отдельный Service.
4. Вернуть строку, которую бот отправит в чат.
Запрос статуса задач
Сначала мы добавили отчёт по всем существующим задачам. Пользователь может написать слова: /morning, morning, /утро, утро, дайджест, и бот запускает функцию buildMorningReply:
if (in_array($command, ['/morning', 'morning', '/утро', 'утро', 'дайджест'], true)) {
return $this->buildMorningReply($payload);
}
Что делает buildMorningReply:
private function buildMorningReply(array $payload): string
{
// Достаем из события Битрикс24 домен, access token и ID пользователя.
$context = $this->extractBitrixContext($payload);
// Передаем эти данные в сервис, который умеет читать задачи.
return $this->taskDigestService->buildMorningDigest(
$context['domain'],
$context['accessToken'],
$context['userId'],
);
}
Контекст по задачам получает функция extractBitrixContext:
  private function extractBitrixContext(array $payload): array
{
$bots = (array) ($payload['data']['BOT'] ?? []);
$botAuth = [] !== $bots ? reset($bots) : [];
$botAuth = is_array($botAuth) ? $botAuth : [];
$domain = (string) ($payload['auth']['domain'] ?? $botAuth['domain'] ?? '');
$accessToken = (string) ($payload['auth']['access_token'] ?? $botAuth['access_token'] ?? '');
$userId = (int) ($payload['data']['USER']['ID'] ?? $payload['auth']['user_id'] ?? 0);
if ('' === $domain '' === $accessToken 0 === $userId) {
throw new \RuntimeException('Не хватает данных Битрикс24 для чтения задач.');
}
return [
'domain' => $domain,
'accessToken' => $accessToken,
'userId' => $userId,
];
}
Общая логика задач находится уже в другом файле — Bitrix24TaskDigestService.php:

Общая логика задач

public function buildMorningDigest(string $domain, string $accessToken, int $userId): string
{
// Загружаем активные задачи пользователя из Битрикс24.
$tasks = $this->loadActiveTasks($domain, $accessToken, $userId);
// Готовим группы задач.
$overdue = [];
$todayTasks = [];
$important = [];
$withoutDeadline = [];
$future = [];
// Раскладываем задачи по категориям.
foreach ($tasks as $task) {
if ($this->isCompleted($task)) {
continue;
}
if (($task['priority'] ?? '') === '2') {
$important[] = $task;
}
$deadline = $this->parseDeadline($task['deadline'] ?? null);
if (null === $deadline) {
$withoutDeadline[] = $task;
continue;
}
// Сравниваем дедлайн с сегодняшним днем.
}
// Собираем текст ответа.
return implode("\n", $lines);
}
Последняя часть — запрос к порталу в функции loadActiveTasks:
$this->httpClient->request('POST', sprintf('https://%s/rest/tasks.task.list', $domain), [
'json' => [
// Берем задачи, где текущий пользователь ответственный.
'filter' => [
'RESPONSIBLE_ID' => $userId,
'!REAL_STATUS' => 5,
],
// Запрашиваем только нужные поля.
'select' => [
'ID',
'TITLE',
'STATUS',
'REAL_STATUS',
'DEADLINE',
'PRIORITY',
],
// Токен Битрикс24 из входящего события бота.
'auth' => $accessToken,
],
]);
Что получается при запуске:
pic
Запрос во внешние API
Бот может работать не только с инструментами портала, но и внешними источниками. Для примера я попросил чат-бота ходить за курсами акций и валют в 2 разных API.
Команды для бота находятся там же, где и команда для сводки задач из портала — в файле ChatbotEventsController.php:
// Валюты: /fx USD EUR
if (in_array($command, ['/fx', 'fx', 'валюта', 'курс'], true)) {
return $this->buildFxReply($arguments);
}
// Акции: /stock AAPL MSFT
if (in_array($command, ['/stock', 'stock', 'акция', 'тикер'], true)) {
return $this->buildStockReply($arguments);
}
// Общая сводка: /market AAPL MSFT USD EUR
if (in_array($command, ['/market', 'market', 'рынок', 'инвестиции'], true)) {
return $this->buildMarketReply($arguments);
Формирование ответа по акциям происходит в функции buildStockReply:
foreach (array_slice($arguments, 0, 5) as $symbol) {
// Пропускаем валюты, оставляем только тикеры акций.
if ($this->marketDataService->isCurrency($symbol)) {
continue;
}
// Получаем котировку акции.
$quotes[] = $this->marketDataService->getStockQuote($symbol);
}
Общая сводка создаётся в buildMarketReply:
foreach ($arguments as $argument) {
// Если это валюта -- кладем в currencySymbols.
if ($this->marketDataService->isCurrency($argument)) {
$currencySymbols[] = $argument;
// Если похоже на тикер -- кладем в stockSymbols.
} elseif (preg_match('/^[A-Z]{1,6}(?:\.[A-Z]{1,4})?$/', $argument)) {
$stockSymbols[] = $argument;
}
}
Внешние API вынесены в отдельный файл MarketDataService.php. Бот получает валюты через сервис Frankfurter в getExchangeRate:
$payload = $this->httpClient
->request('GET', 'https://api.frankfurter.app/latest', [
'query' => [
'from' => $base,
'to' => $target,
],
])
->toArray();
Курсы акций бот пытается получить через 2 разных API: Alpha Vantage или Stooq. Alpha Vantage даёт более надёжную информацию, но для него нужен ключ. Если мы захотим использовать его в будущем, этот ключ нужно будет положить в .env
ALPHA_VANTAGE_API_KEY='твой_ключ'
Если ключа нет, используем более простой сервис Stooq:
$apiKey = (string) ($_ENV['ALPHA_VANTAGE_API_KEY'] ?? $_SERVER['ALPHA_VANTAGE_API_KEY'] ?? '');
if ('' !== $apiKey) {
return $this->getAlphaVantageQuote($symbol, $apiKey);
}
return $this->getStooqQuote($symbol);
Чтобы протестировать работу, нужно вызывать команду и аргументы. Командой может быть одно из слов в списке:
'/market', 'market', 'рынок', 'инвестиции'
И сразу после этого передаём аргументы — названия акций и валют в том виде, как они приняты на рынке. Например, /market AAPL MSFT USD EUR:
pic
Или так — /market TSLA USD JPY:
pic
Что будем делать дальше
Стартер-кит упрощает и ускоряет работу с порталом Битрикс24. В следующий раз мы подробнее разберём, почему это стандарт разработки и создадим ещё что-нибудь полезное и интересное.
Если вам интересна какая-то тема про кастомизацию портала — напишите в комментариях, а мы постараемся раскрыть её в следующих статьях.
Содержание цикла статей про создание приложений с AI-агентами -Источник
 
Loading...
Error