Руководство по совместному использованию бота веб-приложения и службы "Распознавание речи" в Node.js

Создавайте чат-боты, интегрированные со службой "Распознавание речи" (LUIS), используя Node.js. Бот создается с помощью ресурса Бот веб-приложения Azure и Bot Framework версии 4.

В этом руководстве рассмотрено, как выполнять следующие задачи.

  • Создание бота веб-приложения. Этот процесс создает новое приложение LUIS.
  • Скачивание проекта бота, созданного веб-службой ботов
  • Запуск бота и эмулятора на локальном компьютере
  • Просмотр результатов высказывания в боте

Предварительные требования

Создание ресурса бота веб-приложения

  1. На портале Azure выберите Создание ресурса.

  2. С помощью поля поиска найдите и выберите Web App Bot (Бот веб-приложения). Нажмите кнопку создания.

  3. В Службе Bot введите необходимые сведения.

    Параметр Назначение Предлагаемый параметр
    Bot handle (Дескриптор бота) Имя ресурса luis-nodejs-bot- + <your-name>, например luis-nodejs-bot-johnsmith
    Подписка Подписка, в которой нужно создать бот. Основная подписка.
    Группа ресурсов Логическая группа ресурсов Azure Создайте группу для хранения всех ресурсов, используемых с этим ботом, и назовите эту группу luis-nodejs-bot-resource-group.
    Расположение Регион Azure не обязательно должен совпадать с регионом разработки или публикации LUIS. westus
    Ценовая категория Используется для ограничения запросов службы и выставления счетов. F0 — уровень "Бесплатный".
    Имя приложения. При развертывании бота в облаке имя используется в качестве поддомена (например, humanresourcesbot.azurewebsites.net). luis-nodejs-bot- + <your-name>, например luis-nodejs-bot-johnsmith
    Шаблон бота Параметры Bot Framework (см. следующую таблицу)
    Расположение приложения LUIS Должно совпадать с регионом ресурса LUIS westus
    расположение или план службы приложений; Значение по умолчанию не следует изменять.
    Application Insights Значение по умолчанию не следует изменять.
    Идентификатор и пароль приложения Майкрософт Значение по умолчанию не следует изменять.
  4. Выберите следующие компоненты в шаблоне бота, а затем нажмите кнопку Выбрать под этими параметрами:

    Параметр Назначение Выбор
    Язык пакета SDK Язык программирования бота Node.js
    Бот Тип бота Базовый бот
  5. Нажмите кнопку создания. В Azure будет создана и развернута служба ботов. Элементом этого процесса является создание приложения LUIS с именем luis-nodejs-bot-XXXX. Это имя основано на имени приложения службы Azure Bot.

    Создание бота веб-приложения

    Прежде, чем продолжить, дождитесь создания службы ботов.

  6. Выберите Go to resource в уведомлении, чтобы открыть страницу бота веб-приложения.

Бот содержит модель распознавания речи

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

Намерения базового бота LUIS Пример высказывания
Заказ авиабилетов Travel to Paris
Отмена bye
GetWeather what's the weather like?
None Все, что находится за пределами домена приложения.

Тестирование бота в веб-чате

  1. Не выходя из портала Azure для нового бота, выберите Test in Web Chat (Тестирование в веб-чате).

  2. В текстовом поле Введите сообщение введите Book a flight from Seattle to Berlin tomorrow. Бот реагирует с подтверждением того, что вы хотите купить билет на самолет.

    Снимок экрана портала Azure, на котором введен текст "hello".

    Для быстрой проверки бота можно выполнить тестирование. Для выполнения более сложного тестирования, в которое входит отладка, скачайте код бота и используйте Visual Studio Code.

Скачивание исходного кода бота веб-приложения

Чтобы разработать код бота веб-приложения, загрузите код и используйте его на локальном компьютере.

  1. Выберите Сборка в разделе Bot management (Управление ботом) на портале Azure.

  2. Выберите Download Bot source code (Загрузка исходного кода бота).

    Загрузка исходного кода бота веб-приложения для базового бота

  3. При появлении всплывающего диалогового окна с запросом Include app settings in the downloaded zip file? (Включить параметры приложения в скачанный ZIP-файл?) выберите Да. Таким образом вы получите доступ к параметрам LUIS.

  4. Когда исходный код запакован, появляется сообщение со ссылкой для загрузки кода. Перейдите по ссылке.

  5. Сохраните ZIP-файл на локальном компьютере и извлеките файлы. Откройте папку проекта в Visual Studio Code.

Проверка кода, который используется для отправки высказывания в LUIS, и получение ответа

  1. Чтобы отправить речевой фрагмент пользователя в конечную точку прогнозирования LUIS, выберите Диалоги -> FlightBookingRecognizer.js. Это место, где фраза пользователя, введенная в бот, отправляется в LUIS. Ответ от LUIS будет получен из метода executeLuisQuery.

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License.
    
    const { LuisRecognizer } = require('botbuilder-ai');
    
    class FlightBookingRecognizer {
        constructor(config) {
            const luisIsConfigured = config && config.applicationId && config.endpointKey && config.endpoint;
            if (luisIsConfigured) {
                // Set the recognizer options depending on which endpoint version you want to use e.g v2 or v3.
                // More details can be found in https://docs.microsoft.com/en-gb/azure/cognitive-services/luis/luis-migration-api-v3
                const recognizerOptions = {
                    apiVersion: 'v3'
                };
    
                this.recognizer = new LuisRecognizer(config, recognizerOptions);
            }
        }
    
        get isConfigured() {
            return (this.recognizer !== undefined);
        }
    
        /**
         * Returns an object with preformatted LUIS results for the bot's dialogs to consume.
         * @param {TurnContext} context
         */
        async executeLuisQuery(context) {
            return await this.recognizer.recognize(context);
        }
    
        getFromEntities(result) {
            let fromValue, fromAirportValue;
            if (result.entities.$instance.From) {
                fromValue = result.entities.$instance.From[0].text;
            }
            if (fromValue && result.entities.From[0].Airport) {
                fromAirportValue = result.entities.From[0].Airport[0][0];
            }
    
            return { from: fromValue, airport: fromAirportValue };
        }
    
        getToEntities(result) {
            let toValue, toAirportValue;
            if (result.entities.$instance.To) {
                toValue = result.entities.$instance.To[0].text;
            }
            if (toValue && result.entities.To[0].Airport) {
                toAirportValue = result.entities.To[0].Airport[0][0];
            }
    
            return { to: toValue, airport: toAirportValue };
        }
    
        /**
         * This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the Time part.
         * TIMEX is a format that represents DateTime expressions that include some ambiguity. e.g. missing a Year.
         */
        getTravelDate(result) {
            const datetimeEntity = result.entities.datetime;
            if (!datetimeEntity || !datetimeEntity[0]) return undefined;
    
            const timex = datetimeEntity[0].timex;
            if (!timex || !timex[0]) return undefined;
    
            const datetime = timex[0].split('T')[0];
            return datetime;
        }
    }
    
    module.exports.FlightBookingRecognizer = FlightBookingRecognizer;
    
  2. Выберите Диалоги -> mainDialog, чтобы записать речевой фрагмент и отправить его в запрос executeLuisQuery в методе actStep.

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License.
    
    const { TimexProperty } = require('@microsoft/recognizers-text-data-types-timex-expression');
    const { MessageFactory, InputHints } = require('botbuilder');
    const { LuisRecognizer } = require('botbuilder-ai');
    const { ComponentDialog, DialogSet, DialogTurnStatus, TextPrompt, WaterfallDialog } = require('botbuilder-dialogs');
    
    const MAIN_WATERFALL_DIALOG = 'mainWaterfallDialog';
    
    class MainDialog extends ComponentDialog {
        constructor(luisRecognizer, bookingDialog) {
            super('MainDialog');
    
            if (!luisRecognizer) throw new Error('[MainDialog]: Missing parameter \'luisRecognizer\' is required');
            this.luisRecognizer = luisRecognizer;
    
            if (!bookingDialog) throw new Error('[MainDialog]: Missing parameter \'bookingDialog\' is required');
    
            // Define the main dialog and its related components.
            // This is a sample "book a flight" dialog.
            this.addDialog(new TextPrompt('TextPrompt'))
                .addDialog(bookingDialog)
                .addDialog(new WaterfallDialog(MAIN_WATERFALL_DIALOG, [
                    this.introStep.bind(this),
                    this.actStep.bind(this),
                    this.finalStep.bind(this)
                ]));
    
            this.initialDialogId = MAIN_WATERFALL_DIALOG;
        }
    
        /**
         * The run method handles the incoming activity (in the form of a TurnContext) and passes it through the dialog system.
         * If no dialog is active, it will start the default dialog.
         * @param {*} turnContext
         * @param {*} accessor
         */
        async run(turnContext, accessor) {
            const dialogSet = new DialogSet(accessor);
            dialogSet.add(this);
    
            const dialogContext = await dialogSet.createContext(turnContext);
            const results = await dialogContext.continueDialog();
            if (results.status === DialogTurnStatus.empty) {
                await dialogContext.beginDialog(this.id);
            }
        }
    
        /**
         * First step in the waterfall dialog. Prompts the user for a command.
         * Currently, this expects a booking request, like "book me a flight from Paris to Berlin on march 22"
         * Note that the sample LUIS model will only recognize Paris, Berlin, New York and London as airport cities.
         */
        async introStep(stepContext) {
            if (!this.luisRecognizer.isConfigured) {
                const messageText = 'NOTE: LUIS is not configured. To enable all capabilities, add `LuisAppId`, `LuisAPIKey` and `LuisAPIHostName` to the .env file.';
                await stepContext.context.sendActivity(messageText, null, InputHints.IgnoringInput);
                return await stepContext.next();
            }
    
            const messageText = stepContext.options.restartMsg ? stepContext.options.restartMsg : 'What can I help you with today?\nSay something like "Book a flight from Paris to Berlin on March 22, 2020"';
            const promptMessage = MessageFactory.text(messageText, messageText, InputHints.ExpectingInput);
            return await stepContext.prompt('TextPrompt', { prompt: promptMessage });
        }
    
        /**
         * Second step in the waterfall.  This will use LUIS to attempt to extract the origin, destination and travel dates.
         * Then, it hands off to the bookingDialog child dialog to collect any remaining details.
         */
        async actStep(stepContext) {
            const bookingDetails = {};
    
            if (!this.luisRecognizer.isConfigured) {
                // LUIS is not configured, we just run the BookingDialog path.
                return await stepContext.beginDialog('bookingDialog', bookingDetails);
            }
    
            // Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt)
            const luisResult = await this.luisRecognizer.executeLuisQuery(stepContext.context);
            switch (LuisRecognizer.topIntent(luisResult)) {
            case 'BookFlight': {
                // Extract the values for the composite entities from the LUIS result.
                const fromEntities = this.luisRecognizer.getFromEntities(luisResult);
                const toEntities = this.luisRecognizer.getToEntities(luisResult);
    
                // Show a warning for Origin and Destination if we can't resolve them.
                await this.showWarningForUnsupportedCities(stepContext.context, fromEntities, toEntities);
    
                // Initialize BookingDetails with any entities we may have found in the response.
                bookingDetails.destination = toEntities.airport;
                bookingDetails.origin = fromEntities.airport;
                bookingDetails.travelDate = this.luisRecognizer.getTravelDate(luisResult);
                console.log('LUIS extracted these booking details:', JSON.stringify(bookingDetails));
    
                // Run the BookingDialog passing in whatever details we have from the LUIS call, it will fill out the remainder.
                return await stepContext.beginDialog('bookingDialog', bookingDetails);
            }
    
            case 'GetWeather': {
                // We haven't implemented the GetWeatherDialog so we just display a TODO message.
                const getWeatherMessageText = 'TODO: get weather flow here';
                await stepContext.context.sendActivity(getWeatherMessageText, getWeatherMessageText, InputHints.IgnoringInput);
                break;
            }
    
            default: {
                // Catch all for unhandled intents
                const didntUnderstandMessageText = `Sorry, I didn't get that. Please try asking in a different way (intent was ${ LuisRecognizer.topIntent(luisResult) })`;
                await stepContext.context.sendActivity(didntUnderstandMessageText, didntUnderstandMessageText, InputHints.IgnoringInput);
            }
            }
    
            return await stepContext.next();
        }
    
        /**
         * Shows a warning if the requested From or To cities are recognized as entities but they are not in the Airport entity list.
         * In some cases LUIS will recognize the From and To composite entities as a valid cities but the From and To Airport values
         * will be empty if those entity values can't be mapped to a canonical item in the Airport.
         */
        async showWarningForUnsupportedCities(context, fromEntities, toEntities) {
            const unsupportedCities = [];
            if (fromEntities.from && !fromEntities.airport) {
                unsupportedCities.push(fromEntities.from);
            }
    
            if (toEntities.to && !toEntities.airport) {
                unsupportedCities.push(toEntities.to);
            }
    
            if (unsupportedCities.length) {
                const messageText = `Sorry but the following airports are not supported: ${ unsupportedCities.join(', ') }`;
                await context.sendActivity(messageText, messageText, InputHints.IgnoringInput);
            }
        }
    
        /**
         * This is the final step in the main waterfall dialog.
         * It wraps up the sample "book a flight" interaction with a simple confirmation.
         */
        async finalStep(stepContext) {
            // If the child dialog ("bookingDialog") was cancelled or the user failed to confirm, the Result here will be null.
            if (stepContext.result) {
                const result = stepContext.result;
                // Now we have all the booking details.
    
                // This is where calls to the booking AOU service or database would go.
    
                // If the call to the booking service was successful tell the user.
                const timeProperty = new TimexProperty(result.travelDate);
                const travelDateMsg = timeProperty.toNaturalLanguage(new Date(Date.now()));
                const msg = `I have you booked to ${ result.destination } from ${ result.origin } on ${ travelDateMsg }.`;
                await stepContext.context.sendActivity(msg, msg, InputHints.IgnoringInput);
            }
    
            // Restart the main dialog with a different message the second time around
            return await stepContext.replaceDialog(this.initialDialogId, { restartMsg: 'What else can I do for you?' });
        }
    }
    
    module.exports.MainDialog = MainDialog;
    

Запуск кода бота

  1. Откройте консоль Windows либо терминал Linux или macOS.

  2. Перейдите в каталог с кодом бота и введите следующую команду, чтобы установить зависимость dotenv:

    npm install dotenv --save
    
  3. Введите следующую команду, чтобы запустить бота:

    node index.js
    

Откроется окно браузера с веб-сайтом бота веб-приложения на http://localhost:3978/. Сведения о боте отображаются на домашней странице.

Сведения о боте отображаются на домашней странице.

Тестирование бота с помощью Bot Framework Emulator

Отправьте боту запрос с намерением заказа авиабилета.

  1. Запустите эмулятор Bot Framework и выберите Open Bot (Открыть бота).

  2. Во сплывающем диалоговом окне Open Bot (Открыть бот) введите URL-адрес бота, например http://localhost:3978/api/messages. Путь /api/messages — веб-адрес бота.

  3. Заполните поля Microsoft App ID (Идентификатор приложения Майкрософт) и Microsoft App password (Пароль приложения Майкрософт), значения которых можно найти в файле ENV в корне скачанного кода бота.

  4. В эмуляторе Bot Framework введите Book a flight from Seattle to Berlin tomorrow и убедитесь, что получаете такой же ответ от бота, как и при тестировании в веб-чате.

    Снимок экрана: Bot Framework Emulator.

  5. Выберите Да. Бот ответит сводкой своих действий.

  6. Из журнала эмулятора Bot Framework выберите строку, в которой содержится <- trace LuisV3 Trace. Появится ответ в формате JSON, полученный от LUIS, для намерения и сущностей речевого фрагмента.

    Снимок экрана: Bot Framework Emulator с кодом JSON в области справа.

Дополнительные сведения о ботах

Для получения дополнительных сведений об использовании этой службы с помощью ботов начните со следующих ресурсов:

Ресурс Назначение
Служба Azure Bot Служба Azure Bot предоставляет полную развернутую веб-службу с конечной точкой бота. Службы используют Bot Framework, доступную на нескольких языках.
Bot Framework Microsoft Bot Framework — это комплексная платформа для создания решений ИИ корпоративного уровня.
Bot Framework Emulator. Bot Framework Emulator — это кроссплатформенное классическое приложение, которое позволяет разработчикам ботов выполнять тестирование и отладку ботов, созданных с помощью пакета SDK Bot Framework. Bot Framework Emulator можно использовать для тестирования ботов локально на компьютере или удаленно.
Средства бота Средства Bot Framework представляют собой коллекцию кроссплатформенных программ командной строки, спроектированных для поддержки всех этапов разработки ботов.
Примеры конструктора ботов Полностью разработанные примеры ботов предназначены для демонстрации сценариев, которые необходимо реализовать для создания полезных ботов.

Дальнейшие действия

См. дополнительные примеры чат-ботов.

Tutorial: Build LUIS app to determine user intentions (Учебник: создание приложения LUIS для определения намерений пользователя)