Главная » Хабрахабр » Тактичный робот: умеет слушать и не перебивает

Тактичный робот: умеет слушать и не перебивает

Распознавание речи (далее – ASR, Automatic Speech Recognition) используется при создании ботов и/или IVR, а также для автоматизированных опросов. Voximplant использует ASR, предоставляемый «корпорацией добра» – гугловское распознавание работает быстро и с высокой точностью, но… Как всегда, есть один нюанс. Человек может делать паузы даже в коротких предложениях, при этом нам нужна гарантия, что ASR не воспримет паузу как окончание ответа. Если ASR думает, что человек закончил говорить, то после «ответа» сценарий может включить синтез голоса со следующим вопросом – в это же самое время человек продолжит говорить и получит плохой пользовательский опыт: бот/IVR перебивает человека. Сегодня мы расскажем, как с этим бороться, чтобы ваши пользователи не огорчались от общения с железными помощниками.

Концепция

Цель – задать вопрос и выслушать человека, не перебивая и ожидая окончания его ответа. ASR у нас представлен отдельным модулем, где есть событие ASR.Result – оно триггерится, когда человек закончил говорить. Специфика работы ASR от Google в том, что ASR.Result с распознанным текстом вернётся, как только человек сделает хотя бы небольшую паузу и google решит, что сказанная фраза распознана и закончена.

InterimResult. Чтобы дать человеку возможность делать паузы, можно использовать событие ASR. Result. В нём ASR в процессе распознавания возвращает весь «сырой» текст, корректируя и меняя его в зависимости от контекста – и так вплоть до срабатывания ASR. InterimResult является показателем того, что человек в данный момент что-либо говорит. Таким образом, событие ASR. А промежуточные распознанные тексты, полученные из ASR. Мы будем ориентироваться лишь на него и смотреть, как долго оно не приходит. Result – складывать.

В общем виде это будет выглядеть так:

asr.addEventListener(ASREvents.InterimResult, e => { clearTimeout(timer) timer = setTimeout(stop, 3000)
})
asr.addEventListener(ASREvents.Result, e => { answer += " " + e.text
})
function stop(){
//...
}

Раскрываем суть. Таймеры

Для правильной работы с паузами можно создать специальный объект:

timeouts = { silence: null, pause: null, duration: null
}

После заданного вопроса человек зачастую задумывается на несколько секунд. Таймер на тишину в самом начале лучше выставлять 6-8 секунд, ID таймера мы сохраним в параметр timeouts.silence.

Это параметр timeouts.pause. Паузы в середине ответа оптимальны в 3-4 секунды, чтобы человек мог задуматься, но не мучился в ожидании, когда договорил.

Также это оградит нас от случаев, когда человек находится в шумном помещении с фоновыми голосами, которые будут приниматься нами за речь клиента. Общий таймер на весь ответ – timeouts.duration – пригодится, если мы не хотим, чтобы человек разговаривал слишком долго. А также от случаев, когда мы попали на другого робота, который разговаривает с нашим роботом по кругу.

Итого, в начале сценария мы подключаем модуль ASR, объявляем переменные и создаем объект timeouts:

require(Modules.ASR) let call, asr, speech = "" timeouts = { silence: null, pause: null, duration: null
}

Входящий звонок

Когда в сценарий поступает входящий звонок, срабатывает событие AppEvents.CallAlerting. Создадим обработчик для этого события: ответить на звонок, поприветствовать клиента, запустить распознавание после приветствия. А ещё давайте позволим человеку перебить робота с середины задаваемого вопроса (подробности – чуть дальше).

обработчик AppEvents.CallAlerting

VoxEngine.addEventListener(AppEvents.CallAlerting, e => ); call.addEventListener(CallEvents.Disconnected, e => { VoxEngine.terminate() })
})

Видно, что вызываются функции startASR и startSilenceAndDurationTimeouts – разберем, что это и зачем.

Распознавание и таймауты

Распознавание реализовано в функции startASR. Она создает инстанс ASR и направляет голос человека в этот инстанс, также она содержит обработчики для событий ASREvents.InterimResult и ASREvents.Result. Как мы говорили выше, здесь мы трактуем ASR.InterimResult как признак, что человек говорит. Обработчик этого события очищает ранее созданные таймауты, задает новое значение для timeouts.pause и, наконец, останавливает синтезированный голос (вот так человек сможет перебивать бота). Обработчик ASREvents.Result просто конкатенирует все итоговые ответы в переменной speech. Конкретно в этом сценарии speech никак не используется, но при желании ее можно передать на ваш бэкенд, к примеру.

startASR

function startASR() { asr = VoxEngine.createASR({ lang: ASRLanguage.RUSSIAN_RU, interimResults: true }) asr.addEventListener(ASREvents.InterimResult, e => { clearTimeout(timeouts.pause) clearTimeout(timeouts.silence) timeouts.pause = setTimeout(speechAnalysis, 3000) call.stopPlayback() }) asr.addEventListener(ASREvents.Result, e => { // складываем распознаваемые ответы speech += " " + e.text }) // направляем поток в ASR call.sendMediaTo(asr)
}

Функция startSilenceAndDurationTimeouts… Записывает значения соответствующих таймеров:

function startSilenceAndDurationTimeouts() { timeouts.silence = setTimeout(speechAnalysis, 8000) timeouts.duration = setTimeout(speechAnalysis, 30000)
}

И еще немного функций

speechAnalysis останавливает распознавание и анализирует текст из speech (который получен из ASREvents.Result). Если текста нет, то мы повторяем вопрос; если текст есть, то вежливо прощаемся и кладем трубку.

speechAnalysis

function speechAnalysis() { // останавливаем модуль ASR stopASR() const cleanText = speech.trim().toLowerCase() if (!cleanText.length) { // если переменная с нулевой длиной, то это значит что сработал таймер тишины, // т.е. человек вообще ничего не ответил, и мы можем, например, повторить вопрос абоненту handleSilence() } else { call.say( "Большое спасибо за отзыв! До свидания!", Language.RU_RUSSIAN_FEMALE ) call.addEventListener(CallEvents.PlaybackFinished, () => { call.removeEventListener(CallEvents.PlaybackFinished) call.hangup() }) }
}

За повторение вопроса отвечает handleSilence:

function handleSilence() { call.say("Извините, вас не слышно. Расскажите, пожалуйста, как вы оцениваете удобство работы с нашим сервисом?", Language.RU_RUSSIAN_FEMALE) // начнём слушать через 3 секунды и дадим возможность с этого момента перебивать робота setTimeout(startASR, 3000) call.addEventListener(CallEvents.PlaybackFinished, startSilenceAndDurationTimeouts)
}

Наконец, функция-хелпер для останова ASR:

function stopASR() { asr.stop() call.removeEventListener(CallEvents.PlaybackFinished) clearTimeout(timeouts.duration)
}

Все вместе

листинг сценария

Итоговый сценарий показывает, как можно «облагородить» прямолинейного робота, добавив ему немного такта и внимания. Наверняка этот способ – не единственно возможный, поэтому если у вас возникнут мысли, как еще можно элегантно допилить привычное взаимодействие бота и человека – делитесь в комментах. Тем же, кто хочет чего-то более продвинутого и вдруг не читал наш туториал про DialogFlow – рекомендуем ознакомиться.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

[Перевод] Интервью с Дэвидом Гобелем

Дэвид любезно согласился дать LEAF очень интересное интервью. Дэвид Гобель – изобретатель, филантроп, футурист и ярый сторонник технологий омоложения; вместе с Обри де Греем он известен как один из основателей Methuselah Foundation и как автор концепции Longevity Escape Velocity (LEV), ...

10 долларов на хостинг: 20 лет назад и сегодня

Всё кругом дорожает, а технологии дешевеют. Когда-то компьютер или мобильный телефон могли позволить себе единицы, сейчас эти устройства есть в каждой российской семье. Цена мегабайта за последние 20 лет упала в несколько тысяч раз. Ещё один пример — хостинг. В ...