Разбираемся с Microsoft Bot Framework

Маленький Бендер

Так как конторский CI/CD висит на мне, то натурально я заинтересован в том, чтобы билд оставался зелёным. Не то, чтобы я тут же бросался к каждому упавшему тесту, но за ненадёжными точно присматриваю.

Когда ветка master держится красной чересчур уж долго, вот какие штуки начинают творится с её упавшими тестами:

  1. Ищем историю падений теста в нашем Google BigQuery (select Name, Result, count(*) from TestResults_...).
  2. Если исторически тест вел себя в большей степени как генератор случайных результатов, и в меньшей степени как тест, создаём для него тикет.
  3. Добавляем тест в игнор и указываем в качестве причины вновь созданный тикет.
  4. Находим, кто же написал это непотребство (git blame) и вешаем кейс на автора.

В общем, очень просто. И ещё очень скучно. Тестов-то у нас много. Я бы автоматизировал это всё, но есть одно небольшое «но» — не всегда можно определить, кто же текущий «владелец» теста. Программисты же увольняются, рефакторят чужое, ну и ломают git’овскую историю по праздникам. Я думал заморочиться и выкатить какое-нибудь machine learning решение для этого, но то попахивает перебором. А вот написать бота выглядит как-то более выполнимо. Он бы мониторил тесты, отслеживал статистику, и когда ему что-то непонятно, вроде на кого повесить кейс, спрашивал бы меня.

Осталось только понять, как этих ботов делают.

Выбираем фреймуорк для ботов

Если спросить о ботах гугла, то достаточно быстро тот посоветует посмотреть в сторону Microsoft Bot Framework. Он и BotKit иногда рекомендует, но всё-таки когда за решением стоит Microsoft, есть хорошие шансы, что и примеров для него будет достаточно. К тому же писать для Bot Framework можно как на C#, так и на JavaScript, что делает меня вдвойне счастливым.

На первый взгляд, писать на Bot фреймуорке просто. Например, есть объект Bot, который поддерживает Беседы/Conversations  c УмнымиЛюдьми/Users через различные Каналы/Channels. Один Conversation может включать в себя несколько Диалогов/Dialogs, а Channel — это посредством чего пользователь общается с ботом: SMS, Skype, Facebook и прочие. Всё разумно. Есть там и другие концепции, но для начала и этих достаточно.

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

Пишем бота для «Hello World»

«Hello World» в мире ботов — это бот, отвечающий той же фразой, которой к нему обратились. Так как порог входа в ботовские C# библиотеки несколько выше, чем в оные для Nodejs, с последнего и начнём.

Вот, как оно работает. Сначала инициализируем проект и устанавливаем пакет botbuilder :

А потом, собственно, делаем бота:

Так как общаться с ним сейчас мы будем через консоль, то для неё нужно создать консольный же коннектор (строка 2). Затем надо создать самого бота (4), подать ему на вход коннектор (5) и дефолтный обработчик пользовательских фраз. Наш обработчик просто будет отвечать тем же, что ему подали на вход, предварительно приставив к фразе Hello world and. Как-то так:

Типы разговоров с ботами

По пьяни и за жизнь. В MS Bot Framework есть два основных вида типа бесед: водопад (waterfall) и на основе диалогов.

Водопад

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

мост смерти в чат пацифистов

Ничего фундаментально не меняя в предыдущем примере, вот как это можно сделать.

Вся беседа сводится к двум JavaScript-функциям: вопросу («Вы против войны?») и реакции на ответ. Так как на такой вопрос можно ответить только «Да», или «Нет», я использовал встроенную функцию builder.Prompts.choice, которая поможет ответить правильно даже самым талантливым.

А вот как будет выглядить такая беседа.

Что здорово, в зависимости от типа Channel и его коннектора, Prompts.choice постарается использовать поддерживаемый им UI по максимуму. Например, ChatConnector на пару с Bot Emulator умеют рисовать кнопочки для вариантов ответа.

Беседы, основанные на диалогах

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

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

  1. Попросить показать список команд, которые он понимает.
  2. Пнуть, чтобы он начал отслеживать ненадёжные тесты.
  3. Научить балбеса, как часто (в процентах) тест должен падать, чтобы его можно было считать ненадёжным.
  4. Попросить прекратить отслеживать тесты.
  5. Проверить текущий статус.

Каждую тему можно оформить в виде диалога. Что интересно, диалог на одну тему может внезапно перейти на другую. Например, попросив бота начать отслеживать тесты, неизбежно придётся ему сначала рассказать, как их различать (диалог 2 -> 3). Ещё одна интересная штука — у бота должно быть какое-то состояние (state). Во-первых, рубеж, когда после которого падающий тест начинает считаться ненадёжным. Это число можно ассоциировать с текущим пользователем (userData). А во-вторых, бот должен помнить, мониторит ли он сейчас мониторит тесты, или нет. Этот флаг можно привязать к контексту текущей беседы (conversationData).

В общем, вот, как это можно сделать.

Дефолтный обработчик сообщений

Если я скажу боту что-то для него незнакомое, он должен удивиться. И, например, рассказать, что именно он умеет понимать.

А понимать он будет уметь три фразы: status для получения статуса, watch для начала мониторинга, и stop для его завершения.

В принципе, тут нет ничего ни сложного, ни интересного. Разве что создание объекта для хранения состояния. Копи-пастим код и проверяем электронного товарища:

Спрашиваем бота про статус

Обнаруживать, что мы хотим общаться про статус, и вести эту беседу будет Dialog под названием StatusDialog.

Как я и говорил, диалог действительно похож на обычную waterfall беседу. Только с триггером на ключевые слова.

Проверяем:

Просим бота остановиться

Это команда stop и она очень простая. Если в conversationData стоит флаг, что бот занят делом, снимаем его. Если нет — извиняемся и машем.

Настраиваем бота

Этот диалог будет чуууточку, но интереснее. Хотя бы потому, что бот попросит нас ввести число в процентах, и постарается его распарсить. Чтобы не пришлось это делать руками, я использовал очередную встроенную функцию builder.Prompts.number, и вот, что из этого получилось.

Так как у диалога есть триггер, то запустить его можно по ключевому слову в любой момент. Но будет интересно, если бот cам переведёт на него стрелки.

Просим бота поработать

Вот тут уже немного интересно. Кроме проверки на то, не занят ли бот делом уже сейчас, он ещё проверит, рассказал ему кто-нибудь, как часто тест должен падать, чтобы отнести его к ненадёжным. Если failuresPercentageThreshold не установлена, то бот сначала переключится на диалог настройки, и уже потом, по возвращению, продолжит начальный диалог.

Много общаемся

Прикольно.

В качестве бонуса, вот она, полезность от объектно-ориентированного программирования и принципа единственной ответственности. Если заменить  ConsoleConnector на ChatConnector и добавить маленький веб-сервер, то, не меняя абсолютно ничего в мозгах бота, с ним уже можно общаться через UI того же Bot Framework Emulator.

Bot Emulator

Мораль

Оказывается, писать ботов — это весело. И это можно сделать ещё веселее, подключив к процессу Microsoft Cognitive Services, которые могут конкретно добавить мозгов любому боту.

Например, когда я писал триггеры для диалогов, он получались очень уж негибкие. Если вместо команды Watch отправить команду Start, например, то бот извинится, и ничего не сделает. А ведь любому человеку было бы понятно, что в данном контексте это почти одно и то же. А вот прикрутил бы я к процессу распознавания LUIS — сервис для обработки естественного языка, то бот, может, и догадался бы.

Я видел с LUIS пару примеров, и это действительно внушает. Например, показали ему, что ‘Hello’ — это из разряда приветствия. Так он тут же догадался, что ‘Howdy’, ‘Hey’ и даже ‘Wazzup!’ это примерно из той же оперы, так что какой-нибудь диалог, ждущий приветствия, точно бы сработал. Кроме этого Луис понимает, что ’10с’ и ‘десять секунд’ — это одно и то же. Или «Вчера» — это дата. Ну и всё в том же духе. Выглядит внушительно.

Ладно, я всё ещё не решил, буду ли писать бота для юнит тестов, но по крайней мере теперь понимаю, что написать его будет не сложно. Тяжелее будет настроить сервисы для баг трэкера, гита и базы данных с результатами тестов, чтобы бот мог с ними общаться. А остальное — ерунда.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *