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

Как мы собирали собственного голосового 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 отвечать на телефон».

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

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

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

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

Бизнес-процессы под контролем: реальные истории успеха BPM

Жунусов Валихан

Как BPM, ERP, СЭД и КЭДО трансформируют ваш бизнес: путь к эффективности

Автоматизация бизнеса: как выйти из рутины и начать расти

Жунусов Валихан

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

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

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