Как мы научили ИИ разбирать звонки продавцов.

Откуда мы стартуем:

В прошлой главе мы выбрали ASR-провайдер — Assembly AI.

Теперь у нас есть текст звонка, разбитый по спикерам, с таймстемпами каждого слова. Но сам по себе транскрипт бесполезен. Руководителю отдела продаж нужен не просто текст разговора — ему нужен ответ: где менеджер теряет клиента и что исправить в следующем звонке.

Задача этого этапа — научить систему понимать звонок так, как это делает сильный РОП: оценить, как менеджер говорил, прошёл ли он все этапы продажи, и что ему стоит улучшить.

Мы подошли к этому как к исследованию. Сначала — sandbox: Jupyter-ноутбуки, эксперименты, баги, переделки. Потом — перенос в produtcion-сервис. Три итерации, прежде чем всё заработало.

Вот что у нас на входе от ASR:

Как мы научили ИИ разбирать звонки продавцов.

И вот что нужно получить на выходе:

Два слоя анализа. Шесть алгоритмических модулей. Семь LLM- анализаторов. Один scorecard.
Два слоя анализа. Шесть алгоритмических модулей. Семь LLM- анализаторов. Один scorecard.

Два слоя: как говорит и что говорит:

Когда сильный руководитель слушает звонок, он одновременно оценивает две вещи: как менеджер говорит (delivery) и что именно он говорит (sales body). В стратегии SlovoAI мы попытались разложить этот процесс на алгоритмы, чтобы заложить тот же принцип двойного анализа.

Delivery — это про голос. Менеджер говорит слишком быстро? Делает длинные паузы? Перебивает клиента? Использует слова-паразиты? Это всё можно измерить алгоритмически, без LLM. Берёшь таймстемпы, считаешь — готово. Быстро, дёшево, детерминированно.

Sales Body — это про смысл. Менеджер представился? Выявил боль клиента? Сделал презентацию под потребности? Предложил следующий шаг? Здесь нужен LLM — модель, которая понимает контекст разговора и оценивает его по чек-листу продаж.

Как мы научили ИИ разбирать звонки продавцов.

Эти два слоя работают параллельно. Delivery считается за миллисекунды. Sales Body — за несколько секунд (7 LLM-вызовов одновременно). На выходе — единый scorecard.

Delivery: шесть метрик без единого LLM-вызова:

Важно было сделать эти расчёты полностью алгоритмическими — без LLM. Это быстрее, дешевле и предсказуемо в продакшене: берём utterances от ASR и считаем. Никаких моделей, никаких внешних API-вызовов.

Как мы научили ИИ разбирать звонки продавцов.

Первая проблема всплыла сразу. ASR выдаёт строго последовательные сегменты — overlap между спикерами всегда равен нулю. Наш оригинальный алгоритм перебиваний, основанный на пересечении интервалов, показал 0 результатов.

Решение: gap-based heuristic. Если между репликами разных спикеров прошло ≤100ms — это перебивание. Нашли 6 перебиваний, 5 из них с gap=0ms. Подтвердили контекстом — работает.

Вторая проблема — слово «да» в словаре fillers. Из 10 находок 6 были false positives: «да» в контексте ответа — это не паразит. Убрали из словаря — осталось 4 корректных.

Третья — unknown-спикер. ASR иногда не определяет роль. Если считать его за «не-агента», talk ratio агента падает с реальных 28.6% до 22%. Добавили параметр exclude_unknown=True.

→ Все 6 модулей: GO. Время: <100ms суммарно.

Вот как выглядит реальный delivery-отчёт на тестовом звонке (test-local, 2 секунд, 25 utterances):

Как мы научили ИИ разбирать звонки продавцов.

Sales Body: 7 LLM-анализаторов, 23 критерия:

Delivery показывает как говорит менеджер. Но РОПу важнее что. Прошёл ли менеджер по этапам продажи? Выявил боль? Предложил решение? Назначил следующий шаг? Для этого нужен LLM. Мы разбили чек-лист продажи на 7 этапов, каждый — отдельный анализатор со своим промптом:

Как мы научили ИИ разбирать звонки продавцов.

Каждый анализатор получает utterances звонка и возвращает JSON: passed / false / null по каждому критерию, плюс комментарий и evidence — ссылки на конкретные реплики. В качестве модели выбрали Gemini 2.5 Flash Lite через OpenRouter. Причины прагматичные: быстрый, дешёвый, стабильно парсит русский в JSON.

Как мы научили ИИ разбирать звонки продавцов.

Все 7 анализаторов запускаются параллельно. Время определяется самым медленным вызовом, а не суммой. На реальном звонке — 3-7 секунд на полный scorecard. Ноль ошибок парсинга за все тесты.

А вот как выглядит реальный результат Sales Body — чек-лист по этапам продажи:

Как мы научили ИИ разбирать звонки продавцов.

Первые грабли: P4 оценивал клиента:

Запустили первый полный тест. Scorecard показал 3 из 13 — всего 23%.
Звонок слабый, агент пассивный — результат выглядел ожидаемым. Но при ручной проверке стало понятно: что-то не сходится.

Критерий P4 — «фразы, снижающие значимость». Это выражения вроде «можно спросить?..», «извиняюсь за беспокойство...» — они делают речь менеджера менее уверенной. Анализатор нашёл такие фразы и поставил агенту false.

Промпт не уточнял, чью речь нужно оценивать. Модель честно анализировала весь текст звонка и находила нужные фразы — просто они принадлежали не тому спикеру. В итоге менеджер получал штраф за слова клиента.

Исправление оказалось простым: в промпт добавили явное ограничение — P4 анализирует ТОЛЬКО реплики менеджера, полностью игнорируя речь клиента. После этого критерий начал работать корректно.

Этот баг — типичный для LLM-систем. Модель делает то, что написано в промпте. Если граница не задана явно — она возьмёт всё, что видит.

→ Правило: каждый промпт должен явно ограничивать scope анализа.

Каскадная логика и несогласованные поля:

Второй баг был логическим. Критерий C1 — «менеджер сделал попытку сделки». Если C1=false (попытки не было), то C2 («дал выбор без выбора»), C3 («подвёл итог»), C4 («использовал приём ограничения») тоже не могли быть оценены. Но анализатор ставил им false вместо null.

Результат: scorecard показывал 3/13 (23%), хотя справедливая оценка — 3/10 (30%). Три критерия были наказаны за ситуацию, которой не было в звонке.

Как мы научили ИИ разбирать звонки продавцов.

Решение: функция apply_cascade_rules() — post-processing после LLM. Правила: если C1=false → C2, C3, C4 = null. Если P1=false → P2, P3 = null. Зависимые критерии не штрафуют агента за то, чего не было.

Третий баг был инфраструктурным. Ноутбуки использовали поля speaker , start , end , а ASR возвращал speakerId , startMs , endMs . Результат — молчаливые нулевые результаты. Talk ratio показывал 0, перебивания — 0. Ошибки нет, данные просто не попадали в анализ.

Исправление: Pydantic-модель Utterance с нормализацией на входе. Все модули работают с каноническими именами полей. Fallback-логика удалена из 8 файлов.

Три итерации: v1 → v2 → v3

Мы не написали код один раз и не пошли в прод. Было три полных прогона — с разными данными, фиксами и результатами.

v1 — первый запуск на реальном звонке (test-003: 95 секунд, 15 utterances, 3 спикера). Нашли 4 критических бага: P4 оценивал клиента, каскадная логика не работала, NS1 возвращал null вместо false при отказе клиента, поля не совпадали. Scorecard: 3/13 (23%).

v2 — исправили BUG-P4 и BUG-NS1. Промпты стали точнее, комментарии модели — конкретнее. Scorecard: 5/21 (24%). Каскадная логика ещё не подключена.

v3 — подключили каскадные правила, задействовали Pydantic-модель, удалили fallback-логику. Тестировали на новом звонке (test-local: 208 секунд, 25 utterances, более активный агент). Scorecard: 10/21 (48%). Критических багов: 0.

Каждая итерация показывала: проблема была не в модели, а в нашей логике обработки.
Каждая итерация показывала: проблема была не в модели, а в нашей логике обработки.

Рост scorecard с 23% до 48% — не потому что модель стала лучше оценивать. А потому что мы починили баги (каскад убрал несправедливые штрафы) и взяли звонок с более активным агентом. Модель оценивала всё правильно — проблема была в нашей логике обработки результатов.

Как мы научили ИИ разбирать звонки продавцов.

Архитектура: оркестратор и параллельный запуск:

Теперь всё это нужно было превратить из notebooks и экспериментов в сервис, который стабильно работает в продакшене. Принцип архитектуры: каждый анализатор — изолированный модуль с единым интерфейсом. Оркестратор запускает их параллельно, собирает результаты, применяет каскадные правила, формирует scorecard.

Как мы научили ИИ разбирать звонки продавцов.

Есть нюанс: next_step зависит от deal_result. Если клиент отказал (rejected), а менеджер не назначил перезвон — это ошибка. Поэтому оркестратор запускает deal_result первым, а затем прокидывает deal_outcome в next_step.

Деплой — единый Docker-контейнер: Fast API, Redis для хранения состояния задач, async httpx для LLM-вызовов. Готов к разделению на отдельные контейнеры при >100 звонков/час, но на старте это избыточно.

29 тестов — всё зелёное. Сервис стартует, принимает задачи, возвращает метрики и evidence.

Итоговая таблица: 13 анализаторов:

Полный список того, что мы построили. Delivery — детерминированные алгоритмы. Sales Body — LLM через OpenRouter. Все модули — GO для production.

Как мы научили ИИ разбирать звонки продавцов.

Delivery — детерминированные алгоритмы, мгновенный результат. Sales Body — LLM через OpenRouter, 3-7 секунд параллельно. Все 13 модулей прошли sandbox-валидацию и готовы к production.

Что осталось:

Модель оценки работает. 13 анализаторов, 23 критерия, 0 критических багов. Но до «понимает как сильный РОП» ещё есть дистанция.

Как мы научили ИИ разбирать звонки продавцов.

Аналитика теперь работает. Scorecard формируется. Но менеджеру не нужен отчёт — ему нужен простой ответ: что делать в следующем звонке.

Именно этим мы займёмся в следующей главе.

4
1 комментарий