БлогСтатья
Назад

Как мы собирали собственного голосового AI-оператора — FreeSWITCH + Whisper + Alem.ai + LLM + Silero + DamuBPM

Как мы собирали собственного голосового AI-оператора

FreeSWITCH + Whisper + Alem.ai + локальные LLM + Silero TTS + DamuBPM. История о том, как обычный телефонный звонок постепенно превратился в полноценный интерфейс к бизнес-процессам.

Всё начиналось вроде бы просто

Последние несколько дней у нас в B-Apps был довольно интересный эксперимент.

«А давайте сделаем так, чтобы клиент позвонил по обычному телефону, а ему ответил AI».

Ну да. Просто. 😄

На бумаге всё действительно красиво:

Телефонный звонок ↓ Распознавание речи ↓ AI ↓ Генерация голоса ↓ Ответ клиенту

А потом начинаешь это реально собирать — и выясняется, что между этими пятью стрелочками находится примерно миллион мелочей.

В итоге получился рабочий прототип голосового AI-консультанта, который принимает обычный телефонный звонок, слушает человека, понимает русский и казахский, обращается к бизнес-логике DamuBPM, использует AI-инфраструктуру Alem.ai, формирует ответ и проговаривает его обратно в телефонную линию.

Первый слой — FreeSWITCH

FreeSWITCH отвечает непосредственно за телефонию: принимает звонок, работает с SIP/RTP и управляет самим разговором.

Но обычной телефонии недостаточно. Нам нужно было в реальном времени забирать голос звонящего и отправлять его в систему распознавания.

Для этого подключили mod_audio_stream.

FreeSWITCH ↓ WebSocket ↓ Python ↓ Whisper

На каждый звонок создаётся WebSocket:

/ws/{uuid}

UUID — идентификатор конкретного звонка. FreeSWITCH начинает передавать туда PCM-аудио, а Python-сервис постоянно его принимает.

Телефонный звук — отдельная история

Телефонный звук — это вам не студийный микрофон.

Реальный звонок у нас шёл через PCMA, 8000 Hz. Потом поток преобразовывался для Whisper.

На первых тестах Whisper иногда слышал что-то совершенно своё.

Говоришь: «Привет».
Whisper уверенно отвечает: «Беги».

Ну спасибо. 😄

Начали разбираться и быстро выяснили: просто отправлять Whisper каждые три секунды аудио — не лучшая идея.

Фраза может начинаться в одном куске, заканчиваться в другом, плюс тишина, шумы, дыхание и особенности телефонного кодека.

Добавили Voice Activity Detection

Каждый небольшой аудиофрейм анализируется по RMS.

Речь началась Начинаем собирать аудио.
Речь продолжается Продолжаем заполнять буфер.
Появилась тишина Считаем реплику законченной.
Pre-roll Сохраняем небольшой кусок перед началом речи, чтобы не отрезать первые звуки.

То есть если человек сказал:

«Здравствуйте, меня интересует CRM»

мы стараемся отправить Whisper именно всю реплику, а не что-нибудь в духе:

«…ствуйте, меня интересует…»

Whisper на GPU

После нескольких экспериментов пришли к faster-whisper и Whisper large-v3 на NVIDIA GPU.

Whisper получает готовую реплику и возвращает уже нормальный текст:

«Меня зовут Ельдар, я из Алматы, нас интересует автоматизация отдела продаж».

Отдельно экспериментировали с русским и казахским.

Whisper может автоматически определить язык, поэтому нам не обязательно заставлять человека нажимать: «один — русский, два — казахский».

Человек просто говорит.

DamuBPM как оркестратор

Следующая задача — AI.

Здесь мы принципиально не хотели делать архитектуру:

Whisper ↓ LLM ↓ Ответ

Нам хотелось, чтобы управление разговором оставалось внутри нашей платформы.

Поэтому в схеме появился DamuBPM.

Whisper передаёт распознанную реплику в DamuBPM, где находится бизнес-логика разговора:

  • история диалога;
  • состояние обращения;
  • данные клиента;
  • правила дальнейшего движения процесса;
  • вызовы внутренних сервисов;
  • решение о следующем шаге.

Alem.ai и AI-слой

Для AI-части мы используем в том числе Alem.ai.

Для нас это важный архитектурный момент: голосовой оператор не должен быть намертво связан с одной конкретной моделью.

Сегодня одна модель лучше решает одну задачу, завтра появляется другая.

Где-то нужна мощная модель. Где-то маленькая и быстрая.

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

«Из какого вы города?»

Поэтому мы экспериментируем одновременно с AI через Alem.ai и локальными моделями через Ollama.

FreeSWITCH ↓ Whisper ↓ DamuBPM ↓ Alem.ai / Local LLM ↓ DamuBPM ↓ TTS ↓ FreeSWITCH

AI-консультант должен не просто болтать

Консультант постепенно должен выяснить:

  • как зовут клиента;
  • из какого он города;
  • какой продукт его интересует;
  • какую задачу он хочет решить;
  • какая компания;
  • сколько будет пользователей;
  • что используется сейчас;
  • когда планируется запуск;
  • как с клиентом связаться.

Но при этом нельзя превращать разговор в допрос.

— Как вас зовут?
— Город?
— Компания?
— Количество пользователей?
— Срок?

Так никто с роботом разговаривать не захочет.

Поэтому LLM получает историю последних реплик и системный prompt.

Одно из важных правил:

«Если клиент уже сообщил информацию — не спрашивай её повторно».

И тут появился смешной баг

Клиент говорит:

«Меня зовут Ельдар».

А через пару реплик AI:

«Подскажите, пожалуйста, как вас зовут?»

Я уже думаю: ну приехали. 😄

Оказалось, проблема была не столько в самой модели, сколько в истории сообщений.

Некоторые сообщения уходили без корректного role.

Для LLM есть большая разница между:

{


"role": "user",
"content": "Меня зовут Ельдар"
}

и просто:

{


"content": "Меня зовут Ельдар"
}

Когда история стала передаваться нормально:

system ↓ assistant ↓ user ↓ assistant ↓ user

модель стала гораздо лучше держать контекст разговора.

В телефонии latency чувствуется особенно сильно

Для обычного чата две-три секунды ожидания — нормально.

Для телефона — вечность.

Попробуйте во время разговора молчать секунд семь после каждого вопроса.

«Алло?.. Алло? Вы там?..»

Поэтому мы тестируем разные варианты.

Для задач, где нужен внешний AI-сервис, используем Alem.ai.

Для локального контура — модели Qwen через Ollama.

В локальном варианте отключили thinking:

"think": false

Ограничили контекст и максимальную длину ответа.

Голосовому консультанту не нужно писать диссертацию.

Ему нужно сказать:

«Понял. Подскажите, пожалуйста, сколько сотрудников будет работать с системой?»

Всё.

«Минуточку…» оказалось отдельной задачей

Некоторые запросы к бизнес-логике могут занимать от трёх до десяти секунд.

Чтобы человек не думал, что связь пропала, добавили человеческие fillers.

Если ответ быстрый — бот ничего лишнего не говорит.

Если ожидание затянулось:

  • «Минуточку»;
  • «Сейчас уточню»;
  • «Дайте подумать».

Но здесь появилась интересная проблема.

Нужно отличать:

AI думает Можно проиграть filler.
AI ждёт клиента Нужно молчать.

Если консультант уже спросил:

«Из какого вы города?»

и клиент просто молчит, нельзя внезапно отвечать:

«Минуточку…»

Минуточку чего? Мы же его ждём. 😄

Так постепенно появилась маленькая state machine:

LISTENING ↓ PROCESSING ↓ SPEAKING ↓ LISTENING

Fillers разрешены только в состоянии PROCESSING.

Голос — Silero TTS

Хотелось сделать синтез речи локально и без оплаты за каждую фразу.

Для первого рабочего варианта подключили Silero TTS.

Он лёгкий, быстрый, работает локально и нормально подходит для телефонного сценария.

AI response ↓ Silero TTS ↓ WAV ↓ FreeSWITCH ↓ Абонент

Для воспроизведения сделали отдельный небольшой FastAPI-сервис play.py.

Он принимает UUID звонка и WAV-файл, сохраняет его во временный каталог и через FreeSWITCH WebAPI вызывает:

uuid_broadcast

Когда AI начал говорить одновременно с самим собой

Один из самых неприятных багов: вопросы AI начали иногда накладываться друг на друга.

Например:

«Подскажите, пожалуйста, из какого вы…»

и поверх:

«А какая у вас компания?»

Причина оказалась логичной.

HTTP-запрос /play уже вернул OK, но это означает только:

«FreeSWITCH принял команду».

Это НЕ означает:

«FreeSWITCH закончил проигрывать WAV».

Пришлось учитывать реальную продолжительность WAV и сделать общий playback lock.

Теперь принцип жёсткий:

Одна реплика клиента ↓ Один ответ AI ↓ Полностью проиграли звук ↓ Только после этого следующий turn

AI не должен слушать самого себя

Это ещё одна классическая проблема голосовых роботов.

Пока AI говорит, Whisper не должен воспринимать собственный TTS как реплику клиента.

Иначе появляется почти философский эксперимент:

AI говорит ↓ Whisper распознаёт AI ↓ AI отвечает самому себе ↓ Whisper снова распознаёт AI ↓ ...

Можно оставить на ночь и утром посмотреть, к чему они пришли. 😄

Поэтому состояния вроде bot_speaking, turn_busy и блокировки оказались не менее важными, чем сами нейросети.

Первое приветствие

Как только звонок принят, система сразу говорит:

«Вы позвонили в компанию Би апс. По какому вопросу вы звоните? Отдел продаж или техническая поддержка?»

HANGUP как управляющая команда

Понадобилась возможность, чтобы бизнес-логика сама решила: разговор пора завершать.

Для этого используем простой маркер:

HANGUP

Например, DamuBPM/AI возвращает:

Спасибо за обращение. Всего доброго! HANGUP

Python видит управляющий маркер, но клиент его не слышит.

Для TTS остаётся:

«Спасибо за обращение. Всего доброго!»

Мы ждём полного окончания WAV, и только потом FreeSWITCH корректно завершает звонок.

Что получилось в итоге

☎ Клиент ↓ FreeSWITCH ↓ mod_audio_stream ↓ Python ↓ Whisper large-v3 ↓ DamuBPM ↓ Alem.ai / Local LLM ↓ DamuBPM ↓ Silero TTS ↓ WAV ↓ FreeSWITCH WebAPI ↓ ☎ Клиент слышит ответ

Самое интересное — AI + DamuBPM

Потому что AI сам по себе умеет разговаривать.

Но когда за ним находится BPMS, он уже может не только разговаривать, но и запускать реальные действия.

«Проверьте статус моей заявки».

AI понимает намерение → DamuBPM находит процесс → получает статус → Alem.ai или другая модель формирует человеческий ответ → клиент слышит его по телефону.

Или:

  • «Соедините с технической поддержкой»;
  • «Создайте обращение»;
  • «У меня не работает система»;
  • «Какой статус моей заявки?»;
  • «Когда будет готов документ?».

Дальше можно классифицировать обращение, зарегистрировать тикет, определить клиента, проверить SLA, найти ответ в базе знаний и только при необходимости подключить человека.

AI становится интерфейсом

Вот здесь AI перестаёт быть просто чат-ботом.

Он становится интерфейсом к бизнес-процессам.

Web Обычный интерфейс через браузер.
Mobile Мобильное приложение.
REST API Машинная интеграция.
Voice Обычный человеческий голос.

При этом архитектура не завязана намертво на одного AI-провайдера.

Мы можем использовать Alem.ai, локальные модели, специализированные модели или их комбинацию — в зависимости от задачи, требований к скорости, безопасности и инфраструктуре.

Что ещё предстоит сделать

  • улучшать качество казахской речи;
  • бороться с плохими телефонными каналами;
  • ещё уменьшать latency;
  • реализовать нормальный barge-in;
  • улучшить Voice Activity Detection;
  • экспериментировать с голосами;
  • тестировать всё на реальных звонках, а не только на хороших WAV.

То есть с машинами на фоне, плохими гарнитурами, эхом и знаменитым:

«Алло, меня слышно?»

Но уже сейчас система реально разговаривает.

Когда мы начинали, задача звучала: «Давайте заставим AI отвечать на телефон».

Теперь она звучит немного иначе:

«Давайте дадим бизнес-процессам возможность разговаривать с человеком».

И вот эта формулировка мне нравится намного больше.

Читайте больше статей на другие интересные темы

Как мы собирали собственного голосового AI-оператора — FreeSWITCH + Whisper + Alem.ai + LLM + Silero + DamuBPM

Оставьте заявку и мы свяжемся с вами в ближайшее время

Некорректный формат email