MAATRIX / Блог / Колл-центр: ИИ-анализ качества звонков на своём сервере

Колл-центр: ИИ-анализ качества звонков на своём сервере

Колл-центр: ИИ-анализ качества звонков на своём сервере

MAATRIX

Расшифровка звонков решает первую половину задачи — превращает архив аудио в текст. Вторая половина начинается там, где супервайзер садится читать тысячу расшифровок подряд, чтобы понять, кто из операторов поздоровался, кто предложил решение, а кто бросил трубку на середине возражения. Читать это вручную нереально, а выборочная проверка десяти звонков в неделю не показывает картину по отделу. Разумный следующий шаг — прогнать текст через модель, которая проверяет разговор по чек-листу скрипта и сама подсвечивает, куда супервайзеру стоит заглянуть в первую очередь.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество 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 — десятки моделей в одном окне. Оплата картой РФ и по СБП.