Почему расшифровку созвонов мы считаем сами, а не отдаём наружу

Готовое API расшифровки дешевле в разработке ровно до того момента, когда выясняется, что каждый разговор команды уходит целиком третьей стороне. Рассказываем, во что обошлась своя модель.

Записи звонков в Kontext долго лежали мёртвым грузом. Получасовой созвон ради одной договорённости никто не переслушивает, а искать по записи было нечем. Очевидное решение — расшифровка: разговор превращается в текст, текст ищется, а поверх него можно собрать короткое содержание.

Первый вопрос — не «как», а «куда уйдёт разговор»

Самый быстрый путь — отправлять записи в готовый сервис распознавания речи. Вызов API, пара дней на интеграцию, приличное качество. Но у этого пути есть свойство, о котором редко думают заранее: каждый разговор команды уходит третьей стороне целиком.

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

Поэтому речь в текст у нас переводит своя модель, на нашем же сервере.

Сколько это стоит в секундах

Модель работает на процессоре, без видеокарты. Главный вопрос был — успеет ли она. Замер на рабочем сервере: десять минут звука расшифровываются за четыре минуты на четырёх ядрах. Это значит, что всё, что команда наговорила за день, разбирается за ночь, а короткий созвон — пока вы наливаете кофе.

Модель держит в памяти около полугигабайта и считает минутами. Процессу, который отвечает людям на сообщения, делать с ней нечего, поэтому расшифровка живёт в отдельном процессе. Каталог с записями доступен ему только на чтение, а путь к файлу дополнительно проверяется: выход за пределы каталога означал бы чтение любых файлов сервера.

Почему по одной

Очередь фоновых задач умеет брать по четыре задачи сразу. Для расшифровки это ловушка: четыре разговора на четырёх ядрах считаются вчетверо медленнее каждый, потому что модель и так занимает все ядра. Итог тот же, а первый результат появляется в четыре раза позже. Расшифровки идут строго по одной.

Текст раньше содержания

Поверх расшифровки языковая модель собирает короткое содержание: «о чём говорили» и отдельно «договорились» — решения и кто что делает. Здесь мы сознательно сохраняем расшифровку до того, как запросили содержание. Если модель не ответила — кончился лимит, упала сеть, — текст уже в базе и не пропадёт, а за содержанием задача вернётся позже.

Есть и обратная грабля: запись, в которой было тихо. Пустая расшифровка запоминается пустой строкой. Иначе такая запись возвращалась бы в очередь вечно, каждый раз занимая минуты процессора ради ничего.

Честно о качестве

Модель содержания предупреждена, что расшифровка машинная: имена искажаются, реплики не разделены по говорящим. На проверке это сработало как надо — на разговоре без решений она написала «конкретных решений не зафиксировано», а не придумала их. Под содержанием в плеере стоит подпись, что его собрала модель. Пересказ, выданный за протокол, хуже отсутствия пересказа.

Чего нет: разделения реплик по говорящим. Для этого нужна отдельная модель на гигабайты зависимостей ради одной подписи «кто сказал». Пока цена не стоит результата.

Итог

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

Как это выглядит для пользователя — на странице звонков и расшифровки.