Автозаполнение сделки в Bitrix24 после звонка: BANT-анализ на Python с МТС Exolve и YandexGPT

Автозаполнение сделки в Bitrix24 после звонка: BANT-анализ на Python с МТС Exolve и YandexGPT

Привет, Хабр!

Менеджеры продаж не всегда вовремя и полно заносят данные о сделках в CRM после звонка. Часть информации может забыться, а часть может быть записана сокращенно.

Прослушивать звонки вручную и восстанавливать детали слишком трудоёмко, а ресурсов на это часто не хватает. Поэтому в этом материале соберём MVP-сервис на Python, который получает событие о завершённом звонке из МТС Exolve, забирает текст разговора, выделяет из него ключевые поля через YandexGPT и записывает результат в Bitrix24. На выходе получится рабочий пайплайн: вебхук, транскрибация, извлечение полей квалификации и обновление существующей сделки в CRM.

За основу возьмём BANT — базовый фреймворк квалификации лида: Budget, Authority, Need, Timing, то есть бюджет, лицо, принимающее решение, потребность и сроки. И расширим его, добавив оценку интереса клиента, фиксацию конкурентов и возражений. Такого набора достаточно, чтобы квалифицировать лид, приоритизировать сделку и сохранить контекст следующего контакта, но не превращать карточку в анкету на десятки полей.

Стек: Python 3.10+, Flask, SQLite,Call Transcribation API МТС Exolve, YandexGPT API, Bitrix24 REST API.

Архитектура решения

Разделим MVP на пять компонентов. Такой расклад даёт понятный поток данных и не смешивает вебхук, работу с внешними API и запись в CRM в одном файле.

  • app.py принимает вебхук о завершении звонка, валидирует запрос, запускает фоновую обработку и отдаёт быстрый ответ.
  • database.py хранит состояние пайплайна и защищает систему от дублей по call_id.
  • exolve_api.py забирает транскрибацию из МТС Exolve и приводит её к формату диалога.
  • yandex_llm.py формирует строгий запрос к модели и парсит ответ в JSON.
  • bitrix24_crm.py ищет сделку и обновляет пользовательские поля Bitrix24.

Границы ответственности простые: HTTP-слой только принимает событие, база хранит состояние обработки, а интеграции с МТС Exolve, YandexGPT и Bitrix24 изолированы по модулям.

Шаг 1. Принимаем вебхук и быстро отвечаем

Самая чувствительная часть такого сценария — не задерживать ответ на входящее событие. Поэтому мы быстро валидируем payload и сразу уводим тяжёлую работу в фон.

На вход обработчик получает JSON от МТС Exolve, на выходе — короткий HTTP-ответ и фоновую задачу. Критичных мест три: проверить секрет до любой тяжёлой логики, не запускать пайплайн на промежуточных событиях и не создавать второй поток для обработанного call_id.

Важно вернуть именно 202 Accepted, а не ждать полного завершения пайплайна. HTTP-слой подтверждает, что мы приняли событие и взяли его в обработку, а не то, что успели получить транскрибацию, прогнать LLM и обновить CRM. Для MVP хватит threading, но в проде этот слой лучше заменить очередью задач: при нескольких воркерах и перезапусках процесса такой фоновой поток не даёт надёжных ретраев и контроля состояния.

Полный app.py: ▼

Шаг 2. Сохраняем состояние и защищаемся от дублей

У MVP три внешние точки отказа: МТС Exolve, YandexGPT и Bitrix24. Если запись в CRM упадёт по сети, нужно понять, на каком шаге оборвался пайплайн. Поэтому храним не только call_id, но и статус обработки.

Для такого сценария полезно сразу договориться о цепочке состояний: PENDING -> STT_OK -> LLM_OK -> CRM_OK или ERROR. Этого хватает, чтобы глазами понять, где завис звонок, и не лезть сразу в логи. call_id играет две роли: ключ идемпотентности и correlation id, по которому потом можно связать запись в базе, сообщения в логах и ответ внешнего API.

database.py

Этот слой принимает данные из вебхука и возвращает бинарный результат: запись создали или запись с таким call_id существует. Именно первичный ключ даёт идемпотентность. Для MVP SQLite удобна тем, что её можно поднять без миграций, но в проде такой журнал лучше перенести в Postgres.

Полный database.py: ▼

Шаг 3. Получаем транскрибацию и собираем диалог

После вебхука нам нужен не аудиофайл сам по себе, а текст, который можно отдать модели. В этом сценарии используемCall Transcribation API МТС Exolveи не отправляем запись в отдельный сервис распознавания речи.

exolve_api.py

На вход эта функция получает call_id, на выходе — собранный диалог одной строкой на реплику. Здесь мы не делаем один запрос и не падаем сразу, потому что транскрибация после завершения звонка может появиться не мгновенно. Поэтому polling с несколькими попытками практичнее, чем лишняя сложность в оркестрации.

Полный exolve_api.py: ▼

Шаг 4. Извлекаем BANT+ в строгий JSON

Если попросить модель “проанализировать звонок”, она начнёт писать свободным текстом. Для CRM это бесполезно. Поэтому задаём модели жёсткий формат и сразу ограничиваем, что она может вернуть: need, need_description, budget_estimated, decision_maker, timeline, intent_score, competitors, objections.

yandex_llm.py

Эта функция получает текст разговора и возвращает готовую структуру для CRM. В этом фрагменте важны три вещи: temperature=0.1, жёсткая JSON-схема и очистка Markdown-артефактов. В таком режиме модель отвечает стабильнее, усечение транскрипта защищает от переполнения контекста, а результат можно сразу маппить в поля CRM.

Полный yandex_llm.py: ▼

Шаг 5. Записываем результат в Bitrix24

После работы модели нам не нужен свободный текст. Нужен предсказуемый маппинг полей в CRM. Сделаем это через пользовательские поля сделки. Минимальный набор полей такой: описание потребности, бюджет, ЛПР, сроки, интерес, конкуренты и возражения.

bitrix24_crm.py

На вход функции приходит номер клиента и JSON от модели, на выходе — факт успешного обновления сделки. Нормализация номера обязательна: без неё Bitrix24 часто не находит запись.

В текущем решении новая сделка не создаётся: сервис обновляет существующую, найденную по номеру телефона клиента.

Маппинг полей в этом примере прямой:

Поле BANT+

Поле сделки Bitrix24

need_description

UF_CRM_BANT_NEED

budget_estimated

UF_CRM_BANT_BUDGET

decision_maker

UF_CRM_BANT_DM

UF_CRM_BANT_TIMELINE

intent_score

UF_CRM_INTENT

competitors

UF_CRM_COMPETITORS

objections

UF_CRM_OBJECTIONS

Полный bitrix24_crm.py: ▼

Запуск и проверка

После сборки всех частей остаётся проверить, что событие доходит до приложения и проходит через весь пайплайн. Для локального теста достаточно поднять Flask и отправить тестовый вебхук на маршрут /webhook/exolve.

python app.py

Если приложение запущено локально, можно пробросить туннель через ngrok и отправить тестовый payload. В ответе ожидаем 202, а в журнале или в SQLite — новую запись со статусом PENDING, которая затем перейдёт в STT_OK, LLM_OK и CRM_OK.

Проверка выглядит так:

  • маршрут /webhook/exolve отвечает 202 Accepted на валидный call.completed;
  • в SQLite появляется запись с нужным call_id;
  • статус звонка проходит цепочку PENDING -> STT_OK -> LLM_OK -> CRM_OK;
  • в Bitrix24 обновляются пользовательские поля сделки, найденной по телефону;
  • в HTML-журнале видно и итоговый статус, и извлечённые поля BANT+.

Полный test_webhook.py: ▼

Для быстрой проверки результата без захода в SQLite в проекте есть HTML-шаблон с последними записями.

Полный templates/index.html: ▼

Что можно усилить дальше

У MVP есть несколько точек для следующего шага.

  • Фоновую обработку через threading лучше заменить очередью задач, чтобы контролировать ретраи и не терять задачи при перезапусках
  • SQLite стоит заменить на Postgres, если пайплайн будет работать под нагрузкой и с параллельной обработкой
  • Поиск сделки по CONTACT.PHONE стоит заменить на связку контакт -> активная сделка, если в CRM у контакта может быть несколько сделок
  • Проверку через json.loads стоит дополнить схемной валидацией, чтобы контролировать не только формат, но и допустимые значения полей
  • Локальные ретраи внутри модулей стоит вынести в общую механику переобработки, чтобы звонки не зависали в ERROR
  • Логи стоит расширить: сохранять call_id, шаг пайплайна, HTTP-статус и причину ошибки
  • Для хранения транскриптов стоит заранее определить политику по персональным данным и срокам хранения

Мы собрали MVP-сервис, который получает событие о завершённом звонке, забирает транскрибацию из МТС Exolve, извлекает BANT+ через YandexGPT и записывает результат в Bitrix24. Такой сценарий снижает потери на двух этапах: когда менеджер не собрал часть квалификации в разговоре и когда детали теряются при ручном заполнении CRM. Следующий логичный шаг — вынести фоновую обработку в очередь задач, добавить схемную валидацию ответа модели и усилить логику поиска сущностей в CRM, не меняя общий контракт пайплайна.

Кодна гитхабе.

Читать оригинал