Бот на анонимном Chrome в Docker записал 1170 встреч и выдаёт саммари за 4,6 минуты
Автор Habr собрал внутреннего бота для встреч вместо Copilot: за четыре месяца он записал около 1170 встреч, 920 часов речи у 93 организаторов. Медиана от конца записи до саммари с задачами — 4,6 минуты. Распознавание и диаризация идут на своих серверах, аудио наружу не уходит. Потоков два: бот в команде для канальных встреч Teams и анонимный Chrome в контейнере для всех остальных.
AI-обработка оригинала Habr AI; редакция Hamidun News
Договорённости со встреч живут ровно до следующей встречи. Протокол никто не ведёт, через неделю участники помнят около 10% сказанного. Вторая проблема свежее: в компании работают ИИ-агенты, но они знают только то, что записано и проиндексировано. Всё, что решили голосом, для них не существует. Автор материала на Habr решил закрыть обе дыры собственным ассистентом встреч и показал цифры из продакшена.
Почему не Copilot и не штатная расшифровка
Copilot работает только у тех, у кого есть подписка, а для автоматики «у всех команд без ручных действий» это не подходит. Он также не покрывает чужие тенанты, Телемост, МТС Линк и Труконф. Плюс аудио клиентских встреч хотелось обрабатывать на своих серверах.
Штатная русская расшифровка Teams автора тоже не устроила. На одних и тех же встречах она давала 26–37 слов в минуту, тогда как нормальный темп деловой речи около 100, а конвейер автора выдавал 99–108. Технические термины Teams пишет кириллицей: «эд баттон» вместо add button. Выборка небольшая, и автор честно называет сравнение ориентиром, а не бенчмарком.
Два потока вместо одного
Универсальное решение не получилось, потому что встречи Teams бывают двух видов.
Канальные встречи (дейли, планирования, ретро) записываются по политике сами и ложатся на SharePoint. У них нет обычного группового чата: переписка живёт в ветке под корневым постом канала, и анонимного гостя туда не пускают. Поэтому в команду ставится бот, который читает канал и ловит служебное событие CallRecording со статусом Success. Он забирает MP4, превращает его в WAV 16 кГц, отправляет на распознавание и публикует саммари в тред встречи. Для этого нужно RSC-разрешение ChannelMessage.Read.Group, которое выдаёт владелец команды, без админского гранта на весь тенант.
Все остальные встречи обслуживает «Ассистент Встреч». Его добавляют в приглашение как обычного участника, и к началу встречи в неё заходит анонимный Chrome в Docker. Запись идёт только с явного согласия: позвали и впустили — пишем, иначе нет. Потоки разделены физически: свои регистрации, приложения и права, поэтому утечка ключа одного не задевает другой.
Грабли, которые стоит запомнить
Гонка при записи. Состояние сначала хранилось в JSON-файлах, и на первой неделе listener и страховочный крон отправили одну запись дважды. Решение: Postgres с INSERT … ON CONFLICT DO NOTHING и SELECT … FOR UPDATE, а claim возвращает один из режимов new, retake, attach или skip.
Тупики с транскриптами. RSC ChannelMeetingTranscript.Read.Group отдавал 403, SharePoint не принимал app-only токен без сертификата, а Graph onlineMeetings/transcripts требует application access policy на организатора. Поэтому основа конвейера — MP4 и собственное распознавание, а VTT Teams служит вспомогательным источником.
Имена говорящих. Из VTT берутся число спикеров (подсказка num_speakers) и имена. Затем сегменты сопоставляются с кластерами диаризации по временному лагу. На трёх проверенных вручную встречах 94–99% сегментов получили верное имя. Любой сбой уходит в «Аноним»: неверно приписанная реплика хуже анонимной.
Кроме того, календарь в on-prem Exchange Graph API не видит, поэтому пришлось идти через EWS. Сессия браузерного постера тихо умирала из-за session-куки, а детектор говорящего ловил четверть речи, пока не перешли от событий к опросу состояния.
Что это значит на практике
Если строите похожее, то вывод такой. Не пытайтесь закрыть все платформы одним решением: канальные встречи Teams дешевле обслуживать ботом в команде, остальное — присутствием в разговоре. Храните состояние в базе с блокировками, а не в файлах. Промпты саммари делайте под тип встречи (дейли, ретро, планирование) и читайте их при каждой отправке, чтобы править без рестарта. И главное: транскрипты, которые индексирует платформа, дают агентам память о решениях, принятых голосом. Это основа для проактивных агентов.
Ограничения тоже есть. Приватные каналы поток 1 не поддерживает, на внешних встречах бота нужно впустить из лобби, а у самого Teams бывают баги, когда бот перестаёт получать апдейты канала (issue msteams-docs #11106). Цифры при этом из одной компании, без независимой проверки, но четыре месяца продакшена с 93 организаторами — убедительный довод.
- 8 нояб. 2026 г.Microsoft закрыла 1411 уязвимостей за месяц: ИИ ищет дыры быстрее, чем люди их латают
- 8 нояб. 2026 г.Пентагон вводит 3 проверки для каждого фрагмента кода от ИИ
- 8 нояб. 2026 г.GitHub расширил аналитику Copilot до девяти сценариев использования
- 8 нояб. 2026 г.GitHub открыл управление Copilot Code Review по API на трёх уровнях
Вопросы и ответы по материалу
Что известно по теме: почему не Copilot и не штатная расшифровка?
Copilot работает только у тех, у кого есть подписка, а для автоматики «у всех команд без ручных действий» это не подходит. Он также не покрывает чужие тенанты, Телемост, МТС Линк и Труконф. Плюс аудио клиентских встреч хотелось обрабатывать на своих серверах. Штатная русская расшифровка Teams автора тоже не устроила. На одних и тех же встречах она давала 26–37 слов в минуту, тогда как нормальный темп деловой речи около 100, а конвейер автора выдавал 99–108.
Что известно по теме: два потока вместо одного?
Универсальное решение не получилось, потому что встречи Teams бывают двух видов. Канальные встречи (дейли, планирования, ретро) записываются по политике сами и ложатся на SharePoint. У них нет обычного группового чата: переписка живёт в ветке под корневым постом канала, и анонимного гостя туда не пускают. Поэтому в команду ставится бот, который читает канал и ловит служебное событие CallRecording со статусом Success.
Что известно по теме: грабли, которые стоит запомнить?
Гонка при записи. Состояние сначала хранилось в JSON-файлах, и на первой неделе listener и страховочный крон отправили одну запись дважды. Решение: Postgres с INSERT … ON CONFLICT DO NOTHING и SELECT … FOR UPDATE, а claim возвращает один из режимов new, retake, attach или skip. Тупики с транскриптами. RSC ChannelMeetingTranscript.Read.Group отдавал 403, SharePoint не принимал app-only токен без сертификата, а Graph onlineMeetings/transcripts требует application access policy на организатора.
Соберите рабочего ИИ-агента или сервис своими руками без ручного кода
В Академии Жемала Хамидуна (academy.hamidun.com) открыт бесплатный 72-часовой тест-драйв: интерактивные скетч-уроки по Claude Code и Cursor, живой Tech Radar и готовые шаблоны агентов.
Главное из мира ИИ — раз в неделю
7 ключевых событий недели, отобранных вручную. Без шума, репостов и пресс-релизов.
Готово! Проверьте почту — мы отправили подтверждение.