Использование диалогов в навыке

ОБЛАСТЬ ПРИМЕНЕНИЯ: ПАКЕТ SDK версии 4

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

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

Примечание.

Пакеты SDK для JavaScript, C# и Python для Bot Framework по-прежнему будут поддерживаться, однако пакет SDK java отменяется с окончательной долгосрочной поддержкой, заканчивающейся в ноябре 2023 года.

Существующие боты, созданные с помощью пакета SDK для Java, будут продолжать функционировать.

Для создания нового бота рекомендуется использовать Power Virtual Agent и ознакомиться с выбором подходящего решения чат-бота.

Дополнительные сведения см. в статье "Будущее создания бота".

Необходимые компоненты

Примечание.

Распознавание речи (LUIS) будет прекращен 1 октября 2025 года. Начиная с 1 апреля 2023 года вы не сможете создавать новые ресурсы LUIS. Новая версия распознавания речи теперь доступна как часть языка ИИ Azure.

Распознавание речи (CLU) — это обновленная версия LUIS. Дополнительные сведения о поддержке распознавания речи в пакете SDK Bot Framework см. в разделе "Распознавание естественного языка".

Об этом примере

В пример skills skillDialog включены проекты двух ботов:

  • Корневой бот диалога, который использует диалог навыка для применения навыка.
  • Бот навыка диалога, который использует диалог для обработки действий, поступающих от потребителей навыков. Этот навык является адаптацией примера core bot. (Сведения о примере core bot см. в статье о добавлении функции распознавания естественного языка в бота.)

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

Сведения о боте, выполняющем роль потребителя навыка, см. в статье об использовании навыка с помощью диалогов.

Ресурсы

Для развернутых ботов проверка подлинности "бот — бот" требует, чтобы каждый участвующий бот имеет допустимое удостоверение. Однако вы можете протестировать навыки и навыки потребителей локально с помощью эмулятора Bot Framework без сведений об удостоверениях.

Чтобы сделать навык доступным для пользователей ботов, зарегистрируйте навык в Azure. Дополнительные сведения см. в статье о регистрации бота с помощью Azure AI Служба Bot.

Кроме того, бот навыка может использовать модель LUIS для бронирования авиабилетов. Чтобы применить эту модель, с помощью файла CognitiveModels/FlightBooking.json создайте, обучите и опубликуйте модель LUIS.

Конфигурация приложений

  1. При необходимости добавьте сведения об удостоверениях навыка в файл конфигурации навыка. (Если пользователь навыка или навыка указывает удостоверение, оба должны.)

  2. Если вы используете модель LUIS, добавьте идентификатор приложения LUIS, ключ API и имя узла API.

DialogSkillBot\appsettings.json

{
  "MicrosoftAppType": "",
  "MicrosoftAppId": "",
  "MicrosoftAppPassword": "",
  "MicrosoftAppTenantId": "",
  "ConnectionName": "",

  "LuisAppId": "",
  "LuisAPIKey": "",
  "LuisAPIHostName": "",

  // This is a comma separate list with the App IDs that will have access to the skill.
  // This setting is used in AllowedCallersClaimsValidator.
  // Examples: 
  //    [ "*" ] allows all callers.
  //    [ "AppId1", "AppId2" ] only allows access to parent bots with "AppId1" and "AppId2".
  "AllowedCallers": [ "*" ]
}

Логика activity-routing

Этот навык поддерживает несколько разных функций. Он может забронировать авиабилет и получить прогноз погоды для города. Кроме того, если он получает сообщение за пределами одного из этих контекстов, он может использовать LUIS, чтобы попытаться интерпретировать сообщение. Манифест навыка описывает эти действия, входные и выходные параметры, а также конечные точки навыка. Обратите внимание, что навык умеет обрабатывать события BookFlight и GetWeather. Он также обрабатывает действия сообщений.

Навык определяет диалог activity-routing, который используется для выбора запускаемого действия в зависимости от исходного действия, полученного от потребителя навыков. Если предоставлена модель LUIS, она может распознавать в исходном сообщении намерения бронирования авиабилетов и получения прогноза погоды.

Действие book-flight — это многоэтапный процесс, который реализован в отдельном диалоге. Начавшись, это действие обрабатывает все входящие действия. Действие get-weather содержит логику заполнителя, которую для полноценной реализации бота нужно будет заменить.

Диалог activity-routing содержит код для выполнения следующих действий:

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

инициализация диалога;

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

Он также может принимать распознаватель LUIS. Если этот распознаватель инициализирован, диалог попытается применить его для оценки намерения в исходном действии сообщения.

DialogSkillBot\Dialogs\ActivityRouterDialog.cs

private readonly DialogSkillBotRecognizer _luisRecognizer;

public ActivityRouterDialog(DialogSkillBotRecognizer luisRecognizer)
    : base(nameof(ActivityRouterDialog))
{
    _luisRecognizer = luisRecognizer;

    AddDialog(new BookingDialog());
    AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { ProcessActivityAsync }));

    // The initial child Dialog to run.
    InitialDialogId = nameof(WaterfallDialog);
}

обработка начального действия;

На первом (и только первом) шаге в главном каскадном диалоге навык проверяет тип входящего действия.

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

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

DialogSkillBot\Dialogs\ActivityRouterDialog.cs

private async Task<DialogTurnResult> ProcessActivityAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    // A skill can send trace activities, if needed.
    await stepContext.Context.TraceActivityAsync($"{GetType().Name}.ProcessActivityAsync()", label: $"Got ActivityType: {stepContext.Context.Activity.Type}", cancellationToken: cancellationToken);

    switch (stepContext.Context.Activity.Type)
    {
        case ActivityTypes.Event:
            return await OnEventActivityAsync(stepContext, cancellationToken);

        case ActivityTypes.Message:
            return await OnMessageActivityAsync(stepContext, cancellationToken);

        default:
            // We didn't get an activity type we can handle.
            await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Unrecognized ActivityType: \"{stepContext.Context.Activity.Type}\".", inputHint: InputHints.IgnoringInput), cancellationToken);
            return new DialogTurnResult(DialogTurnStatus.Complete);
    }
}
// This method performs different tasks based on the event name.
private async Task<DialogTurnResult> OnEventActivityAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    var activity = stepContext.Context.Activity;
    await stepContext.Context.TraceActivityAsync($"{GetType().Name}.OnEventActivityAsync()", label: $"Name: {activity.Name}. Value: {GetObjectAsJsonString(activity.Value)}", cancellationToken: cancellationToken);

    // Resolve what to execute based on the event name.
    switch (activity.Name)
    {
        case "BookFlight":
            return await BeginBookFlight(stepContext, cancellationToken);

        case "GetWeather":
            return await BeginGetWeather(stepContext, cancellationToken);

        default:
            // We didn't get an event name we can handle.
            await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Unrecognized EventName: \"{activity.Name}\".", inputHint: InputHints.IgnoringInput), cancellationToken);
            return new DialogTurnResult(DialogTurnStatus.Complete);
    }
}

обработка действий сообщения;

Если настроен распознаватель LUIS, навык вызывает LUIS и запускает действие на основе намерения. Если распознаватель LUIS не настроен или намерение не поддерживается, навык отправляет сообщение об ошибке и заканчивается.

DialogSkillBot\Dialogs\ActivityRouterDialog.cs

// This method just gets a message activity and runs it through LUIS. 
private async Task<DialogTurnResult> OnMessageActivityAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    var activity = stepContext.Context.Activity;
    await stepContext.Context.TraceActivityAsync($"{GetType().Name}.OnMessageActivityAsync()", label: $"Text: \"{activity.Text}\". Value: {GetObjectAsJsonString(activity.Value)}", cancellationToken: cancellationToken);

    if (!_luisRecognizer.IsConfigured)
    {
        await stepContext.Context.SendActivityAsync(MessageFactory.Text("NOTE: LUIS is not configured. To enable all capabilities, add 'LuisAppId', 'LuisAPIKey' and 'LuisAPIHostName' to the appsettings.json file.", inputHint: InputHints.IgnoringInput), cancellationToken);
    }
    else
    {
        // Call LUIS with the utterance.
        var luisResult = await _luisRecognizer.RecognizeAsync<FlightBooking>(stepContext.Context, cancellationToken);

        // Create a message showing the LUIS results.
        var sb = new StringBuilder();
        sb.AppendLine($"LUIS results for \"{activity.Text}\":");
        var (intent, intentScore) = luisResult.Intents.FirstOrDefault(x => x.Value.Equals(luisResult.Intents.Values.Max()));
        sb.AppendLine($"Intent: \"{intent}\" Score: {intentScore.Score}");

        await stepContext.Context.SendActivityAsync(MessageFactory.Text(sb.ToString(), inputHint: InputHints.IgnoringInput), cancellationToken);

        // Start a dialog if we recognize the intent.
        switch (luisResult.TopIntent().intent)
        {
            case FlightBooking.Intent.BookFlight:
                return await BeginBookFlight(stepContext, cancellationToken);

            case FlightBooking.Intent.GetWeather:
                return await BeginGetWeather(stepContext, cancellationToken);

            default:
                // Catch all for unhandled intents.
                var didntUnderstandMessageText = $"Sorry, I didn't get that. Please try asking in a different way (intent was {luisResult.TopIntent().intent})";
                var didntUnderstandMessage = MessageFactory.Text(didntUnderstandMessageText, didntUnderstandMessageText, InputHints.IgnoringInput);
                await stepContext.Context.SendActivityAsync(didntUnderstandMessage, cancellationToken);
                break;
        }
    }

    return new DialogTurnResult(DialogTurnStatus.Complete);
}

запуск многошагового действия;

Действие бронирования авиабилетов book-flight запускает многошаговый диалог для получения сведений о бронировании от пользователя.

Действие get-weather не реализовано. Сейчас оно возвращает заполнитель для сообщения и завершает работу.

DialogSkillBot\Dialogs\ActivityRouterDialog.cs

private async Task<DialogTurnResult> BeginBookFlight(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    var activity = stepContext.Context.Activity;
    var bookingDetails = new BookingDetails();
    if (activity.Value != null)
    {
        bookingDetails = JsonConvert.DeserializeObject<BookingDetails>(JsonConvert.SerializeObject(activity.Value));
    }

    // Start the booking dialog.
    var bookingDialog = FindDialog(nameof(BookingDialog));
    return await stepContext.BeginDialogAsync(bookingDialog.Id, bookingDetails, cancellationToken);
}
private static async Task<DialogTurnResult> BeginGetWeather(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    var activity = stepContext.Context.Activity;
    var location = new Location();
    if (activity.Value != null)
    {
        location = JsonConvert.DeserializeObject<Location>(JsonConvert.SerializeObject(activity.Value));
    }

    // We haven't implemented the GetWeatherDialog so we just display a TODO message.
    var getWeatherMessageText = $"TODO: get weather for here (lat: {location.Latitude}, long: {location.Longitude}";
    var getWeatherMessage = MessageFactory.Text(getWeatherMessageText, getWeatherMessageText, InputHints.IgnoringInput);
    await stepContext.Context.SendActivityAsync(getWeatherMessage, cancellationToken);
    return new DialogTurnResult(DialogTurnStatus.Complete);
}

возврат результата.

Навык запускает диалог бронирования для действия book-flight. Так как диалог activity-routing содержит только один шаг, при завершении диалога бронирования завершится и диалог activity-routing, а результат выполнения диалога бронирования автоматически становится результатом диалога activity-routing.

Действие get-weather просто завершается без возвращаемого значения.

Отмена многошагового действия

Диалог бронирования и его дочерний диалог date-resolver являются производными от базового диалога cancel-and-help, который проверяет получение сообщений от пользователя.

  • При вводе help или ? он отображает справочную информацию и возвращается на следующем шаге в поток беседы.
  • При вводе cancel или quit он завершает все диалоги, завершая работу навыка.

Дополнительные сведения см. в статье об обработке прерываний пользователя.

Регистрация службы

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

Манифест навыка

Манифест навыка — это файл в формате JSON с описанием действий, которые может выполнять навык, его входных и выходных параметров, а также конечных точек навыка. Манифест содержит сведения, которые вам нужны для доступа к навыку из другого бота.

DialogSkillBot\wwwroot\manifest\dialogchildbot-manifest-1.0.json

{
  "$schema": "https://schemas.botframework.com/schemas/skills/skill-manifest-2.0.0.json",
  "$id": "DialogSkillBot",
  "name": "Skill bot with dialogs",
  "version": "1.0",
  "description": "This is a sample skill definition for multiple activity types.",
  "publisherName": "Microsoft",
  "privacyUrl": "https://dialogskillbot.contoso.com/privacy.html",
  "copyright": "Copyright (c) Microsoft Corporation. All rights reserved.",
  "license": "",
  "iconUrl": "https://dialogskillbot.contoso.com/icon.png",
  "tags": [
    "sample",
    "travel",
    "weather",
    "luis"
  ],
  "endpoints": [
    {
      "name": "default",
      "protocol": "BotFrameworkV3",
      "description": "Default endpoint for the skill.",
      "endpointUrl": "https://dialogskillbot.contoso.com/api/messages",
      "msAppId": "00000000-0000-0000-0000-000000000000"
    }
  ],
  "activities": {
    "bookFlight": {
      "description": "Books a flight (multi turn).",
      "type": "event",
      "name": "BookFlight",
      "value": {
        "$ref": "#/definitions/bookingInfo"
      },
      "resultValue": {
        "$ref": "#/definitions/bookingInfo"
      }
    },
    "getWeather": {
      "description": "Retrieves and returns the weather for the user's location.",
      "type": "event",
      "name": "GetWeather",
      "value": {
        "$ref": "#/definitions/location"
      },
      "resultValue": {
        "$ref": "#/definitions/weatherReport"
      }
    },
    "passthroughMessage": {
      "type": "message",
      "description": "Receives the user's utterance and attempts to resolve it using the skill's LUIS models.",
      "value": {
        "type": "object"
      }
    }
  },
  "definitions": {
    "bookingInfo": {
      "type": "object",
      "required": [
        "origin"
      ],
      "properties": {
        "origin": {
          "type": "string",
          "description": "This is the origin city for the flight."
        },
        "destination": {
          "type": "string",
          "description": "This is the destination city for the flight."
        },
        "travelDate": {
          "type": "string",
          "description": "The date for the flight in YYYY-MM-DD format."
        }
      }
    },
    "weatherReport": {
      "type": "array",
      "description": "Array of forecasts for the next week.",
      "items": [
        {
          "type": "string"
        }
      ]
    },
    "location": {
      "type": "object",
      "description": "Location metadata.",
      "properties": {
        "latitude": {
          "type": "number",
          "title": "Latitude"
        },
        "longitude": {
          "type": "number",
          "title": "Longitude"
        },
        "postalCode": {
          "type": "string",
          "title": "Postal code"
        }
      }
    }
  }
}

Схема манифеста навыка — это файл в формате JSON с описанием схемы манифеста навыков. Последняя версия схемы — версия 2.1.

Тестирование бота навыка

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

Скачайте и установите последнюю версию Bot Framework Emulator.

  1. Запустите бот навыка диалога и корневой бот диалога локально на компьютере. Если вам нужны инструкции, ознакомьтесь с файлом примера README для C#, JavaScript, JavaScript или Python.
  2. Примените эмулятор для тестирования бота.
    • Когда вы впервые присоединяетесь к беседе, бот отображает приветственное сообщение и спрашивает, какой навык вы хотите вызвать. В нашем примере бота навыков есть всего один навык.
    • Выберите DialogSkillBot.
  3. Затем бот просит выбрать действие для навыка. Выберите BookFlight.
    1. Навык начинает работу с действия book-flight, а вам нужно ответить на вопросы.
    2. Когда навык завершит работу, корневой бот отобразит сведения о резервировании и снова предложит вам вызвать навык.
  4. Снова выберите DialogSkillBot и BookFlight.
    1. Ответьте на первый вопрос, а затем введите cancel, чтобы отменить действие.
    2. Бот навыка завершит работу, не завершив действие, а потребитель снова предложит вам вызвать навык.

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

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

  • Потребитель навыка и все навыки, которые он потребляет, прямо или косвенно, должны работать.
  • Если боты работают локально и если у любого из ботов есть идентификатор приложения и пароль, все боты должны иметь действительные идентификаторы и пароли.
  • Если боты развернуты, узнайте, как отладить бота из любого канала с помощью ngrok.
  • Если некоторые боты выполняются локально, а некоторые развертываются, см. инструкции по отладке навыка или потребителя навыков.

В противном случае можно отладить потребителя навыка или навыка, как отладить другие боты. Дополнительные сведения см. в статье отладка бота и отладка с помощью эмулятора Bot Framework.

Дополнительная информация: