опыт из первых рук - self-hosted ai

Скрайб: ИИ-ассистент, который ходит на мои Zoom-звонки

Бот-участник заходит в звонок по календарю, ведёт транскрипт «кто что сказал», отвечает голосом за ~2 секунды, понимает команды вроде «помолчи, пока я не скажу "продолжаем"» и помнит историю каждого клиента. Всё крутится на моём собственном VPS - записи звонков не уходят ни в какой чужой сервис. Собрано за 9 дней вечерами в паре с Claude Code.

~2 сзадержка голосового ответа (realtime-диалог)
0.6 сот конца фразы до строки в live-транскрипте
1 VPS8 ГБ RAM, всё self-hosted, без подписок на бот-сервисы
9 днейот идеи до боевых звонков с клиентами

01Что он умеет

Сам приходит на звонки

Смотрит в календарь: фиолетовый ивент = коучинговая сессия, любой ивент с zoom-ссылкой = цель. За 2 минуты до старта бот уже в комнате, мне в Telegram падает ссылка на live-транскрипт.

Транскрипт по говорящим

Отдельная аудио-дорожка на каждого участника - «кто сказал» известно точно, без диаризации. На клиентских сессиях имена заменяются на «Коуч» и «Клиент» ещё до записи в базу.

Голосовой диалог

«Скрайб, ...» - и он отвечает голосом в звонок. После ответа 20 секунд слушает уточнения, потом сам выходит из беседы и ждёт следующего обращения.

Инструкции натуральным языком

«Помолчи, пока я не скажу "продолжаем"», «напомни через 5 минут», «отвечай только в чат», «когда заговорим про бюджет - скажи». Подтверждает своими словами и исполняет.

Видит экран и чат

Кадры демонстрации экрана (один на слайд) описываются vision-моделью и попадают в контекст. Сообщения чата Zoom сохраняются в общую ленту.

Память по историям общения

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

Спросить вне звонка

Личный Telegram-канал: спрашиваю обычным текстом - что решили на прошлой сессии, где обсуждали конкретную тему, какие звонки были на неделе, пришли транскрипт. Оттуда же отправляю его в звонок по ссылке. Канал закрыт списком: всем, кроме меня, бот просто не отвечает - ни ответа, ни отказа.

Чистый лист по умолчанию

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

02Архитектура

Три сервиса на одном VPS: календарь-зеркало, self-hosted бот-платформа Attendee и ядро «scribe-core», где живёт вся логика. Внешних зависимостей три: Groq для распознавания речи, OpenAI для realtime-голоса и OpenRouter для остальных LLM-задач.

Google Calendar ЦВЕТ ИВЕНТА = ТИП ВСТРЕЧИ SYNC 60 C Календарь-зеркало двусторонний CRUD, метаданные, фиолетовый = коучинг 1-1 Оркестратор кому идти и когда, ретраи, чистка зомби, TG-уведомления СОЗДАТЬ БОТА Zoom-звонок Я + КЛИЕНТ + БОТ-УЧАСТНИК SDK Attendee (self-hosted) open-source бот-платформа, Zoom Meeting SDK, 5 контейнеров ZOOM-ЗАПИСЬ НЕ ИСПОЛЬЗУЕТСЯ АУДИО ПО СПИКЕРАМ · WS МИКС 24 КГЦ · WS КАДРЫ ЭКРАНА · WS ЧАТ · WEBHOOK ГОЛОС И ЧАТ ОБРАТНО scribe-core - ядро (FastAPI + SQLite) STT-конвейер VAD -> Whisper, фильтр глюков Realtime-голос диалог ~2 с, окно 20 с, стоп Директивы «помолчи до фразы X», таймеры Кадры экрана + vision кадр на слайд, описание в ленту Память по историям только по подтверждённой привязке Саммари темы, договорённости, задачи Анонимизация Коуч / Клиент до записи в БД Ответчик-фолбэк LLM + TTS, если realtime упал Telegram мне «бот в звонке» + саммари после Админка live-транскрипт, клиенты, кадры SQLite + файлы на VPS данные не покидают мой сервер
Внешние LLM-вызовы (Groq, OpenAI Realtime, OpenRouter) идут из scribe-core через прокси в Европе - об этом ниже, в граблях.

Почему такие блоки

03Жизнь голосового обращения

Самое интересное - как бот понимает, что говорят именно ему, и не лезет в чужой разговор. Обращение по имени сначала проходит через «диспетчер», и только потом решается, что это было: вопрос, команда замолчать, поведенческая инструкция - или вообще разговор О боте в третьем лице, на который отвечать не нужно.

«Скрайб, ...» wake-regex ловит и «скрайп», и «скай» «стоп»? «отбой»? проверяется ПЕРВЫМ: мгновенно замолкает LLM-классификатор вопрос, инструкция или разговор о боте (не с ним)? О БОТЕ ВОПРОС ИНСТРУКЦИЯ Не обращение третье лицо - игнор Realtime-диалог ответ ~2 с, потом окно 20 с на уточнения - дальше слушает имя Директива «молчу до фразы X», таймер, «только чат», наблюдение за темой; валидируется до принятия, молчание - с лимитом времени Как он не мешает живому разговору окно диалога закрылось - модель отключается от аудио и «слышит» только своё имя в транскрипте пока бот говорит - его же голос из микса гейтируется, чтобы он не отвечал сам себе (эхо) на групповом звонке готовый ответ ждёт паузы в эфире (~1.5 с тишины) и только тогда звучит; нет паузы за 7 с - ответ уходит текстом в чат, а повторное «Скрайб!» значит «говори сейчас»
Перебить бота можно голосом в любой момент - barge-in отменяет генерацию на полуслове.

04Стек и цифры

СлойЧем сделаноЗаметка
Бот в звонкеAttendee (open source, ELv2) + Zoom Meeting SDKGeneral App в Zoom Marketplace, ревью не нужно для своих встреч
РаспознаваниеКаскад: Groq Whisper large-v3, при rate-limit - фолбэк OpenAI Whisper; чанки по VAD0.6-0.7 с на фразу; чанк теряется только при отказе обоих движков, под нагрузкой сегменты автоматически укрупняются
Realtime-голосOpenAI gpt-realtime по websocketсемантический VAD на их стороне, barge-in из коробки
Остальные LLMOpenRouter (классификатор, vision, саммари, память)быстрые задачи - gpt-4o-mini, саммари - Claude
Ядро и календарьPython + FastAPI + SQLite, vanilla JSбез фреймворков на фронте - страницы просто быстрые
ИнфраструктураDocker на VPS 8 ГБ, nginx, git-деплойбот в звонке ест 1-2 ГБ - это главный лимит параллельности

Качество держат ~400 автотестов и smoke-прогон по проду после каждого деплоя: проверяется всё вплоть до инварианта «в коучинговых транскриптах нет ни одного настоящего имени».

05Грабли, которые сэкономят вам дни

06Как это строилось

Весь проект - парная работа с Claude Code: я формулирую требования голосом и текстом, агенты пишут код, деплоят и сами проверяют результат в браузере. Что сработало в процессе: