
Привет! Меня зовут Игорь Росляков, я технический писатель. По приглашению руководителя направления «Маркет и интеграции» Сергея Вострикова готовлю цикл статей на тему ИИ-ассистированной разработки решений для Битрикс24.
Почти все проекты мы делаем на основе одного репозитория b24-ai-starter, который служит базой для разработки приложений. В нём уже есть все инструкции для ИИ, которые облегчают работу агентов.
Статьи из цикла можно использовать как туториалы при создании локальных приложений для своего бизнес-портала. Их можно показать своим разработчикам или использовать самому, если работаете один и хотите кастомизировать работу дополнительными удобными функциями.
Сегодня — 5-я статья. Мы создадим чат-бота, зарегистрируем его в портале и научим делать что-нибудь полезное. Всю работу будем делать с помощью AI-агентов. Во время работы разберём все возникающие сложности и устройство приложения.
Ссылка на итоговый репозиторий, который уже подключен к порталу:
github.com/igorrosliakov-bitrix24/Bitrix24-ChatBot

Игорь Росляков
Технический писатель
Что было в предыдущих туториалах:
Пишем первое приложение с AI-стартером, чтобы видеть прибыли и убытки
Добавляем в бизнес-портал Битрикс24 роботов для автоматизации
Что даёт воспроизводимая среда разработки и как развернуть контейнеры на VPS
Анализ и модернизация коннектора баз данных с помощью AI-агентов
Что будет в этой статье:
Содержание цикла статей про создание приложений с AI-агентами
Что нужно установить перед работой
Нам понадобятся:
Docker и Docker Compose для контейнеризации.
Git для контроля версий и пуша в удалённый репозиторий.
make для коротких команд.
Публичный HTTPS-туннель через CloudPub. Получить можно через десктопный клиент или инструмент командной строки clo.
Что за боты и где они находятся
В портале есть раздел Мессенджер. Там находятся все диалоги:

Если создать чат-бота, он тоже будет здесь. С ним можно начать диалог, попросить выполнить какие-то действия или запросить информацию.
Регистрируем приложение в портале
Сначала нужно прокинуть туннель через CloudPub, чтобы портал Битрикс24 мог обращаться к нашему приложению по HTTPS. Если не хотите через CloudPub, можно задеплоить на сервере — но это обычно дороже и дольше. Как копировать и настроить проект, прокинуть туннель с CloudPub и зарегистрировать приложение в своём портале, мы рассказали в первой статье в разделах:
Копируем и настраиваем проект — для чат-бота при выборе бэкенда на этапе настройки проекта нужно выбрать php.
Регистрируем приложение на портале Битрикс24 — кроме этапа регистрации в портале, это разбираем отдельно ниже.
При регистрации нужно указать путь обработчика и путь первоначальной установки. Для большинства проектов эти пути выглядит как URL от Cloudpub и тот же самый URL с эндпойнтом /install:

Но у чат-бота сценарий немного другой, поэтому оба пути должны быть одинаковыми и заканчиваться на эндпойнт /api/install. Ещё одно отличие: нужно выбрать пункт «Использует только API», потому что у чат-бота нет фронтенда:

Обновление: во время работы над статьёй в стартер внесли правку — теперь для приложений, которые используют только 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) для регистрации чат-бота.
Проверяем, что чат-бот появился в портале
После успешной регистрации чат-бота можно найти в мессенджере, если начать вводить его имя:

Но в моём случае от момента регистрации до появления чат-бота в портале прошло довольно много времени, потому что во время интеграции всплыло несколько небольших ошибок, которые агент последовательно устранял.
При интеграции редко бывает одна большая ошибка. Чаще это цепочка маленьких несовпадений: несколько маленьких мест, где ожидания документации, портала и нашего проекта расходятся.
Дальше я опишу основные проблемы интеграции, но сначала остановимся на том, как с ними работать.
Как работать с ошибками: смотреть логи и передавать их агенту
Лучшее, что вы можете сделать во время работы с агентом — сразу дать ему доступ к файлу логов 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 местах.
При регистрации приложения в портале:

Те же права должны быть прописаны в .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, ], ]);
Что получается при запуске:

Запрос во внешние 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:

Или так — /market TSLA USD JPY:

Что будем делать дальше
Стартер-кит упрощает и ускоряет работу с порталом Битрикс24. В следующий раз мы подробнее разберём, почему это стандарт разработки и создадим ещё что-нибудь полезное и интересное.
Если вам интересна какая-то тема про кастомизацию портала — напишите в комментариях, а мы постараемся раскрыть её в следующих статьях.
Содержание цикла статей про создание приложений с AI-агентами
Пишем первое приложение с AI-стартером, чтобы видеть прибыли и убытки
Добавляем в бизнес-портал Битрикс24 роботов для автоматизации
Что даёт воспроизводимая среда разработки и как развернуть контейнеры на VPS
Анализ и модернизация коннектора баз данных с помощью AI-агентов
Создание чат-бота в портал Битрикс24 с помощью AI-агентов