Uso de diálogos en una aptitud

SE APLICA A: SDK v4

En este artículo se muestra cómo crear una aptitud que admita varias acciones. Admite estas acciones mediante diálogos. El diálogo principal recibe la entrada inicial del consumidor de aptitudes y, a continuación, inicia la acción adecuada. Para más información sobre cómo implementar el consumidor de aptitudes en el código de ejemplo asociado, vea cómo consumir una aptitud mediante diálogos.

En este artículo se da por supuesto que ya está familiarizado con la creación de aptitudes. Para más información sobre cómo crear un bot de aptitudes en general, consulte cómo implementar una aptitud.

Nota:

Los SDK de JavaScript, C# y Python de Bot Framework seguirán siendo compatibles, pero el SDK de Java se va a retirar con la compatibilidad final a largo plazo que finaliza en noviembre de 2023.

Los bots existentes creados con el SDK de Java seguirán funcionando.

Para la creación de nuevos bots, considera el uso de Power Virtual Agents y lee sobre cómo elegir la solución de bot de chat adecuada.

Para obtener más información, consulta El futuro de la creación de bots.

Requisitos previos

Nota:

Reconocimiento del lenguaje (LUIS) se retirará el 1 de octubre de 2025. A partir del 1 de abril de 2023, no podrás crear nuevos recursos de LUIS. Hay disponible una versión más reciente de las funcionalidades de reconocimiento del lenguaje como parte del Lenguaje de Azure AI.

Reconocimiento del lenguaje conversacional (CLU), una característica del lenguaje de Azure AI, es la versión actualizada de LUIS. Para obtener más información sobre la compatibilidad con reconocimiento del lenguaje en el SDK de Bot Framework, consulte Reconocimiento del lenguaje natural.

Acerca de este ejemplo

El ejemplo skills skillDialog incluye proyectos para dos bots:

  • El bot raíz del diálogo, que utiliza una clase de diálogo de aptitud para consumir una aptitud.
  • El bot de aptitud de diálogo, que utiliza un diálogo para controlar las actividades que provienen de los consumidores de aptitudes. Esta aptitud es una adaptación del ejemplo de bot principal. Para más información sobre el bot principal, consulte cómo incorporar reconocimiento de lenguaje natural a un bot.

Este artículo se centra en cómo usar un diálogo dentro de un bot de aptitudes para administrar varias acciones.

Para más información sobre el bot de consumidor de aptitudes, consulte cómo consumir una aptitud mediante diálogos.

Recursos

Para los bots implementados, la autenticación entre bots requiere que cada bot participante tenga una identidad válida. Sin embargo, puede probar las aptitudes y los consumidores de aptitudes localmente con Bot Framework Emulator sin información de identidad.

Para que la aptitud esté disponible para los bots orientados al usuario, registre la aptitud en Azure. Para más información, vea cómo registrar un bot con Azure Bot Service.

Opcionalmente, el bot de aptitudes puede utilizar un modelo LUIS de reserva de vuelos. Para utilizar este modelo, use el archivo CognitiveModels/FlightBooking.json para crear, entrenar y publicar el modelo de LUIS.

Configuración de aplicaciones

  1. Opcionalmente, agregue la información de identidad de la aptitud al archivo de configuración de la aptitud. (Si la aptitud o el consumidor de aptitudes especifican una identidad, ambos deben especificarla.)

  2. Si usa el modelo de LUIS, agregue el identificador de aplicación, la clave de API y el nombre de host de API de LUIS.

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": [ "*" ]
}

Lógica del enrutamiento de actividades

La aptitud admite un par de características diferentes. Puede reservar un vuelo o conocer el tiempo de una ciudad. Además, si recibe un mensaje fuera de estos contextos, puede usar LUIS para intentar interpretar el mensaje. El manifiesto de la aptitud describe estas acciones, sus parámetros de entrada y salida, y los puntos de conexión de la aptitud. Cabe destacar que la aptitud puede controlar un evento "BookFlight" o "GetWeather". También puede controlar las actividades de mensajes.

La aptitud define un diálogo de enrutamiento de actividades que se usa para seleccionar la acción que se va a iniciar, en función de la actividad de entrada inicial del consumidor de aptitudes. Si se proporciona, el modelo de LUIS puede reconocer intenciones de reserva de vuelo y de consulta del tiempo en un mensaje inicial.

La acción de reserva de vuelo es un proceso que consta de varios pasos y se implementa como un diálogo independiente. Una vez iniciada la acción, este diálogo controla las actividades entrantes. La acción de consulta del tiempo tiene una lógica de marcador de posición que se reemplazaría en un bot totalmente implementado.

El diálogo de enrutamiento de actividades incluye código para:

Los diálogos que se usan en la aptitud heredan de la clase de diálogo de componente. Para más información sobre los diálogos de componente, consulte cómo administrar la complejidad de los diálogos.

Inicialización del diálogo

El diálogo de enrutamiento de actividades incluye un diálogo secundario para reservar un vuelo. El diálogo en cascada principal tiene un paso que iniciará una acción en función de la actividad inicial recibida.

También acepta un reconocedor de LUIS. Si se inicializa este reconocedor, el diálogo lo utilizará para interpretar la intención de una actividad de mensaje inicial.

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);
}

Procesamiento de una actividad inicial

En el primer y único paso del diálogo de cascada principal, la aptitud comprueba el tipo de actividad entrante.

  • Las actividades de eventos se reenvían a un controlador de actividad según el evento, que inicia la acción adecuada según el nombre del evento.
  • Las actividades de mensajes se reenvían a un controlador de la actividad según el mensaje, que realiza un procesamiento adicional antes de decidir qué hacer.

Si la aptitud no reconoce el tipo de la actividad entrante o el nombre del evento, envía un mensaje de error y finaliza.

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);
    }
}

Control de las actividades de mensajes

Si el reconocedor de LUIS está configurado, la aptitud llama a LUIS y, a continuación, inicia una acción basada en la intención. Si el reconocedor de LUIS no está configurado o no se admite la intención, la aptitud envía un mensaje de error y finaliza.

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);
}

Inicio de una acción de varios pasos

La acción de reserva de vuelo inicia un diálogo de varios pasos para obtener los detalles de la reserva del usuario.

La acción de obtención del tiempo no está implementada. Actualmente, envía un mensaje de marcador de posición y finaliza.

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);
}

Devolución de un resultado

La aptitud inicia un diálogo de reserva para la acción de reserva de vuelo. Como el diálogo de enrutamiento de actividades tiene un solo paso, cuando el diálogo de reserva finaliza, el diálogo de enrutamiento de actividades también finaliza y el resultado del diálogo de reserva se convierte en el resultado del diálogo de enrutamiento de actividades.

La acción de obtención del tiempo simplemente finaliza sin establecer un valor devuelto.

Cancelación de una acción de varios pasos

El diálogo de reserva y su diálogo de resolución de fecha secundario derivan del diálogo base de cancelación y ayuda, que comprueba los mensajes del usuario.

  • Cuando se elige "ayuda" o "?", se muestra un mensaje de ayuda y el flujo de conversación continúa en el siguiente turno.
  • Cuando se elige "cancelar" o "salir", se cancelan todos los diálogos y la aptitud finaliza.

Para más información, consulte cómo controlar las interrupciones del usuario.

Registro de servicios

Los servicios necesarios para esta aptitud son los mismos que los necesarios para un bot de aptitudes en general. Vea cómo implementar una aptitud para obtener una explicación de los servicios necesarios.

Manifiesto de aptitud

Un manifiesto de aptitud es un archivo JSON que describe las actividades que puede realizar la aptitud, sus parámetros de entrada y salida, y sus puntos de conexión. El manifiesto contiene la información que necesita para tener acceso a la aptitud desde otro bot.

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"
        }
      }
    }
  }
}

El esquema del manifiesto de aptitud es un archivo JSON que describe el esquema del manifiesto de aptitud. La versión más reciente es v2.1.

Prueba del bot de aptitudes

Puede probar la aptitud en el emulador con el consumidor de aptitudes. Para ello, debe ejecutar los bots de aptitudes y de consumidor de aptitudes al mismo tiempo. Vea cómo usar un diálogo para consumir una aptitud para obtener información sobre cómo configurar la aptitud.

Descargue e instale la versión más reciente de Bot Framework Emulator.

  1. Ejecute el bot de aptitud de diálogo y el bot raíz de diálogo localmente en su máquina. Si necesita instrucciones, consulte el archivo de ejemplo README de C#, JavaScript, Java o Python.
  2. Use el emulador para probar el bot.
    • La primera vez que se une a la conversación, el bot muestra un mensaje de bienvenida y le pregunta a qué aptitud le gustaría llamar. El bot de aptitud de este ejemplo tiene solo una aptitud.
    • Seleccione DialogSkillBot.
  3. A continuación, el bot le pide que elija una acción para la aptitud. Elija "BookFlight".
    1. La aptitud comienza la acción de reserva de vuelo; responda a los avisos.
    2. Cuando la aptitud se completa, el bot raíz muestra los detalles de la reserva antes de volver a solicitar la aptitud a la que le gustaría llamar.
  4. Vuelva a seleccionar DialogSkillBot y "BookFlight".
    1. Responda a la primera solicitud y, a continuación, escriba "Cancelar" para cancelar la acción.
    2. El bot de aptitud finaliza sin completar la acción y el consumidor solicita la aptitud a la que le gustaría llamar.

Más sobre la depuración

Dado que se autentica el tráfico entre aptitudes y consumidores de aptitudes, hay pasos adicionales al depurar estos bots.

  • El consumidor de aptitudes y todas las aptitudes que consume, directa o indirectamente, deben ejecutarse.
  • Si los bots se ejecutan localmente y si alguno de los bots tiene un identificador de aplicación y una contraseña, todos los bots deben tener identificadores y contraseñas válidos.
  • Si todos los bots están implementados, consulte cómo depurar un bot desde cualquier canal mediante ngrok.
  • Si algunos de los bots se ejecutan localmente y algunos se implementan, consulte cómo depurar una aptitud o consumidor de aptitudes.

De lo contrario, puede depurar un consumidor de aptitudes o una aptitud como depurar otros bots. Para obtener más información, consulte Depuración de un bot y Depuración con Bot Framework Emulator.

Información adicional