Эта статья является третьей из серии статей (предыдущие части №1 и №2) о том, как мы настраиваем чат-бота - NinjaChat - для обслуживания наших отправителей и грузополучателей здесь, в Ниндзя Ван. Здесь я расскажу, как использовались резервные варианты, чтобы гарантировать, что ввод от наших пользователей-ботов всегда разумно управлялся (даже если указанный ввод не имел смысла в контексте разговора). Но что не менее важно, я также расскажу о том, как мы реализовали локализацию для нашего бота, который на данный момент доступен на четырех языках - английском, индонезийском, тайском и вьетнамском.

Использование резервных копий для восстановления после ошибок

Было бы глупо предусматривать только счастливые пути в дереве разговоров, которое касается работы чат-бота с произвольным текстом, но почти невозможно обеспечить намерения для обработки всего диапазона ввода, который пользователи предположительно могут предоставить. К счастью для нас, Dialogflow предоставляет резервные намерения, которые, как и обычные намерения, сопоставляются с набором входных контекстов и активируют набор выходных контекстов. Но они делают это, когда не удается найти другого совпадения по намерениям, учитывая действия пользователя и обучение, предусмотренное для каждого намерения.

Фактически, когда вы создаете своего самого первого агента (сущность, которая обрабатывает ваш диалог с вашими пользователями и хранит все ваши намерения), Dialogflow автоматически предоставляет Default Fallback Intent без контекстов ввода и вывода, что означает, что это буквально все для все сценарии, когда пользователь вводит что-то, что не соответствует ни одному из ваших намерений. Тем не менее, полагаясь исключительно на резервный вариант по умолчанию, вы получаете некачественный чат-бот по той простой причине, что он не может контролировать, какие контексты, зависящие от потока, деактивировать / повторно активировать при совпадении.

Это означает, что, если бы пользователь находился в процессе ввода выбранного адреса получения и, если резервный вариант по умолчанию неоднократно совпадал, срок службы предыдущих контекстов получения в конечном итоге истек. Это оставляет пользователя в некотором роде неопределенности, где он / она находится в разговоре, не имея ни контекстов главного меню, ни контекстов, специфичных для потока (например, получения). Любое высказывание пользователя на этом этапе не будет соответствовать тому, что пользователь ожидает от разговора, и может даже вызвать неожиданные результаты.

Для нашей реализации чат-бота мы намеренно создали резервные варианты для всех критических состояний / путей в дереве разговоров. В результате, независимо от того, где пользователь может быть в разговоре, произнесение мусора вернет пользователя туда, где он был раньше. Рассмотрим это взаимодействие:

// Намерение: Shipper — Create Pickup выполнено
Чат-бот: Какую дату получения вы хотите? ‹Показывает список дат как опции›

Пользователь: оставьте меня в покое.

// Намерение: Shipper — Select Pickup Date (Fallback) выполнено
Чат-бот: извините, я не заметил этого. ‹Показывает список дат как опции›

Здесь мы настроили резервный вариант, который имеет те же контексты ввода, что и намерение, которое предположительно должно быть сопоставлено, если пользователь ввел действительную дату (например, скажем Shipper — Select Pickup Date). Преимущества того, что этот конкретный резервный вариант теперь соответствует вводу мусора:

  • Мы можем повторно активировать конкретное резервное намерение, поскольку выходные контексты совпадают с контекстами, которые он принимал в качестве входных, что означает, что он фактически вернет разговор обратно в состояние, в котором он был до того, как пользователь ввел мусор. Если намерение Shipper — Create Pickup имело shipper-sns и shipper-create-pickup в качестве выходных контекстов, резервное намерение примет эти намерения как входные и установит их также как выходные контексты.
  • Мы можем настроить действие, которое соответствует тому, что пользователь видел в предыдущем взаимодействии, в данном случае что-то вроде DISPLAY_PICKUP_DATES, так что отклик на откат («Извините, я этого не заметил.» ) персонализирован и имеет ожидаемый правильный ответ от пользователя, например дата из списка дат.

Но что, если мы хотим вернуть пользователя обратно в его предыдущее состояние, несмотря на наличие действительного совпадения намерений в Dialogflow? Это был сценарий, с которым мы часто сталкивались.

Давайте использовать тот же пример, что и выше. Хотя мы настроили Shipper — Select Pickup Date для принятия списка дат и ответа на него, на самом деле мы хотим ограничить дату, которую пользователь в конечном итоге выберет, той, которая была представлена ​​в качестве варианта в предыдущем взаимодействии (это даты, которые были проверены по настройки отправителя хранятся в нашей базе данных). Нет простого способа создать намерение, которое соответствовало бы только определенным датам, которые различаются для каждого отправителя, поэтому нам нужен способ эффективно отображать ошибку и заставлять пользователя выбирать другую дату. Вот тут-то и проявляются события.

// Намерение: Shipper — Create Pickup выполнено
Чат-бот: Какую дату получения вы хотите? ‹Показывает список дат как опции›

Пользователь: 29 марта 2020 г., пожалуйста.

// Намерение: Shipper — Create Pickup (Event — Invalid Date) выполнено
Чат-бот: К сожалению, выбранная вами дата недействительна. Пожалуйста, попробуйте еще раз. ‹Показывает список дат как опции›

В дополнение к настроенным резервным намерениям у нас также есть обычные намерения, которые сопоставляются событиями с целью сброса состояния на этих стыках, где выполняется проверка соответствия пост-намерения.

Здесь у нас будет что-то вроде Shipper — Create Pickup (Event — Invalid Date), настроенного с TRIGGER_VALIDATION_FALLBACK_EVENT в качестве его события. Мы вызываем тот же sessionsClient.detectIntent() в клиенте API, но вместо этого передаем его в значении события. Единственное отличие состоит в том, что, в отличие от обычного резервного намерения, это намерение проверки имеет те же входные контексты, что и ожидаемое согласованное намерение Shipper — Select Pickup Date, и оно имеет контексты вывода, аналогичные контексту предыдущего состояния Shipper — Create Pickup, то есть shipper-sns и shipper-create-pickup. О, и, конечно же, тоже самое действие DISPLAY_PICKUP_DATES.

Использование ответов для локализации вывода

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

Намерения будут настроены с одинаковыми ответами для всех поддерживаемых языков, где сам ответ является ключом сообщения, который сопоставлен с локализованным значением в нашем messages.<language code> файле, например. messages.en-SG, messages.vi-VN и т. Д.

//Field from intent json config file
  "messages": [
    {
      "type": 0,
      "lang": "en",
      "condition": "",
      "speech": "df_select_order_enter_tracking_id"
    },
    {
      "type": 0,
      "lang": "id",
      "condition": "",
      "speech": "df_select_order_enter_tracking_id"
    },
    {
      "type": 0,
      "lang": "th",
      "condition": "",
      "speech": "df_select_order_enter_tracking_id"
    },
    {
      "type": 0,
      "lang": "vi",
      "condition": "",
      "speech": "df_select_order_enter_tracking_id"
    }
  ],

При построении проекта запускается сценарий, который вставляет все новые пары значений ключей сообщения, найденные в центральном файле messages.en (который мы используем как источник истины), в Lokalise, а также обновляет изменения в существующих парах. Переводчики, работающие над Lokalise, регулярно обновляют там переводы для других языков, которые загружаются и добавляются в проект во время сборки. Во время выполнения все ключи сообщений переводятся в их локализованные значения путем обращения к этим файлам сообщений. И на этом наши проблемы с переводом были (в основном) решены.

//Sample entries from messages.en
df_default_introduction=Thank you for using Ninja Van! How can I help you today?
df_other_options_prompt=How else can I help you?
df_inquire_prompt=What would you like to inquire?
df_customer_other_issues_no_matched_orders=It looks like you have no (matched) orders in our system. Would you like to speak to a live agent anyway?
df_live_agent_connect=Our live agent is connecting with you now. Please hold on for a moment!

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

Использование событий для принудительного сопоставления намерений

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

Проще говоря, вот как это работало ранее. Для каждого настроенного нами намерения, если есть параметр меню, который был показан пользователю, который должен был соответствовать ему, мы отправили его локализованный текст в виде строки запроса в Dialogflow.

Теперь вместо этого мы настроили событие для этого намерения, которое соответствует ключу сообщения показанной опции. Затем мы убедились, что веб-перехватчик, который доходит до нас при нажатии опции, также содержит идентификатор ключа сообщения, который мы передаем непосредственно Dialogflow, инициируя событие. Это событие, конечно, тогда однозначно и очень точно совпадет с первоначальным намерением, которое мы настроили.

Какая от этого польза? Во-первых, теперь мы можем гарантировать совпадение намерений для нашей настройки агента, пока пользователь нажимает кнопку во время навигации нашего чат-бота, а не вводит произвольный текст (и, согласно нашим исследованиям, вместо этого они часто предпочитают нажимать кнопки). Другие дополнительные преимущества:

  • Ускорение разработки при работе над функцией.
  • QA не будет заблокирован переводами во время тестирования нашего приложения.
  • Больше не нужно беспокоиться о переводах (кого я обманываю, меньше).

Использование приоритетов для управления соответствием намерений

Наконец, приоритеты позволяют нам предпочтительно сопоставлять определенные намерения, когда группа намерений более или менее одинаково действительна и использует одни и те же входные контексты. Значение от 0 до 1 000 000 может быть присвоено полю priority намерения, где более высокое значение означает более высокий приоритет. (Для них также можно установить предварительно заданные значения на консоли, как показано выше.)

Некоторые из способов, которыми мы использовали эту функцию, включают:

  • Отмена приоритета начального Start намерения ниже всех других намерений, чтобы оно всегда соответствовало существующему пользовательскому намерению.
  • Приоритезация намерений, которые обучены распознавать ответные фразы (например, «Вернуться в главное меню»). Это полезно в потоках, где следующее сопоставляемое намерение принимает @any ввод от пользователя, такой как штрих-код заказа, с помощью которого нужно искать детали. Чтобы обратная фраза не совпадала со штрих-кодом, мы вместо этого убеждаемся, что возвращаемое намерение имеет более высокий приоритет.
  • Отключение определенного потока (например, потока для клиентов, чтобы перепланировать свои поставки) путем установки приоритета начального начального намерения (Customer — Reschedule Delivery), которое приводит ко всем другим намерениям на этом пути, как -1. В будущем, когда этот поток / функция станет доступной, нам нужно будет только сбросить этот приоритет на положительное значение, чтобы включить его, поскольку внутренний код обработки этих действий уже существует в SNS.

Эта предпоследняя часть завершает большую часть деталей реализации, а последняя статья расскажет, как мы тестировали нашего бота и что ждет NinjaChat в будущем.