Колл-центр: ИИ-анализ качества звонков на своём сервере
Расшифровка звонков решает первую половину задачи — превращает архив аудио в текст. Вторая половина начинается там, где супервайзер садится читать тысячу расшифровок подряд, чтобы понять, кто из операторов поздоровался, кто предложил решение, а кто бросил трубку на середине возражения. Читать это вручную нереально, а выборочная проверка десяти звонков в неделю не показывает картину по отделу. Разумный следующий шаг — прогнать текст через модель, которая проверяет разговор по чек-листу скрипта и сама подсвечивает, куда супервайзеру стоит заглянуть в первую очередь.
Содержание
- Что уже должно быть готово: текст, а не аудио
- Чек-лист как промпт: что проверяет модель
- Технически: локальная LLM поверх текста расшифровки
- Выявление проблемных звонков для разбора супервайзером
- Агрегированная статистика по отделу
- Где проходит граница: это помощник, а не судья
- Развёртывание: из чего собрать стек на своём сервере
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Что уже должно быть готово: текст, а не аудио
Всё, что описано ниже, работает поверх текстовой расшифровки звонка — самого распознавания речи здесь нет, это отдельная задача, разобранная в материале расшифровка звонков на своём сервере через Whisper. Если Whisper ещё не поднят, начинать стоит оттуда: без текста с таймкодами и, желательно, разметкой по спикерам («оператор» / «клиент») строить анализ качества нечем — модель должна понимать, кто из собеседников не поздоровался, а не гадать по контексту.
Пайплайн получения текста из звонка проще всего оформить как HTTP-эндпоинт, на который CRM или АТС отправляет запись сразу после завершения разговора — подход разобран в статье как поднять API транскрибации звонков на сервере. Дальше в цепочку добавляется ещё один шаг — вызов LLM с текстом расшифровки и чек-листом скрипта.
Чек-лист как промпт: что проверяет модель
Анализ качества звонка — это автоматизация того, что раньше делал супервайзер с бумажным бланком оценки: пробежать разговор и отметить, случились ли обязательные пункты скрипта. Типичный чек-лист для отдела продаж или поддержки:
- поздоровался ли оператор и представился;
- уточнил ли имя клиента и цель обращения;
- предложил ли конкретное решение проблемы, а не общую отписку;
- называл ли клиента по имени в ходе разговора;
- отработал ли возражение, если оно прозвучало, а не проигнорировал;
- озвучил ли следующий шаг (перезвонит, оформит заявку, передаст коллеге);
- попрощался ли корректно, без обрыва на середине фразы.
Для LLM это превращается в промпт с текстом расшифровки и просьбой вернуть структурированный ответ — не свободный текст, а строго определённый формат, который потом можно сохранить в базу и агрегировать:
Ты — супервайзер отдела контроля качества колл-центра.
Проверь расшифровку звонка по чек-листу ниже.
Отвечай ТОЛЬКО валидным JSON без пояснений вне JSON.
Чек-лист:
1. greeting — оператор поздоровался и представился
2. purpose — уточнил цель обращения клиента
3. solution_offered — предложил конкретное решение
4. objection_handled — отработал возражение (null, если возражений не было)
5. next_step — озвучил следующий шаг
6. closing — корректно попрощался
Для каждого пункта верни true/false/null и короткую цитату из текста,
на основании которой сделан вывод.
Формат ответа:
{"greeting": {"passed": bool, "quote": "..."},
"purpose": {"passed": bool, "quote": "..."},
"solution_offered": {"passed": bool, "quote": "..."},
"objection_handled": {"passed": bool|null, "quote": "..."},
"next_step": {"passed": bool, "quote": "..."},
"closing": {"passed": bool, "quote": "..."},
"summary": "одна фраза о звонке",
"risk_flags": ["грубость", "не решён вопрос клиента", ...]}
Текст звонка:
{{TRANSCRIPT}}
Требование цитаты под каждым пунктом — не формальность: без неё модель иногда ставит true там, где в тексте пункта скрипта на самом деле нет, и вы получаете красивую, но неверную статистику. Цитата даёт супервайзеру возможность за секунду проверить, согласен ли он с выводом модели, не перечитывая весь звонок целиком.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть WhisperТехнически: локальная LLM поверх текста расшифровки
Держать это на своём сервере так же оправданно, как и Whisper: в тексте звонка — те же персональные данные клиента, что и в аудио, и внешний API анализа несёт тот же риск утечки, который расшифровка на своём сервере как раз и снимает. Разница в требованиях к железу: LLM для оценки диалогов прожорливее Whisper, и для моделей уровня 7–14B параметров разумнее взять сервер с GPU, если звонков в отделе — сотни в день.
Локальный инференс проще всего поднять через Ollama или vLLM с OpenAI-совместимым API — тогда вызов из скрипта ничем не отличается от обращения к внешнему сервису, только эндпоинт смотрит на localhost:
import json
import requests
def analyze_call(transcript: str) -> dict:
prompt = SCRIPT_CHECKLIST_PROMPT.replace("{{TRANSCRIPT}}", transcript)
response = requests.post(
"http://localhost:11434/api/generate",
json={"model": "qwen2.5:14b", "prompt": prompt, "format": "json", "stream": False},
timeout=120,
)
return json.loads(response.json()["response"])
Модель не обязана быть флагманской — оценка по чек-листу ближе к классификации, чем к свободному диалогу, и модели уровня 7–14B параметров на русском справляются приемлемо. Точную скорость и качество на своих звонках стоит проверить на собственной выборке — они сильно зависят от длины разговора и качества расшифровки, чужие цифры сюда переносить не стоит. Параметр format: "json" у Ollama заставляет модель отдавать валидный JSON, что снимает половину проблем с парсингом ответа в продакшене.
Выявление проблемных звонков для разбора супервайзером
Смысл автоматической проверки — не выставить оценку и забыть, а отсортировать поток звонков так, чтобы супервайзер тратил время на те, где действительно что-то пошло не так, а не пролистывал штатные разговоры. Простое правило маршрутизации по результатам анализа:
def needs_review(result: dict) -> bool:
failed_checks = sum(
1 for key in ("greeting", "purpose", "solution_offered", "next_step", "closing")
if result[key]["passed"] is False
)
has_risk = len(result.get("risk_flags", [])) > 0
objection_missed = result["objection_handled"]["passed"] is False
return failed_checks >= 2 or has_risk or objection_missed
Звонки, попавшие под needs_review, падают в отдельную очередь — карточку в CRM с пометкой «на разбор» и цитатами из ответа модели, чтобы супервайзер сразу видел, что именно смутило проверку, а не искал сам. Это не заменяет прослушивание — это экономит время на выборе, что слушать в первую очередь. При сотнях звонков в день такая фильтрация обычно сокращает очередь ручного разбора на порядок.
Отдельно стоит помечать флагом risk_flags не только нарушения скрипта, но и содержательные сигналы — жалобу на грубость, упоминание конкурента, слова вроде «расторгаю договор». Такие звонки имеет смысл выводить в отдельную, более срочную очередь — их разбор не терпит недели ожидания.
Агрегированная статистика по отделу
Отдельные звонки полезны для разбора конкретного случая, но ценность для руководителя — в цифрах по отделу за период. Результаты анализа каждого звонка стоит писать в простую таблицу и дальше агрегировать обычным SQL:
CREATE TABLE call_quality (
call_id TEXT PRIMARY KEY,
operator_id TEXT,
call_date DATE,
greeting BOOLEAN,
solution_offered BOOLEAN,
next_step BOOLEAN,
closing BOOLEAN,
risk_flags INTEGER,
needs_review BOOLEAN
);
SELECT
operator_id,
COUNT(*) AS total_calls,
ROUND(100.0 * SUM(CASE WHEN greeting THEN 1 ELSE 0 END) / COUNT(*), 1) AS greeting_pct,
ROUND(100.0 * SUM(CASE WHEN solution_offered THEN 1 ELSE 0 END) / COUNT(*), 1) AS solution_pct,
SUM(CASE WHEN needs_review THEN 1 ELSE 0 END) AS flagged_calls
FROM call_quality
WHERE call_date >= CURRENT_DATE - INTERVAL '7 days'
GROUP BY operator_id
ORDER BY flagged_calls DESC;
Такой отчёт за неделю сразу показывает, у кого из операторов регулярно проседает конкретный пункт скрипта — не «плохо работает» вообще, а конкретно «не предлагает решение» или «не проговаривает следующий шаг». Это полезнее для обучения, чем общая оценка: тренинг можно направить точечно на слабое место, а не гонять весь отдел по всему скрипту заново. Собирать такие сводки и рассылать их руководителю по расписанию — отдельная, но смежная задача, разобранная в материале как автоматизировать отчёты через ИИ на сервере.
Если колл-центр уже держит CRM с историей звонков на выделенном сервере, таблицу качества разумно завести прямо рядом с ней, а не городить отдельную базу — тогда карточка клиента и оценка разговора живут в одном месте. Требования к такому серверу под CRM на растущий отдел разобраны отдельно в статье выделенный сервер в России для CRM на сотни пользователей.
Где проходит граница: это помощник, а не судья
Здесь стоит быть честным: соблазн выставить автоматическую систему на замену живой оценке большой, а цена ошибки — тоже. LLM ошибается: принимает саркастичное «здравствуйте, конечно» за настоящее приветствие, не улавливает контекст, где оператор извинился раньше, чем ожидал чек-лист, путает вежливый отказ клиента с необработанным возражением. На пограничных звонках доля таких ошибок заметно выше, чем на однозначных — модель хорошо ловит явные нарушения, но плохо справляется с нюансами живого разговора.
Практический вывод: система отбирает кандидатов на проверку и снимает рутину с очевидных случаев, но финальную оценку — премию, выговор, решение по испытательному сроку — ставит человек, сам прослушавший или прочитавший звонок. Разумная практика — держать небольшую случайную выборку звонков, которую супервайзер проверяет вручную независимо от результата автоматики, и периодически сверять её с оценками модели: если расхождение растёт, чек-лист или промпт пора пересматривать. Это не разовая настройка, а процесс регулярной калибровки — система экономит время, но не должна экономить суждение там, где оно определяет судьбу конкретного сотрудника.
Развёртывание: из чего собрать стек на своём сервере
Весь пайплайн — расшифровка плюс анализ качества — укладывается в несколько сервисов на одном сервере:
| Компонент | Задача | Требования |
|---|---|---|
| faster-whisper / whisper.cpp | Речь в текст | CPU, 4+ vCPU, 8 ГБ RAM |
| Ollama / vLLM | Оценка звонка по чек-листу | GPU желателен при сотнях звонков в день, иначе CPU с большим запасом времени |
| PostgreSQL / SQLite | Хранение результатов и агрегация | Минимальные требования, растёт с объёмом истории |
| n8n или собственный скрипт-оркестратор | Связка шагов: приём файла → расшифровка → анализ → запись в базу → уведомление супервайзера | 1–2 vCPU достаточно |
Для отдела до полусотни звонков в день чек-лист на CPU без видеокарты вполне вытягивает пакетную обработку ночью или между сменами. Как только объём растёт до сотен звонков и результат анализа нужен в течение часа, а не на следующий день, стоит присмотреться к серверу с GPU — инференс LLM на видеокарте ускоряется кратно. Резервное копирование расшифровок, промптов и статистики стоит настроить сразу — принцип тот же, что и для остального ИИ-стека на сервере.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть WhisperОбсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Частые вопросы
Можно ли обойтись без своего сервера и прогонять расшифровки через облачный API LLM?
Технически можно, но в облако тогда уходит уже не аудио, а расшифрованный текст с именем клиента и содержанием обращения — риск для персональных данных остаётся тем же, только на следующем шаге пайплайна.
Нужна ли для анализа качества обязательно диаризация по спикерам?
Она сильно упрощает задачу — без неё модели приходится самой угадывать, кто из собеседников поздоровался или предложил решение, а на спорных фразах это добавляет ошибок. При двухканальной записи разметка получается почти бесплатно уже на этапе расшифровки.
Насколько точна автоматическая оценка по сравнению с ручной?
Зависит от чёткости скрипта и качества промпта — чем однозначнее формулировки в чек-листе, тем меньше расхождений с человеком. На старте стоит закладывать регулярную сверку с ручными оценками и не доверять системе финальные кадровые решения без проверки.
Что делать, если чек-лист меняется — например, отдел вводит новый скрипт продаж?
Промпт — это текстовый файл, а не обученная модель, правка занимает минуты. Старую статистику при этом лучше не смешивать с новой без пометки, что чек-лист менялся.
Сколько звонков в день реально тянет один сервер с GPU?
Зависит от длины разговоров, модели и параллельности обработки очереди — точные цифры стоит снимать на собственном тесте, а не ориентироваться на чужие оценки.
Нужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.