Cервис "Автосекретарь"
Описание сервиса
Сервис автосекретарь распознает имя и фамилию абонента, которому вы хотите позвонить. После чего производит вызов на данного абонента.
Пример:
Абонент снимает трубку и набирает номер, на который завязан ivr-скрипт автосекретаря. После приветствия, абонент произносит фразу: Владислав Блинов. Система распознает данные слова и пытается найти в телефонной книге, соответствие имени и фамилии с номером. В случае успеха, система производит вызов на номер абонента.
Архитектура
- Clerk — он же ecss-clerk или автосекретарь. HTTP-сервер (elixir/phoenix), принимающий запросы на распознавание и отдающий результаты в виде JSON;
- Asr Manager — HTTP-сервер (python), который управляет TCP-инстансами Kaldi. Может пересобрать модель, добавив новые слова, держать несколько экземпляров tcp-серверов и управлять ими;
- TCP Asr Server — собственно, один из инстансов, которым управляет Asr Manager.
Принцип работы
Пользователь снимает трубку и набирает номер, на который завязан ivr-скрипт автосекретаря. После синтезированного приветствия, в котором пользователя просят произнести имя и фамилию, поток с его микрофона отправляется на специальный порт MSR, который работает по следующему принципу: определяет тишину и нарезает по ней чанки. Каждый чанк отправляется отдельным HTTP POST-запросом в сторону ecss-clerk (через restfs) с заголовком "Content-type: Transfer-Encoding". По имени wav-файла ecss-clerk и идентифицирует чанк. Каждый чанк отправляется на распознавание, по результату которого phonebook пытается найти подходящий номер. Если не удалось, clerk просит следующий чанк и так до тех пор, пока не получится найти номер или не сработает таймаут в ivr-скрипте.
В случае успеха, json с номером летит на MSR через restfs, MSR отдает json ядру. Промежуточные ответы тоже доходят до ядра. Они имеют HTTP-код 206 и не содержат в прилагаемом теле json-а поля с номером, но имеют поле с распознанной строкой. Имея эту строку, по таймауту ivr может синтезировать ответ для пользователя, уведомив о том, что найти распознанную строку не удалось. Если до таймаута ecss-clerk не пришлёт ответ ядру, то синтезированное уведомление не будет содержать распознанной строки.
Синтез происходит через restfs. Resfts проксирует запрос на настроенный tts-сервер.
После получения PCM ecss-clerk выполняет следующие действия:
- Отправляет pcm на распознавание (через TCP);
- По результату распознавания пытается найти номер в телефонной книге;
- Посылает ответ.
Принцип поиска распознанной строки в телефонной книге
Ответ от ASR
На этапе поиска по телефонной книге уже получен ответ от ASR.
Причем ответ — это не просто строка, а список строк.
Условный пример ответа от ASR:
алексей
алексей лосев
алексей лысенко
Который преобразуем в список вида:
["алексей лысенко", "алексей лосев", "алексей"]
Где "алексей лысенко" финальный результат, а остальные промежуточные.
Причина, по которой мы учитываем промежуточные варианты проста — в них может быть правильный вариант распознавания. Вероятность этого мала, но она есть.
По какой книге производится поиск
Поиск производится по xml-книге, взятой с restfs.
Restfs, в свою очередь, формирует xml по данным из одного выбранного источника: ssw, mysql, ldap.
При старте автосекретаря производится импорт xml. По ней формируется структура вида ключ-значение, где ключ — имя, значение — телефон.
Кроме этого, формируется ещё одна подобная структура, содержащая уникальные имена и фамилии из книги, где ключ — уникальное имя, значение — полное имя.
По этим двум мапам — телефонной книге и уникальным именам и производится поиск.
Алгоритм поиска
- Берем строку из списка;
- Проверяем, содержится ли данная строка в таблице hard_aliases;
- Раскрываем сокращения (алиасы из таблицы aliases) и добавляем их в список, по которому будет производиться рекурсивный поиск;
- По каждой строке в сформированном списке делаем поиск:
- Формируем список с перестановками для строки:
- Проверяем, содержится ли строка (именно целиком, а не как подстрока) в уникальных именах. Если да — заменяем эту строку на полное имя;
- Ищем строку в телефонной книге. Если номер найден — поиск закончен.
- Формируем список с перестановками для строки:
- Если в списке из пункта 3 ещё остались строки, то берем следующую.
Пример
Имеем ответ от ASR:
["женя казарцев", "женя казарцев", "женя"]
Двигаемся согласно алгоритму:
- Берем строку "женя казарцев" из списка;
- Проверяем, содержится ли данная строка в таблице hard_aliases. В нашем примере её нет, так что передаем строку дальше как "женя казарцев";
- Раскрываем сокращения ["женя казарцев", "божена казарцев", "евгений казарцев", "евгения казарцев"];
По каждой строке в сформированном списке делаем поиск строки;
Строки "женя казарцев" и "божена казарцев" пропущены, т.к. пройдя всю цепочку ниже номера для них не были найдены.
Выбираем строку "евгений казарцев":
- Формируем список с перестановками для строки ["евгений казарцев", "казарцев евгений"]
- Проверяем, содержится ли строка (именно целиком, а не как подстрока) в уникальных именах. Если да — заменяем эту строку на полное имя. Если не содержится, передаем как есть — "евгений казарцев";
- Ищем строку в телефонной книге. Если не найден — берем следующий элемент из списка с перестановками евгений казарцев -> 4819;
- Формируем список с перестановками для строки ["евгений казарцев", "казарцев евгений"]
- Если в списке из пункта 3 ещё остались строки, то берем следующую. Здесь мы пропустили строки "женя казарцев" и "божена казарцев".
Установка
Установка выполняется из репозитория:
sudo sh -c "echo 'deb [arch=amd64] http://archive.eltex.org/ssw/bionic/clerk/1.0 stable main extras external' > /etc/apt/sources.list.d/eltex-ecss10-stable.list" sudo apt update
Далее необходимо установить пакет ecss-clerk:
sudo apt install ecss-clerk
Во время установки необходимо ответить на все задаваемые вопросы, особенно важно указать правильный url для импорта телефонной книги в формате xml и данные mysql пользователя.
Ответы на вопросы более детально описаны ниже.
Во время установки будут применены миграции в бд (создание базы данных, таблицы и т.д.).
Во время первого запуска начнут создаваться стандартная база слов (около 6,5 тысяч).
После создания базы рекомендуется запустить рекомпиляцию asr-модели.
Ответы на вопросы:
Вопросы | Ответы |
---|---|
Выберите IPv4 для ecss-clerk (Choise IPv4 for ecss-clerk) | IP хоста |
Укажите порт для ecss-clerk (Enter ecss-clerk port) | 9010 |
Хотите ли вы сохранить записи? (Do you want save records?) | yes |
Введите частоту дискретизации (Enter sample rate) | 8000 |
Введите URL-адрес для импорта xml-телефонной книги (Enter url for import xml phonebook) | http://system.restfs.ecss:9990/ssw?host=book&user_agent=cisco_search&translit=false&skip_no_disp=true |
Уровень логирования (Log level) | debug |
Как вы хотите настроить пользователя mysql? (How do you want configure mysql user?) | create |
Введите логин пользователя MySQL (Enter login for mysql user) | ecss-clerk |
Введите размещения пользователя MySQL (Enter mysql user host) | 127.0.0.1 |
Введите пароль для пользователя mysql (Enter password for mysql user) | password |
Управление через systemd
Убедитесь, что сервисы запущены:
sudo systemctl status ecss-clerk
sudo systemctl status kaldi-ru
В противном случае сервис необходимо включить.
sudo systemctl start ecss-clerk
sudo systemctl start kaldi-ru
Конфигурация
Конфигурация происходит при установке deb-пакета ecss-clerk. Сам файл с конфигурацией расположен по пути /etc/ecss/ecss-clerk/config.yaml:
http:
url: localhost # ip адрес ecss-clerk
port: 9010 # порт ecss-clerk
asr:
manager:
ip: localhost # ip адрес http сервера kaldi (kaldi-ru)
port: 9011 # порт http сервера kaldi (kaldi-ru)
backend: tcp_kaldi # сервис, с помощью чего kaldi-ru запускает модель (сейчас не используется)
frequency: 8000 # частота, на которой будет запущен asr
pcm:
save_pcm: true # сохранять ли приходящие pcm
pcm_dir: /var/lib/ecss/ecss-clerk/pcm_files # куда сохранять pcm, если нужно
phonebook:
url_xml: http://system.restfs.ecss:9990/ssw?host=book&user_agent=cisco_search&translit=false&skip_no_disp=true # url для импорта xml книги
log:
level: debug # уровень лога
console_level: info # уровень консольного лога (видно через journalctl)
path: /var/log/ecss/ecss-clerk # куда писать лог
database:
database: ecss_clerk_db_repo # название бд в mysql
username: ecss-clerk
password: password
hostname: localhost
aliases_dir: /var/lib/ecss/ecss-clerk/default_aliases # директория, в которой хранятся файлы с дефолтными сокращениями (для миграции)
Править конфигурацию руками не рекомендуется, лучше делать это через dpkg-reconfigure
sudo dpkg-reconfigure ecss-clerk
Проверка работоспособности
Для проверки работоспособности можно обратиться к сервису через curl:
curl <host>:9010/test.pcm -T test.pcm
Где
- host — адрес хоста, на котором запущен ecss-clerk;
- test.pcm — частота из config.yaml, кодек pcm_s16le, 1 канал (можно с wav заголовком).
Настройка на стороне ECSS-10
Интеграция с ECSS-10 выполнена через IVR-скрипт.
После звонка на ivr, голос от абонента идет на asr_service. Если до таймаута приходит ответ с номером, то происходит звонок на этого абонента. В противном случае мы оказываемся в другой ветке, из которой делается повторный запрос (блок goto, который выполняется 2 раза). Причем, если промежуточный ответ от автосекретаря содержал не пустое поле с распознанной строкой, то она будет произнесена. Кроме этого есть два RPС блока, необходимые для вызова коллбеков на стороне ecss-clerk. Они тегируют запросы как удачные и не удачные. Соответственно, если было падение с таймаутом, то будет вызван негативный коллбек (negative_url). Если в ответе был номер, то будет вызван удачный коллбек (positive_url).
{"actions":{"begin_1":{"name":"begin","params":{"description":""},"pos":{"x":2,"y":0},"cases":{"next":"ivr_2"},"links":{"next":{"points":[{"cx":450,"cy":70},{"cx":450,"cy":125},{"cx":450,"cy":180}],"text_pos":0.2}}},"ivr_2":{"name":"ivr","params":{"description":"","play":[{"type":"speech","name":"Произнесите имя и фамилию абонента.","tts":{"folder":"b1giqb9no4enfheittff","is_use_folder":false,"key":"AgAAAAAJ1M1DAATuwaBaOCTmh006hgeOvVDkmtg","is_use_key":false,"lang":"ru-RU","is_use_lang":false,"voice":"zahar","is_use_voice":false,"speed":"1.2","is_use_speed":false,"emotion":"good","is_use_emotion":false},"location":"","group":"","variable_type":""}],"ivr":[],"asr_service":"localhost:9010","playback_interruption":true,"volume_interruption":25,"asr_delay":0,"wait_time":8,"extension_dialing":true,"direct_call_to_extension":true,"max_digits":"3","interdigit_timeout":2,"use_calling_iface":false},"pos":{"x":2,"y":1},"cases":{"Extension":"condition_4","No Match":"condition_22"},"links":{"Extension":{"points":[{"cx":450,"cy":220},{"cx":450,"cy":275},{"cx":450,"cy":330}],"text_pos":0.2},"No Match":{"points":[{"cx":450,"cy":220},{"cx":540,"cy":350},{"cx":630,"cy":480}],"text_pos":0.2}}},"condition_4":{"name":"condition","params":{"description":"","conditions":[{"code":"0","condition":"(%ASR_RECOGNIZED%<>\"\")and(%ASR_NUMBER%<>\"\")"}]},"pos":{"x":2,"y":2},"cases":{"0":"condition_23","False":"condition_22"},"links":{"0":{"points":[{"cx":450,"cy":370},{"cx":360,"cy":425},{"cx":270,"cy":480}],"text_pos":0.2},"False":{"points":[{"cx":450,"cy":370},{"cx":540,"cy":425},{"cx":630,"cy":480}],"text_pos":0.2}}},"set_10":{"name":"set","params":{"description":"","variables":[{"key":"EXTENSION","value":"%ASR_NUMBER%"}]},"pos":{"x":1,"y":7},"cases":{"next":"dial_11"},"links":{"next":{"points":[{"cx":270,"cy":1120},{"cx":270,"cy":1175},{"cx":270,"cy":1230}],"text_pos":0.2}}},"dial_11":{"name":"dial","params":{"description":"","numbers":"%EXTENSION%","noanswer_timeout":55,"use_calling_iface":false},"pos":{"x":1,"y":8},"cases":{"Busy/No answer":"undefined","Error":"undefined"},"links":{"Busy/No answer":{"points":[{"cx":270,"cy":1270},{"cx":270,"cy":1325},{"cx":270,"cy":1380}],"text_pos":0.2,"pos":{"x":1,"y":9}},"Error":{"points":[{"cx":270,"cy":1270},{"cx":180,"cy":1325},{"cx":90,"cy":1380}],"text_pos":0.2,"pos":{"x":0,"y":9}}}},"goto_17":{"name":"goto","params":{"description":"","max_hops":"2","goto":"ivr_2"},"pos":{"x":4,"y":9},"cases":{"Exit":"play_18","Goto":"ivr_2"},"links":{"Exit":{"points":[{"cx":810,"cy":1420},{"cx":810,"cy":1475},{"cx":810,"cy":1530}],"text_pos":0.2},"Goto":{"points":[{"cx":810,"cy":1420},{"cx":810,"cy":800},{"cx":450,"cy":180}],"text_pos":0.2}}},"play_18":{"name":"play","params":{"description":"","play":[{"type":"speech","name":"Исчерпан лимит попыток. До свид+ания-.","tts":{"folder":"b1giqb9no4enfheittff","is_use_folder":false,"key":"AgAAAAAJ1M1DAATuwaBaOCTmh006hgeOvVDkmtg","is_use_key":false,"lang":"ru-RU","is_use_lang":false,"voice":"zahar","is_use_voice":false,"speed":"1.2","is_use_speed":false,"emotion":"neutral","is_use_emotion":true},"location":"","group":"","variable_type":""}],"replay":"1"},"pos":{"x":4,"y":10},"cases":{"next":"undefined"},"links":{"next":{"points":[{"cx":810,"cy":1570},{"cx":990,"cy":1625},{"cx":1170,"cy":1680}],"text_pos":0.2,"pos":{"x":6,"y":11}}}},"play_19":{"name":"play","params":{"description":"","play":[{"type":"speech","name":"Соединяю.","tts":{"folder":"b1giqb9no4enfheittff","is_use_folder":false,"key":"AgAAAAAJ1M1DAATuwaBaOCTmh006hgeOvVDkmtg","is_use_key":false,"lang":"ru-RU","is_use_lang":false,"voice":"zahar","is_use_voice":false,"speed":"1.2","is_use_speed":false,"emotion":"good","is_use_emotion":false},"location":"","group":"","variable_type":""}],"replay":"1"},"pos":{"x":1,"y":6},"cases":{"next":"set_10"},"links":{"next":{"points":[{"cx":270,"cy":970},{"cx":270,"cy":1025},{"cx":270,"cy":1080}],"text_pos":0.2}}},"rpc_20":{"name":"rpc","params":{"description":"","type":"HTTP","url":"http://%ASR_SERVICE%/%ASR_NEGATIVE_URL%","method":"HEAD","request_timeout":0,"headers":[],"max_bytes":"10000","expected_encoding":"utf","rpc_comm":[]},"pos":{"x":2,"y":4},"cases":{"Error":"set_24"},"links":{"Error":{"points":[{"cx":450,"cy":670},{"cx":450,"cy":725},{"cx":450,"cy":780}],"text_pos":0.2}}},"rpc_21":{"name":"rpc","params":{"description":"","type":"HTTP","url":"http://%ASR_SERVICE%/%ASR_POSITIVE_URL%","method":"HEAD","request_timeout":0,"headers":[],"max_bytes":"10000","expected_encoding":"utf","rpc_comm":[]},"pos":{"x":0,"y":4},"cases":{"Error":"set_25"},"links":{"Error":{"points":[{"cx":90,"cy":670},{"cx":90,"cy":725},{"cx":90,"cy":780}],"text_pos":0.2}}},"condition_22":{"name":"condition","params":{"description":"","conditions":[{"code":"0","condition":"(%ASR_NEGATIVE_URL%<>\"\")"}]},"pos":{"x":3,"y":3},"cases":{"0":"rpc_20","False":"condition_27"},"links":{"0":{"points":[{"cx":630,"cy":520},{"cx":540,"cy":575},{"cx":450,"cy":630}],"text_pos":0.2},"False":{"points":[{"cx":630,"cy":520},{"cx":630,"cy":725},{"cx":630,"cy":930}],"text_pos":0.2}}},"condition_23":{"name":"condition","params":{"description":"","conditions":[{"code":"0","condition":"(%ASR_POSITIVE_URL%<>\"\")"}]},"pos":{"x":1,"y":3},"cases":{"0":"rpc_21","False":"play_19"},"links":{"0":{"points":[{"cx":270,"cy":520},{"cx":180,"cy":575},{"cx":90,"cy":630}],"text_pos":0.2},"False":{"points":[{"cx":270,"cy":520},{"cx":270,"cy":725},{"cx":270,"cy":930}],"text_pos":0.2}}},"set_24":{"name":"set","params":{"description":"","variables":[{"key":"ASR_NEGATIVE_URL","value":""}]},"pos":{"x":2,"y":5},"cases":{"next":"condition_27"},"links":{"next":{"points":[{"cx":450,"cy":820},{"cx":540,"cy":875},{"cx":630,"cy":930}],"text_pos":0.2}}},"set_25":{"name":"set","params":{"description":"","variables":[{"key":"ASR_POSITIVE_URL","value":""}]},"pos":{"x":0,"y":5},"cases":{"next":"play_19"},"links":{"next":{"points":[{"cx":90,"cy":820},{"cx":180,"cy":875},{"cx":270,"cy":930}],"text_pos":0.2}}},"condition_27":{"name":"condition","params":{"description":"","conditions":[{"code":"0","condition":"(%ASR_RECOGNIZED%<>\"\")and(%ASR_NUMBER%=\"\")"}]},"pos":{"x":3,"y":6},"cases":{"0":"play_28","False":"play_32"},"links":{"0":{"points":[{"cx":630,"cy":970},{"cx":630,"cy":1025},{"cx":630,"cy":1080}],"text_pos":0.2},"False":{"points":[{"cx":630,"cy":970},{"cx":720,"cy":1025},{"cx":810,"cy":1080}],"text_pos":0.2}}},"play_28":{"name":"play","params":{"description":"","play":[{"type":"speech","name":"Не удалось найти абонента %ASR_RECOGNIZED%","tts":{"folder":"b1giqb9no4enfheittff","is_use_folder":false,"key":"AgAAAAAJ1M1DAATuwaBaOCTmh006hgeOvVDkmtg","is_use_key":false,"lang":"ru-RU","is_use_lang":false,"voice":"zahar","is_use_voice":false,"speed":"1.1","is_use_speed":true,"emotion":"good","is_use_emotion":false},"location":"","group":"","variable_type":""}],"replay":"1"},"pos":{"x":3,"y":7},"cases":{"next":"set_29"},"links":{"next":{"points":[{"cx":630,"cy":1120},{"cx":720,"cy":1175},{"cx":810,"cy":1230}],"text_pos":0.2}}},"set_29":{"name":"set","params":{"description":"","variables":[{"key":"ASR_RECOGNIZED","value":""},{"key":"ASR_NUMBER","value":""}]},"pos":{"x":4,"y":8},"cases":{"next":"goto_17"},"links":{"next":{"points":[{"cx":810,"cy":1270},{"cx":810,"cy":1325},{"cx":810,"cy":1380}],"text_pos":0.2}}},"play_32":{"name":"play","params":{"description":"","play":[{"type":"speech","name":"Не удалось распознать абонента.","tts":{"folder":"b1giqb9no4enfheittff","is_use_folder":false,"key":"AgAAAAAJ1M1DAATuwaBaOCTmh006hgeOvVDkmtg","is_use_key":false,"lang":"ru-RU","is_use_lang":false,"voice":"zahar","is_use_voice":false,"speed":"1.2","is_use_speed":false,"emotion":"good","is_use_emotion":false},"location":"","group":"","variable_type":""}],"replay":"1"},"pos":{"x":4,"y":7},"cases":{"next":"set_29"},"links":{"next":{"points":[{"cx":810,"cy":1120},{"cx":810,"cy":1175},{"cx":810,"cy":1230}],"text_pos":0.2}}}},"name":"simple_auto_attendant5","description":"","version":"3.14.6.67","settings":{"speech":{"folder":"b1giqb9no4enfheittff","key":"AgAAAAAJ1M1DAATuwaBaOCTmh006hgeOvVDkmtg","lang":"ru-RU","voice":"zahar","speed":"1.2","emotion":"good","terminate_if_tts_failed":false}},"id":"163xd8a7ecfd21v5"}
После установки ecss-clerk скрипт можно найти в папке /etc/ecss/ecss-clerk/simple_auto_attendant5.json
Импортировать скрипт можно через интерфейс командной строки:
domain/<DOMAIN>/ivr/script/.import --id simple_auto_attendant5 --json <IVR-скрипт>
Пример
admin@[sip1@ecss1#ECSS 010145]:/$ domain/arko/ivr/script/.import --id simple_auto_attendant5 --json {"actions":{"begin_1":{"name":"begin","params":{"description":""},"pos":{"x":2,"y":0},"cases":{"next":"ivr_2"} / ... / "emotion":"good","terminate_if_tts_failed":false}},"id":"163xd8a7ecfd21v5"} Script successfully imported with id <<"simple_auto_attendant5">>
После импорта нужно зайти IVR редактор, выбрать скрипт "simple_auto_attendant5" и в поле "адрес сервиса распознавания речи" блока "ivr" указать ip-адрес и порт сервера с установленным ecss-clerk.
Затем необходимо настроить нового абонента, включить на нём услугу «personal ivr» и выбрать скрипт, проимпортированный ранее.
Для добавления собственных слов в базу нужно воспользоваться HTTP API сервиса ecss-clerk. Более подробно об этом написано в данном разделе.