BrainstormBuddy: как я собрал AI ассистента, который слушает созвон и подсказывает ответ в реальном времени
С обычными AI-ассистентами всё довольно понятно: написал вопрос, подождал несколько секунд, получил ответ.
На созвоне такая схема почти бесполезна.
Когда тебе задают вопрос на собеседовании, встрече с клиентом или переговорах, нельзя сказать: «секунду, сейчас скопирую это в ChatGPT». Ответ нужен здесь и сейчас. Желательно — пока пауза ещё не стала неловкой.
Так появился BrainstormBuddy — Windows-приложение, которое слушает разговор, распознаёт речь и показывает подсказку прямо поверх остальных окон.
Причём вся цепочка может работать локально: от захвата звука до speech-to-text и LLM.
Исходники открыты, проект распространяется под MIT.
GitHub: https://github.com/liveinno/brainstorm-buddy
Скачать:
В этой статье хочу рассказать не столько о кнопках приложения, сколько о том, что оказалось внутри.
Потому что самое интересное началось примерно в тот момент, когда схема «взяли звук → отправили в Whisper → спросили LLM» встретилась с реальным разговором.
Что вообще делает BrainstormBuddy
У приложения два источника звука.
Первый — системный loopback. Это то, что я слышу из Zoom, Teams, браузера или любого другого приложения.
Второй — мой микрофон.
Они идут отдельно, поэтому система понимает, где говорит собеседник, а где пользователь.
Дальше цепочка примерно такая:
На первый взгляд здесь вообще нет ничего необычного.
На практике каждый из этих переходов превращается в отдельную инженерную проблему. Как понять, что человек закончил предложение? Что делать, если он думает и замолкает на полсекунды?
Что делать, если видео играет 40 секунд вообще без тишины?
Что будет, если LLM отвечает пять секунд, а за это время собеседник успел сказать ещё три фразы?
Что произойдёт, когда пользователь посреди звонка подключит Bluetooth-наушники? А если модель вернула HTTP 200, но самого ответа в content нет?
Вот из таких мелочей в итоге и состоит real-time AI.
Почему я не стал делать всё через облако
Самый простой вариант такого продукта — отправлять аудио на внешний STT, затем результат в облачную LLM.
Технически это заметно легче.
Но разговоры — довольно чувствительные данные. Это могут быть интервью, внутренние встречи, коммерческие переговоры.
Поэтому BrainstormBuddy изначально проектировался так, чтобы весь pipeline можно было запустить локально.
Для русского speech-to-text используется GigaAM. Модель работает через ONNX Runtime. Для английского и смешанной речи можно использовать Whisper. LLM тоже необязательно должна находиться в облаке: приложение умеет работать с Ollama и OpenAI-compatible API.
При этом я не стал делать из local-first религию.
На обычном ноутбуке небольшая локальная LLM почти неизбежно проигрывает быстрой облачной модели и по задержке, и по качеству ответа. Поэтому архитектура гибридная: пользователь сам решает, какие части pipeline оставить на машине, а какие вынести наружу.
Можно получить полностью локальную схему:
Audio → GigaAM → локальная LLM → Overlay
а можно оставить распознавание на компьютере, но использовать внешнюю LLM:
Audio → GigaAM → OpenAI-compatible API → Overlay
или подключить собственный STT-сервер.
То есть приложение скорее оркестрирует несколько независимых компонентов, чем жёстко привязывается к конкретной модели.
Текущий desktop-стек довольно обычный для Windows: C# 12, .NET 8, WPF, NAudio, ONNX Runtime, Whisper и HTTP API для языковых моделей.
Самое сложное — понять, когда человек закончил говорить
STT нельзя бесконечно кормить непрерывным аудиопотоком. Сначала его нужно разрезать на фразы.
Самый очевидный способ — считать фразу законченной после периода тишины. Но люди говорят неидеально: начинают мысль, делают паузу, продолжают. Если слишком быстро закрывать аудиочанк, LLM начинает отвечать на половину предложения. Если ждать слишком долго — пропадает ощущение real-time.
В BrainstormBuddy поэтому используется несколько ограничений. Обычно фраза закрывается после примерно 1,8 секунды тишины. Но если звук идёт непрерывно и пауза вообще не наступает, срабатывает дополнительный лимит и накопленный фрагмент всё равно отправляется в STT. Есть и абсолютный потолок длительности чанка как защита от аномальных ситуаций.
Этот механизм появился после вполне реального бага: включаешь видео, звук идёт непрерывно, индикатор работает, но транскрипции нет. Приложение не зависло — оно просто всё ещё ждёт окончания одной гигантской «фразы».
Аудиопауза ещё не означает конец мысли
Есть другая ситуация:
«Мы решили что…»
Пауза.
«…перенесём сервис в Kubernetes».
Для аудиодетектора это могут быть две разные фразы, но по смыслу это одно предложение.
Поэтому поверх STT появился небольшой семантический слой. Если распознанная реплика выглядит незаконченной, запрос к LLM можно ненадолго придержать и объединить со следующим фрагментом.
Важно, что сама транскрипция при этом появляется сразу. Задерживается только AI-ответ. Пользователь видит, что приложение его услышало, но модель не начинает отвечать на каждую половину предложения.
Это довольно простая эвристика, но в живом разговоре она иногда полезнее добавления ещё одной модели в pipeline.
Почему STT и LLM пришлось разделить
Изначально обработка была практически последовательной: получили аудио, распознали, отправили текст в LLM, дождались ответа и пошли дальше.
Проблема обнаружилась быстро. Если LLM отвечает пять секунд, разговор на эти пять секунд не останавливается. Собеседник продолжает говорить, новые аудиофрагменты накапливаются, а транскрипция начинает отставать.
Поэтому STT и генерация теперь работают независимо:
Audio → STT Queue → STT Worker → текст на экран→ LLM Queue → LLM Worker → AI-ответ
Как только STT закончил распознавание, фраза сразу появляется в интерфейсе. Затем она попадает в отдельную очередь LLM, и ответ добавляется позже.
Для меня это стало одним из главных выводов проекта: медленная LLM не должна блокировать транскрипцию.
В real-time приложениях лучше вообще меньше думать о последовательной цепочке функций и больше — о нескольких независимых потоках данных, которые работают с разной скоростью.
Контекст иногда важнее размера модели
Можно подключить мощную LLM, но если она ничего не знает о пользователе, ответы на собеседовании будут слишком общими или начнут содержать придуманные факты.
Поэтому в BrainstormBuddy можно передавать контекст: опыт, проекты, технологии и другую информацию, которую модель должна учитывать при формировании подсказок.
Кроме интервью есть несколько режимов работы: встречи, продажи, brainstorming, перевод. По сути BrainstormBuddy постепенно превратился не столько в «помощника для интервью», сколько в движок для AI-подсказок во время живого разговора.
Overlay и ощущение real-time
Результат показывается в прозрачном WPF-окне поверх остальных приложений. Сначала появляется транскрипция, затем к ней добавляется ответ AI.
Это маленькая деталь, которая сильно влияет на UX. Пользователь не смотрит пять секунд на пустое окно и не думает, работает ли приложение вообще. Он сразу видит: речь распознана, ответ генерируется.
Overlay можно менять по размеру, положению и прозрачности.
А еще есть транскрибация из файлов, саммари встреч, протокол встречи и много еще другого. Буду рад предложениям по улучшению функционала, в статью не получается все фичи приземлить.