isamarin/alisa

YandexAlisa PHP Bot

1.0.2 2019-11-28 16:09 UTC

README

Предварительная настройка

Морфологический словарь

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

$bot = new Alisa('NAME');                         
//Такой используется по умолчанию
$bot->setDictionaryPath($_SERVER['DOCUMENT_ROOT'] . '/dicts/');

Директория хранения сессий

Сессии – *.json файл в котором хранится вся сервисная информация в рамках сущности одного диалога. Необходимо указать путь до дирректории, в которую будут складироваться данные.

$bot = new Alisa('NAME');                         
//Такой используется по умолчанию
$bot->setDictionaryPath($_SERVER['DOCUMENT_ROOT'] . '/sessions/');

Протокол работы

//Создаем бота. Аргумент – название навыка
$bot = new Alisa('myawesomebot');

//Создаем триггер, в качестве аргумента строка с уникальным именем
$helloTrigger = new Trigger('HELLO');

//Привязываем к триггеру токены – в данном случае в одну группу.
//Токены – ключевые слова, объединенные в группы
$helloTrigger->linkTokens(['привет','здравствуйте','приветсвую']);


$bayTrigger = new Trigger('bay');
$bayTrigger->linkTokens(['пока','до свидания','прощай']);

//Привязываем триггеры боту
$bot->addTrigger($helloTrigger,$bayTrigger);

//Отправляем ответ, если распознан $helloTrigger
$bot->sendResponse($helloTrigger,static function(){
    //$answer - экземпляр отправляемого ответа

    $answer = new Response();
    $answer->addText('Привет!');
    $answer->addText('Доброго времени суток!');

    //обязательно возвращаем объект Response
    return $answer;
);

//Отправляем ответ, если распознан $bayTrigger
$bot->sendResponse($bayTrigger,static function(){
    $answer = new Response();
    $answer->addText('Прошай!');
    $answer->addText('Всего доброго');

    //обязательно возвращаем объект Response
    return $answer;
);

Стандартные триггеры

По протоколу работы бот обязан иметь как минимум триггер для приветствия, обработку ошибки (вызов помощи).

//Будет вызван автоматически, при первом обращении пользователя к навыку
$helloTrigger = new Trigger('HELLO');
$helloTrigger->setAsInit(true);

//Будет вызыван автоматически, если ну удалось распознать запрос
$mistakeTrigger = new Trigger('MISTAKE');
$mistakeTrigger->setAsMistake(true);


//Отправляем ответ, если распознан $mistakeTrigger
$bot->sendResponse($mistakeTrigger,static function(){
    $answer = new Answer();
    $answer->addText('Не удалось понять вашу команду :( ');
    return $answer;
);

В случае, если данные триггеры не определены, бот отошлет ответ по умолчанию со ссылкой на данный пункт. И если это причина, по которой ты читаешь этот текст – шалость удалась.

Триггеры

Триггер

Триггер – команда, на которую должен отреагрировать бот.

Токены

Токены – ключевые слова, или варианты запроса (зависит от выбранного типа распознования)

MORPHY_STRICT

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

$greenTea->linkTokens(['дай','хочу','налей'],['чай'],['зеленый']);
$blackTea->linkTokens(['дай','хочу','налей'],['чай'],['черный','индийский']);
$coffeTrigger->linkTokens(['дай','хочу','налей'],['кофе']);

При запросе "Налейка, пожалуйста черного чая" сработает $blackTea; При запросе "Сил нет как хочу бодрящего такого кофе" – $coffeTrigger;

Плюсы:

  • Хорошо различает очень похожие запросы
  • При первом свопадении всех групп токенов, обработка следующих триггеров прекращается
  • Очередностью добавления триггеров можно определить те триггеры, которые будут обрабатываться первым. См пункт выше.
  • Может вызывать триггер с ошибкой, если команда не была распознана. В данном примере, попросив налить пива, бот скажет что такими полномочиями не обладает.

Минусы:

  • Необходимо строго определять важность того или иного ключевого слова. В случае с $greenTea и $blackTea, с аналогичными двумя первыми группами, решающим фактором сыграет третья гурппа токенов – черный чай или все таки чай зеленый.
  • И колличество триггеров, и колличеество групп токенов сказывается на скорости работы.

Дамерау-Левенштейн

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

$greenTea->linkTokens(['Налей зеленого чая'],['Хочу зеленого чая'],['Дай зеленый чай']);
$blackTeas->linkTokens(['Налей чергого чая'],['Хочу черного чая'],['Дай черный чай']);
$coffeTrigger->linkTokens(['Налей кофе'],['Хочу кофе'],['Дай кофе']);

Плюсы:

  • Не нужно определять группы ключевых слов. Достаточно прописать только несколько возможных вариантов, которые может запросить пользователь
  • Как правило, запрос обрабатывается быстрее, чем аналогичная вариация MORPHY_STRICT

Минусы:

  • Обрабатываются все триггеры, для которых прописаны токены. Даже если правильный триггер был выбран первым, остальные триггеры все равно пройдут проверку. Результирующий триггер – триггер с наибольшим совпадением. Соответвено чем больше триггеров, тем медленее работает.
  • В отличие от MORPHY_STRICT не может вызывать сообщение с ошибкой распознования. Данный режим всегда выдает какой-либо триггер. В данном примере, попросив налить пива, по какой-то причине бот наливает кофе.

Делегирование

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

// Декларируем триггеры
$nameTrigger = new Trigger('NAME');
$sNameTrigger = new Trigger('SECOND_NAME');
$yoTrigger = new Trigger('YEARS');
$personTrigger = new Trigger('PERSON');

// Назначаем токены для триггера 
$nameTrigger->setTokens(['давай','хочу','может'],['знакомиться','познакомиться','представлюсь']);

// Привязываем следующие триггеры
$nameTrigger->nextDelegate($sNameTrigger);
$sNameTrigger->nextDelegate($yoTrigger);
$yoTrigger->nextDelegate($personTrigger);

// Обрабочтик запроса. Сработает если пользователь произнес "Давай познакомимся"
$bot->sendResponse($nameTrigger,static function() use ($bot){
    $answer = new Response();
    $answer->addText('Какое твое имя?');
    return $answer;
});

// После шага $nameTrigger сработает обработчик $sNameTrigger
$bot->sendResponse($sNameTrigger,static function() use ($bot){
    $answer = new Response();
    $answer->addText('А фамилия?');
    return $answer;
});

// После шага $sNameTrigger сработает обработчик $yoTrigger
$bot->sendResponse($yoTrigger,static function() use ($bot){
    $answer = new Response();
    $answer->addText('Сколько тебе лет?');
    return $answer;
});

Для каждого из триггеров нужно создать обработчик с вопросом. При запросе пользователя "Ну давай познакомимся" сработает триггер $nameTrigger. Для триггеров $sNameTrigger и $yoTrigger токены не нужны (в общем то в данном случае они и не смогут сработать, тк пользотваель будет передавать информацию, и в ней нельзя распознать команду), они будут вызваны автоматически друг за другом.

Данные

Триггеры можно использовать не только как способ определения команды пользователя, но и для сбора информации.

//$personTrigger – назначен как следующий триггер после $yoTrigger
$bot->sendResponse($personTrigger,static function() use ($bot){

    //В качестве аргумента строка, уникальное название триггера
    $name = $bot->getTriggerData('NAME');
    $sName = $bot->getTriggerData('SECOND_NAME');
    $yo = $bot->getTriggerData('YEARS');
    $answer = new Answer();
    $answer->addText("Хорошо {$name} {$sName}, я тебя запомнила, и что тебе {$yo} лет – тоже");
    return $answer;
);

Бот сохраняет только один экземпляр данных для триггера, таким образом получить можно только последние полученные данные

Ответы

Ответы – это информация, которую бот отсылает пользователю, как только сработал триггер.

Текст и TTS

//$personTrigger – назначен как следующий триггер после $yoTrigger
$bot->sendResponse($personTrigger,static function() use ($bot){
    $answer = new Answer();
    $answer->addText('Один вариант ответа','Од!ин вари!ант отв!ета');
    return $answer;
);

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

Варианты

Для большей интерактивности следует использовать несколько возможных вариантов ответа. Вариант при отправке будет выбран случайно.

$bot->sendResponse($bullshitTrigger,static function() {
    $answer = new Answer();
    $answer->addText('Да не может быть!');
    $answer->addText('Чушь собачья!');
    $answer->addText('Не верю!');
    return $answer;
);

Кнопки

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

$bot->sendResponse($helloPerson,static function() {
    $answer = new Answer();
    $answer->addText('Может познакомимся?');
    $buttonY = new Button('Давай');
    $answer->addButton($button);
    $buttonN = new Button();
    $buttonN->setTitle('Неа');
    return $answer;
);

В данном примере при нажатии на кнопку будет вызван $nameTrigger из примера в пункте "Делегирование". Таким образом $nameTrigger может быть взыван как голосом пользователя, так и нажатием на кнопку, если пользователь не разговорчив.

Кнопки

Виды кнопок

Бот Алисы может выводить пользователи кнопки в двух разных видах – как кнопка, размещенная под диалогом, так и ссылка внутри отправляемого ответа.

$bButton = new Button('Это кнопка');
//Будет отображена как кнопка, под диалогом. Со следующем ответом отображена не будет, если ее не привязатели к ответу Response
$bButton->setHide(true);

$bLink = new Button('Это кнопка, но как ссылка')
//Будет отображена в ответе. Даже если пользователь выбирал что-то другие, эта ссылка так и останется в сообщении.
$bButton->setHide(false);

Базовые возможности

$bButton = new Button('Это кнопка');

* * * 

// Можно установить заголовок не в конструкторе, а в методе. Можно использовать как способ изменеия заголовка имеющейся кнопки, тем самым создавая вариативность
$bButton->setTitle('Это все еще кнопка')

// К кнопке можно добавить ссылку. При клике на нее произойдет переход по адресу в браузере.
$bButton->addLink('www.SITENAME.DOMAIN');

// Кнопке можно добавить обработчик какого-нибудь тригера. При клике на кнопку бот в первую очередь проверяет есть ли связанный Триггер для кнопки, и если есть – вызывает именно его.
$bButton->linkTrigger(Trigger $trigger);

//Кнопке можно передать какие-нибудь данные, которые можно будет забрать и использовать, если пользователь нажимал на эту кнопку.
$bButton->addPayload(['DATA'=>'SOME_DATA']);

Сохранение данных

Помимо отправленных пользователем данных, которые привязывается и сохраняеются триггером, можно сохранить и свои данные.

$bot->sendResponse($firstRequest,static function() use($bot) {
    * * *
   $bot->storeCommonData('Черный чай','TEA');
   $bot->getCommonData('TEA'); // Вернет "Черный чай", в т.ч. если пользователь уже перешел к другому запросу
    * * *
);
$bot->sendResponse($secondRequest,static function() use($bot) {
    * * *
   $bot->getCommonData('TEA'); // Вернет "Черный чай", в т.ч. если пользователь уже перешел к другому запросу
    * * *
);

Больше возможностей

Зацкиливание триггера

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

//Допустим что код триггера $chooseMeds – "MEDS"
$bot->sendResponse($chooseMeds, static function () use ($bot) {
    $answer = new Response();

    //Проверяем, это первичный запрос или цикл?
    if ( ! $bot->isRepeatedRequest()) {
        //Нет, значит это первичный запрос со стороны пользователя, предлагаем выбор
        $answer->addText('Какую таблетку ты выберешь, Нео?');
        //Задаем циклирование, результат будем проверять в этом же триггере
        $bot->setRepeat(true);
    } else {
        //Неправильный выбор?
        if ($bot->getTriggerData('MEDS') !== 'Синяя') {
            $answer->addText('Неверный выбор, Нео');
            //Задать цикл
            $bot->setRepeat(true);
        } else {
            //Все ок, выход из триггера
            $answer->addText('Ты сделал правильный выбор о-о');
        }
    }
    return $answer;
});

Внутреннее делегирование

Ручная подстановка данных для триггера

Пост обработка ответа