Краткий обзор архитектуры
Приложение секретаря организовано в виде композиции docker-контейнеров, взаимодействующих через HTTP/HTTPS и WebSocket-соединения, которые устанавливаются на отдельном хосте (рисунок 1).
Рисунок 1
Компоненты системы
Docker-контейнеры
| Контейнер | Описание | Порт(ы) | Технологии |
|---|---|---|---|
| clerk | HTTP-сервер для приёма аудио, поиска контактов и управления телефонной книгой | 8000/tcp | Go |
| asr-server | Распознавание речи и компиляция моделей | 8003/tcp | Python |
| vosk-server | WebSocket-сервер для распознавания аудио | 2700/tcp | Python, Vosk |
| mongo | Хранилище истории вызовов и статистики | 27017/tcp | MongoDB |
| keycloak | Сервис авторизации и управления учётными записями | 8085/tcp | Java, Keycloak |
| keycloak-db | База данных для Keycloak | 5432/tcp | PostgreSQL |
| nginx | Прокси-сервер для веб-интерфейса | 8080/tcp | Nginx |
| webbackend | Бэкенд для веб-интерфейса | 8091/tcp | — |
ECSS-10 SSW
| Сервис | Описание |
|---|---|
| RestFS | HTTP интерфейс с помощью которого взаимодействует MSR и IVR script взаимодействуют с clerk |
| MSR | Медиа сервер. Отправляет аудио поток от абонента через restfs в clerk |
| IVR Script | Скрипт реализующий основную логику работы с clerk |
Структура docker compose
Проект построен на модульном принципе, что позволяет гибко комбинировать сервисы:
| Модуль | Файл | Назначение |
|---|---|---|
| Базовое ядро | compose.yaml | Основные сервисы: Clerk, ASR-Server, Vosk-Server, Mongo, Nginx, WebBackend. Обязателен для запуска |
| Модуль авторизации | compose.keycloak.yaml | Интеграция Keycloak + Keycloak-DB + Keycloak-Importer в общую сеть |
| Модуль мониторинга | compose.peeper.yaml | Стек для сбора метрик и логов (опционально) |
Варианты запуска
| Конфигурация | Состав | Использование |
|---|---|---|
| Встроенный Keycloak | Ядро + Keycloak | Полноценная автономная система с авторизацией |
| Внешний Keycloak | Только ядро | Интеграция с существующим Keycloak-сервером. Параметры задаются через .env или переменные окружения |
| Встроенный Keycloak + Мониторинг | Ядро + Keycloak + Peeper | Полный стек с мониторингом |
| Внешний Keycloak + Мониторинг | Ядро + Peeper | Мониторинг с внешней авторизацией |
Детальное описание сервисов
Clerk (Go-сервер)
Назначение: Основной сервис, реализующий логику голосового помощника.
Функции:
- Приём аудиопотока от MSR/Softswitch через HTTP POST
/speech-recognition/{filename}; Проксирование аудио в ASR-Server по WebSocket;
Получение гипотез распознавания от ASR;
Диалоговый поиск контакта в телефонной книге с учётом контекста;
Формирование ответов для MSR (номер, уточняющий вопрос, ошибка);
Ведение истории в MongoDB;
Обновление телефонной книги (по расписанию и вручную через
/pb_update);Запуск перекомпиляции ASR-модели;
Публикация статусов через WebSocket
/status.
ASR-Server (Python-сервер)
Назначение: Обработка аудио, распознавание речи и управление голосовой моделью.
Функции:
WebSocket
/ws/asr_vad— приём аудио, VAD-сегментация и распознавание через Vosk;Отправка partial и final результатов в Clerk;
Получение телефонной книги от Clerk для подготовки словаря;
Компиляция модели с новым словарём (фонетика, граф распознавания);
Перезапуск Vosk-Server с новой моделью;
Предоставление статуса компиляции через
/status/compile.
Vosk-Server (Python-сервер)
Назначение: Непосредственное распознавание речи на основе модели Vosk.
Функции:
Приём аудио по WebSocket;
Распознавание с возвратом промежуточных (partial) и финальных (final) результатов;
Перезагрузка модели по команде от ASR-Server.
Mongo (MongoDB)
Назначение: Хранение истории и статистики.
Коллекции:
history — записи вызовов с полями:
call_ref— уникальный идентификатор вызова;source— номер звонящего;clerk_num— номер автосекретаря;number— найденный номер;status—unknown/good/bad/call_ended/no_number;date— дата и время;files— сопоставление аудиофайлов и распознанных текстов;additional— дополнительные параметры (частота дискретизации, источник).
stats — агрегированная статистика по источникам:
Source— источник вызовов;RequestCount— общее количество;Correct— успешные;Failed— неуспешные.
WebBackend
Назначение: Бэкенд для веб-интерфейса.
Функции:
Аутентификация через Keycloak;
Предоставление WebSocket-каналов для UI;
Работа с MongoDB для отображения истории и статистики.
Nginx/Frontend
Назначение: Веб-интерфейс и проксирование.
Функции:
Раздача статического фронтенда;
Проксирование запросов UI к:
Clerk (история, статистика, поиск, телефонная книга, статусы);
WebBackend (WebSocket-каналы).
Отдача логов и PCM-файлов для анализа.
Keycloak + Keycloak-DB + Keycloak-Importer
Назначение: Авторизация и управление пользователями.
Компоненты:
Keycloak — сервер авторизации;
Keycloak-DB — PostgreSQL для хранения realm'ов, клиентов, пользователей;
Keycloak-Importer — однократный импорт настроек (
clerk-realm.json) при старте.
Процесс загрузки телефонной книги и компиляции модели
Источники телефонной книги
Система поддерживает два источника, настраиваемых через переменную PB_SOURCES:
AddressBook (GraphQL-сервис)
Корпоративный источник с иерархической структурой (отделы, группы, подгруппы);
Авторизация через Keycloak (JWT) или API-ключ;
Параметры:
ADDRESS_BOOK_URL— URL GraphQL-API;AB_AUTH_TYPE—BY_KEYCLOAK_JWTилиAPI_KEY;AB_AUTH_HOST,AB_AUTH_PORT,AB_AUTH_REALM,AB_CLIENT_ID,AB_CLIENT_SECRET— для JWT;AB_API_KEY— для API-ключа.
VCF-файл (HTTP-источник)
Статический файл формата vCard;
Доступен по HTTP/HTTPS;
Параметр:
PB_URL— URL файла.
Комбинированный режим
PB_SOURCES=ADDRESSBOOK,VCF— Address Book имеет приоритет, VCF игнорируется;PB_SOURCES=ADDRESSBOOK— только Address Book;PB_SOURCES=VCF— только VCF.
Процесс загрузки и подготовки
Инициализация (при старте):
Clerk загружает телефонную книгу из настроенного источника;
Выполняется маппинг данных во внутренний формат (
contact.Contact);Контакты валидируются (обязательные поля, корректность номеров, ФИО).
Периодическое обновление (каждые 12 часов или по запросу
/pb_update):Повторная загрузка из источника;
Применение изменений через
ApplyDiff(добавление, обновление, удаление);Сохранение пользовательских настроек (множители);
Отправка подготовленного словаря в ASR-Server.
Компиляция голосовой модели
Подготовка словаря:
Контакты транслитерируются и форматируются;
Учитывается поле «Множитель» для увеличения веса контакта;
Словарь отправляется в ASR-Server через
SendPrepare.
Запуск компиляции:
ASR-Server получает запрос
SendRecompile;Запускается фоновая задача компиляции;
Создаются фонетические данные и пересобирается поисковый граф.
Ожидание завершения:
Clerk периодически опрашивает статус (
GetStatusCompile);Статусы:
wait→compile→ready/fail.
Перезапуск Vosk-Server:
Новая модель копируется в Vosk-Server;
Через WebSocket подаётся команда на загрузку модели.
Публикация статусов:
Статус компиляции транслируется всем подписчикам через WebSocket
/status.
Механизм «тюнинга» модели
В веб-интерфейсе для контакта можно задать значение «Множитель». Значение сохраняется в оперативной памяти и в backup.pb.
При обновлении телефонной книги:
- Загружается актуальный список контактов из источника;
- Сохранённые значения множителей переносятся по UID контактов;
- Контакт с множителем N фигурирует в тренировочном словаре N раз;
- Запускается перекомпиляция модели.
Множитель меняет относительный вес контакта, повышая вероятность его выбора при распознавании.
Обработка вызова
Приём аудио от MSR/Softswitch
Запрос:
POST /speech-recognition/domain/{domain}/{filename};Формат аудио: PCM/WAV, 48 kHz, моно, 16 бит (s16E);
Заголовки:
x-call-ref— уникальный идентификатор вызова;x-asr-service— целевой ASR (для балансировки);Expect: 100-continue— ожидание подтверждения.
Процесс:
Clerk извлекает домен, имя файла, call-ref;
По имени файла определяет номера звонящего и автосекретаря;
Создаёт контекст обработки;
Запускает потоковую отправку аудио в ASR-Server по WebSocket.
Запись истории и аудио
Для каждого вызова создаётся запись в MongoDB (история);
Аудиопоток сохраняется на диск для последующего анализа;
Итоговый результат обновляет запись истории.
Передача аудио в ASR по WebSocket
Clerk устанавливает WebSocket-соединение с ASR-Server;
Аудио отправляется бинарными сообщениями;
В конце передаётся сигнал EOF;
ASR-Server возвращает текстовые сообщения:
partial— промежуточный результат;result— финальный результат.
VAD и сегментация речи (в ASR-Server)
Клиент шлёт аудио-чанки │ ▼ [Буфер VAD] │ ▼ Анализ (200 мс) │ ├─ Тишина → ожидание ├─ Начало речи → старт сегмента └─ Конец речи → завершение сегмента │ ▼ Накопление секундных порций │ ▼ Отправка в Vosk (порциями по 1 секунде) │ ▼ Получение partial-результатов → отправка в Clerk │ ▼ Завершение сегмента → отправка целого отрезка в Vosk │ ▼ Получение final-результата → отправка в Clerk
Диалоговый поиск контакта
Алгоритм для каждого текстового сегмента:
Добавить сегмент к общему накопленному тексту;
Нормализовать текст (регистр, лишние символы);
Выполнить поиск в телефонной книге:
По полному имени, фамилии, отчеству;
По подразделению, группе (при наличии).
Возможные исходы:
Однозначный номер → немедленный ответ
200 OKс номером;Несколько контактов → уточняющий вопрос;
Нет контакта → сообщение об ошибке;
У контакта нет номера → сообщение об ошибке;
Пустой текст → ожидание следующих сегментов.
Контекст диалога:
Хранится по
call_ref;Содержит предыдущий запрос и «следующий атрибут» для уточнения (фамилия, имя, подразделение).
Итоговый ответ и взаимодействие с MSR
Ничего не распознано
HTTP/1.1 206 Partial Content
{
"done": false,
"recognized": "",
"negative_url": "negative/<callRef>"
}
Уточнение / Нет контакта
HTTP/1.1 206 Partial Content
{
"done": true,
"recognized": "<текст>",
"answer": "<фраза для TTS>",
"negative_url": "negative/<callRef>"
}
Успешный поиск
HTTP/1.1 200 OK
{
"done": true,
"recognized": "<текст>",
"number": "<найденный номер>",
"positive_url": "positive/<callRef>",
"negative_url": "negative/<callRef>"
}
История и статистика
Статусы истории:
unknown— автоматически при создании записи или при неоднозначном результате;good— при успешном поиске или по запросу/positive/{callRef};bad— по запросу/negative/{callRef};call_ended— при обрыве вызова или таймауте ASR;no_number— контакт найден, но без номера.
История вызовов
Эндпоинты Clerk:
GET /history— список всех записей;GET /history/{callRef}— конкретная запись;DELETE /history— удаление истории.
Структура записи:
{
"_id": "ObjectId(...)",
"call_ref": "3095-6666",
"req_source": "ivr",
"detection_attr": "fio",
"status": "unknown",
"date": "2026-03-20T11:17:57.709Z",
"files": {
"2026-03-20_11-17-54-519208_asr_3095-6666.wav": "ковалев"
},
"additional": {
"source": "msr",
"sample_rate": 48000
},
"source": "3095",
"clerk_num": "6666",
"number": null
}
Статистика
Периодический пересчёт (раз в 10 минут);
Хранится в коллекции
stats;Поля:
Source,RequestCount,Correct,Failed;Эндпоинт:
GET /stats.
Мониторинг (опционально)
Модуль мониторинга (compose.peeper.yaml) включает стек для сбора метрик и логов:
Сбор метрик с Docker-контейнеров;
Визуализация состояния системы;
Анализ логов.
Порты и сетевые взаимодействия
| Сервис | Порт | Протокол | Назначение |
|---|---|---|---|
| Clerk | 8000/tcp | HTTP/WS | Основное API, WebSocket статусов |
| ASR-Server | 8003/tcp | HTTP/WS | Распознавание, компиляция |
| Vosk-Server | 2700/tcp | WebSocket | Распознавание аудио |
| Mongo | 27017/tcp | TCP | База данных |
| Keycloak | 8085/tcp | HTTP | Авторизация |
| Keycloak-DB | 5432/tcp | TCP | PostgreSQL |
| Nginx | 8080/tcp | HTTP | Frontend, прокси |
| WebBackend | 8091/tcp | HTTP/WS | Backend для UI |