Почему расшифровку созвонов мы считаем сами, а не отдаём наружу
Готовое API расшифровки дешевле в разработке ровно до того момента, когда выясняется, что каждый разговор команды уходит целиком третьей стороне. Рассказываем, во что обошлась своя модель.
Записи звонков в Kontext долго лежали мёртвым грузом. Получасовой созвон ради одной договорённости никто не переслушивает, а искать по записи было нечем. Очевидное решение — расшифровка: разговор превращается в текст, текст ищется, а поверх него можно собрать короткое содержание.
Первый вопрос — не «как», а «куда уйдёт разговор»
Самый быстрый путь — отправлять записи в готовый сервис распознавания речи. Вызов API, пара дней на интеграцию, приличное качество. Но у этого пути есть свойство, о котором редко думают заранее: каждый разговор команды уходит третьей стороне целиком.
Разговор команды — самое личное, что есть в рабочем мессенджере. Переписку пишут, помня, что её прочитают; на созвоне говорят то, что никогда бы не написали. Обсуждение увольнения, спор о бюджете, разбор инцидента с именами. Отдавать это наружу ради удобства — ровно то решение, которое продукт не должен принимать за клиента молча.
Поэтому речь в текст у нас переводит своя модель, на нашем же сервере.
Сколько это стоит в секундах
Модель работает на процессоре, без видеокарты. Главный вопрос был — успеет ли она. Замер на рабочем сервере: десять минут звука расшифровываются за четыре минуты на четырёх ядрах. Это значит, что всё, что команда наговорила за день, разбирается за ночь, а короткий созвон — пока вы наливаете кофе.
Модель держит в памяти около полугигабайта и считает минутами. Процессу, который отвечает людям на сообщения, делать с ней нечего, поэтому расшифровка живёт в отдельном процессе. Каталог с записями доступен ему только на чтение, а путь к файлу дополнительно проверяется: выход за пределы каталога означал бы чтение любых файлов сервера.
Почему по одной
Очередь фоновых задач умеет брать по четыре задачи сразу. Для расшифровки это ловушка: четыре разговора на четырёх ядрах считаются вчетверо медленнее каждый, потому что модель и так занимает все ядра. Итог тот же, а первый результат появляется в четыре раза позже. Расшифровки идут строго по одной.
Текст раньше содержания
Поверх расшифровки языковая модель собирает короткое содержание: «о чём говорили» и отдельно «договорились» — решения и кто что делает. Здесь мы сознательно сохраняем расшифровку до того, как запросили содержание. Если модель не ответила — кончился лимит, упала сеть, — текст уже в базе и не пропадёт, а за содержанием задача вернётся позже.
Есть и обратная грабля: запись, в которой было тихо. Пустая расшифровка запоминается пустой строкой. Иначе такая запись возвращалась бы в очередь вечно, каждый раз занимая минуты процессора ради ничего.
Честно о качестве
Модель содержания предупреждена, что расшифровка машинная: имена искажаются, реплики не разделены по говорящим. На проверке это сработало как надо — на разговоре без решений она написала «конкретных решений не зафиксировано», а не придумала их. Под содержанием в плеере стоит подпись, что его собрала модель. Пересказ, выданный за протокол, хуже отсутствия пересказа.
Чего нет: разделения реплик по говорящим. Для этого нужна отдельная модель на гигабайты зависимостей ради одной подписи «кто сказал». Пока цена не стоит результата.
Итог
- расшифровку считает своя модель на нашем сервере — сторонним сервисам распознавания разговоры не уходят;
- десять минут звука — четыре минуты расчёта;
- записи по одной, текст раньше содержания, тишина не зацикливает очередь;
- краткое содержание собирает языковая модель — и об этом честно написано и в плеере, и в политике конфиденциальности.
Как это выглядит для пользователя — на странице звонков и расшифровки.