Описание сервиса

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

Абонент снимает трубку и набирает номер, на который завязан 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. По ней формируется структура вида ключ-значение, где ключ — имя, значение — телефон.
Кроме этого, формируется ещё одна подобная структура, содержащая уникальные имена и фамилии из книги, где ключ — уникальное имя, значение — полное имя.

По этим двум мапам — телефонной книге и уникальным именам и производится поиск.

Алгоритм поиска

  1. Берем строку из списка;
  2. Проверяем, содержится ли данная строка в таблице hard_aliases;
  3. Раскрываем сокращения (алиасы из таблицы aliases) и добавляем их в список, по которому будет производиться рекурсивный поиск;
  4. По каждой строке в сформированном списке делаем поиск:
    1. Формируем список с перестановками для строки:
      1. Проверяем, содержится ли строка (именно целиком, а не как подстрока) в уникальных именах. Если да — заменяем эту строку на полное имя;
      2. Ищем строку в телефонной книге. Если номер найден — поиск закончен.
  5. Если в списке из пункта 3 ещё остались строки, то берем следующую.

Пример

Имеем ответ от ASR:

["женя казарцев", "женя казарцев", "женя"]

Двигаемся согласно алгоритму:

  1. Берем строку "женя казарцев" из списка;
  2. Проверяем, содержится ли данная строка в таблице hard_aliases. В нашем примере её нет, так что передаем строку дальше как "женя казарцев";
  3. Раскрываем сокращения ["женя казарцев", "божена казарцев", "евгений казарцев", "евгения казарцев"];
  4. По каждой строке в сформированном списке делаем поиск строки;

    Строки "женя казарцев" и "божена казарцев" пропущены, т.к. пройдя всю цепочку ниже номера для них не были найдены.

    Выбираем строку "евгений казарцев":

    1. Формируем список с перестановками для строки ["евгений казарцев", "казарцев евгений"]
      1. Проверяем, содержится ли строка (именно целиком, а не как подстрока) в уникальных именах. Если да — заменяем эту строку на полное имя. Если не содержится, передаем как есть — "евгений казарцев";
      2. Ищем строку в телефонной книге. Если не найден — берем следующий элемент из списка с перестановками евгений казарцев -> 4819;
  5. Если в списке из пункта 3 ещё остались строки, то берем следующую. Здесь мы пропустили строки "женя казарцев" и "божена казарцев".

Установка      

Установка выполняется из репозитория:

deb [arch=amd64] http://archive.eltex.org/ssw/bionic/3.14 stable main extras external

Необходимо установить пакет ecss-clerk.

ecss10@ecss1:~$ 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 # директория, в которой хранятся файлы с дефолтными сокращениями (для миграции)
CODE

Править конфигурацию руками не рекомендуется, лучше делать это через 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).

 IVR-скрипт
{"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"}
CODE

После установки 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. Более подробно об этом написано в данном разделе.