Создание IVR на базе Астериск, с распознаванием DTMF и имён сотрудников (на базе Voicer от уважаемого antirek) и подключение к существующей АТС.

Наступающий новый год принёс в компанию, где я работаю, (не)радостную весть — умер древний сервер с системой IVR и PCI-платой на 4 аналоговых порта от Dialogic. Внезапно выяснилось, что современные системы не имеют PCI слотов, старые — не дружат с новыми версиями ОС и неизвестно сколько проживут, а лицензия имеется лишь на конкретную старую версию программы.
Так родилась идея поднять IVR без платы (и бесплатно) на виртуальной машине с Asterisk.

Итого, нам потребуются:

  • Существующая АТС с абонентами
  • Виртуальная машина (в примере — ubuntu 18.04 lts).
  • Регистрация на каком-либо из сервисов распознавания голоса: wit.ai, google или яндекс
  • Минимальное знание линукс систем
  • Желание разобраться с Астериск

Если все пункты успешно выполнены — можно приступать. Первым делом стоит поставить сам asterisk, голосовые подсказки для теста, а также nodejs и npm.

apt install nodejs asterisk npm asterisk-core-sounds-ru-gsm

Далее нам потребуется сам сервис распознавания voicer и process manager для его запуска:

npm install voicer -g
npm install pm2 -g

Создадим папки и конфигурацию для voicer:

mkdir -p /etc/voicer/data

И запишем конфигурацию в файл /etc/voicer/config.js, добавив туда свои логин/пароль и ключ (developer_key) к желаемому сервису. В моём случае был выбран wit.ai — бесплатен, достаточно точен для наших задач.

module.exports = {
    agi: {
        port: 3000
    },
    web: {
        port: 3100,
        auth: true,
        username: 'ИМЯ_ПОЛЬЗОВАТЕЛЯ',
        password: 'ПАРОЛЬ_ПОЛЬЗОВАТЕЛЯ',
        realm: 'НАЗВАНИЕ_КОМПАНИИ'
    },
    processing: {
        totalAttempts: 2,
        playGreeting: true,
        playBeepBeforeRecording: false   //use system beep
    },
    asterisk: {
        sounds: {
            onErrorBeforeFinish: 'invalid',
            onErrorBeforeRepeat: 'invalid',
            greeting: 'beep'
        },
        recognitionDialplanVars: {
            status: 'RECOGNITION_RESULT',
            target: 'RECOGNITION_TARGET'
        }
    },
    record: {
            directory: '/tmp',
            type: 'wav',
            duration: 3,
    },
    recognize: {
            directory: '/tmp',
        type: 'witai',    // ['yandex', 'google', 'witai']
        options: {
            developer_key: 'XXXXXXXXXXXXXXXXXXX'
        }
    },
    lookup: {
        type: 'file',
        options: {
            dataFile: '/etc/voicer/data/peernames.json'
        }
    },
    logger: {
        console: {
            colorize: true
        },
        file: {
            filename: '/var/log/voicer.log',
            json: false
        }
    }
};

Далее нам понадобится создать сервис для запуска voicer. Это будет файл /etc/init.d/voicer:

#!/bin/sh

### BEGIN INIT INFO
# Provides:          voicer
# Required-Start:    $network $syslog $named
# Required-Stop:     $network $syslog $named
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start/stop voicer
### END INIT INFO

set -e

if [ -z "$1" ] ; then
    echo "Usage: $0 [start|stop|restart]"
    exit 0
fi

if [ "$1" = "start" ] ; then
     VOICER_CONFIGFILE=/etc/voicer/config pm2 start voicer
elif [ "$1" = "stop" ] ; then
    pm2 stop voicer
elif [ "$1" = "restart" ] ; then
    $0 stop
    sleep 5
    $0 start
fi

Ну и активировать его:

systemctl daemon-reload
systemctl enable voicer
systemctl start voicer

При этом надо учесть, что voicer будет складывать записанные файлы в папку /tmp и было бы её неплохо периодически чистить. Создаём простой скрипт для этого и складываем его в /etc/cron.daily

#!/bin/sh

rm /tmp/*.wav

Ну а теперь осталась более сложная часть — настроить сам asterisk. Поскольку АТС у нас уже есть и все клиенты используют её, то большая часть конфигурации нам не нужна. Удаляем (или сохраняем в тёмное прохладное сухое место — по желанию) из папки /etc/asterisk всё, кроме asterisk.conf, modules.conf и sip.conf. И дописываем конфигурацию sip.conf следующими строками, добавив нужные нам значения для:

  • fromdomain — адрес сервера с астериск
  • host — адрес АТС
  • fromuser:secret и defaultuser:remotesecret — пары логин: пароль, которыми обменяются asterisk и АТС


[main_link]
fromdomain=xxx.xxx.xxx.xxx
host=xxx.xxx.xxx.yyy
insecure=port
port=5060
realm=asterisk
sendrpid=pai
fromuser=111
secret=111
defaultuser=222
remotesecret=222
qualify=yes
type=friend
disallow=all
allow=alaw
context=main_link
promiscredir=yes
dtmfmode=auto

Тут стоит обратить внимание на то, в каком режиме работают dtmf команды в вашей АТС. Учтите, что они могут обрабатываться разными методами для внешних вызовов АТС (например — из города, или с мобильного телефона) и внутренних звонков между абонентами вашей АТС. В моём случае, в первом варианте это были inband dtmf сигналы, во втором — rfc2833. Но auto с этим справился. Также вас могут ожидать интересные особенности взаимодействия именно с вашей АТС — придётся на личном опыте выяснить, что требуется, например, для сохранения АОН вызывающего (внешнего) абонента при возврате звонка с IVR на АТС (строка sendrpid).

И остался ещё один конфигурационный файл. Самый важный. Dialplan, он же extensions.conf. В нём вам обязательно нужно указать ваш внешний номер (на который будут звонить люди), номер секретаря (на который уйдут звонки по умолчанию) и пути до ваших голосовых подсказок.

[general]
static=yes
writeprotect=no

[globals]
[main_link]
exten => ВАШ_ВНЕШНИЙ_НОМЕР,1,Goto(ivr_tree,s,1)

[ivr_tree]

;allow direct dialing to internal users
exten => _40XX,1,Background(custom/common/SoedinyauVas)
;check user voice existance
same => n,Set(exists=${STAT(e,${ASTDATADIR}/sounds/custom/${EXTEN}.vox)})
same => n,Playback(custom/${IF($[ ${exists} = 1 ] ? ${EXTEN} : Sotrudnik)})
same => n,Transfer(SIP/${EXTEN}@АДРЕС_СТАНЦИИ)

;start intro
exten => s,1,Answer()
same => n,Set(CHANNEL(language)=ru)
same => n,Background(custom/common/Welcome)

;start recognition
same => n,AGI(agi://localhost:3000)
same => n,GotoIf($[${RECOGNITION_RESULT}=SUCCESS]?:default)
same => n,Background(custom/common/SoedinyauVas)
;check user voice existance
same => n,Set(exists=${STAT(e,${ASTDATADIR}/sounds/custom/${RECOGNITION_TARGET}.vox)})
;play user name or default name
same => n,Playback(custom/${IF($[ ${exists} = 1 ] ? ${RECOGNITION_TARGET} : Sotrudnik)})
same => n,Transfer(SIP/${RECOGNITION_TARGET}@АДРЕС_СТАНЦИИ)
same => n,Hangup()

;default route
same => n(default),Transfer(SIP/4001@АДРЕС_СТАНЦИИ)

В данном примере используются записи с приветствием, именами сотрудников и фраза «соединяю вас». При этом подсказка для сотрудника ищется по его номеру, а если её нет произносится просто «с сотрудником». Подобные подсказки рекомендую заказать студии — системы синтеза речи, увы, не совершенны и их произношение названия вашей компании и фамилий сотрудников годится лишь для развлечения. 40XX — номера в компании, 4001 — секретарь.

Особенность данной ситуации в том, что у нас звонками занимается именно АТС. Таким образом, мы вместо обычной команды Dial используем команду Transfer — и тогда входящий звонок после IVR полностью уходит с asterisk и освобождает sip каналы АТС (их количество часто жёстко зафиксировано лицензией). В большинстве случаев в команде Transfer советуют использовать название направления (main_link), но для моей АТС подошло только прямое указание адреса станции.

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

Теперь наш астериск умеет получать входящие звонки, распознавать DTMF сигналы, распознавать имена сотрудников и перенаправлять на их внутренние номера. Осталось лишь убедить станцию, что ей нужно отправлять входящие звонки на наш сервер. В зависимости от производителя эти настройки могут быть абсолютно разнообразны, но желаемый путь будет содержать слова ARS и маршрутизация звонков. Но это уже совсем другая история.

Комментарии (6)


  1. arheops
    17.01.2019 22:27

    Несовершенны как раз системы распознавания.
    А вот системы синтезирования, у того же гугла, слабо отличаются от средне-проффесиональных записей уже.
    Недостаток данного подхода — голос распознается с задержкой от 3 секунд.
    Более коректно исспользовать EAGI и google GRPC, распознается без задержки на лету.


    1. DaemonGloom Автор
      17.01.2019 07:19

      Обычно уходит примерно секунда на распознавание. Системы, которые распознают на лету, а не предварительно записывают удобны, но и стоят ощутимо больше при этом. При этом несут в себе всё те же задержки из-за интернета.
      Как раз синтезирование гугла и пробовал. Синтез названия компании и некоторых фамилий вызывает желание посмеяться, но не желание использовать гугл для озвучивания фраз.


      1. arheops
        17.01.2019 09:40

        Секунда + время записи. Это если у пользователя нет шума. Если есть — ваш подход вообще не прекращает запись.
        Google cloud recognition упомянутый вами в статье стоит одинаково в версии streaming и post.


        1. DaemonGloom Автор
          17.01.2019 11:40

          Не совсем так. Пишется указанное количество секунд и отправляется на сервер. В конкретном примере — 3 секунды. Этого хватает, чтобы назвать любого сотрудника.


  1. demonit
    17.01.2019 21:27

    афигеть — это ж сколько это говнище от диалоджика проработало? я с такими еще 12 лет назад работал :)


    1. DaemonGloom Автор
      18.01.2019 07:33

      11 лет примерно. И дальше бы работало, но материнская плата подвела.