Uso de un diálogo para consumir una aptitudUse a dialog to consume a skill

se aplica a: SDK V4APPLIES TO: SDK v4

En este artículo se muestra cómo usar un diálogo de aptitud dentro de un consumidor de aptitudes.This article demonstrates how to use a skill dialog within a skill consumer. El diálogo de aptitud expone las actividades del bot primario al bot de aptitud y devuelve las respuestas de la aptitud al usuario.The skill dialog posts activities from the parent bot to the skill bot and returns the skill responses to the user. El bot de aptitud al que tiene acceso este consumidor puede controlar tanto las actividades de mensaje como las actividades de evento.The skill bot accessed by this consumer can handle both message and event activities. Para ver un ejemplo de un manifiesto de aptitud e información sobre cómo implementar la aptitud, consulte cómo usar diálogos dentro de una aptitud.For a sample skill manifest and information about implementing the skill, see how to use dialogs within a skill.

Para obtener información sobre el uso de un bot de aptitud fuera de los diálogos, vea cómo implementar un consumidor de aptitudes.For information about using a skill bot outside of dialogs, see how to implement a skill consumer.

PrerrequisitosPrerequisites

Nota

A partir de la versión 4.11, no necesita un identificador de aplicación y una contraseña para probar un consumidor de aptitudes localmente en el emulador.Starting with version 4.11, you do not need an app ID and password to test a skill consumer locally in the Emulator. Todavía se necesita una suscripción de Azure para implementar el consumidor en Azure o para consumir una aptitud implementada.An Azure subscription is still required to deploy your consumer to Azure or to consume a deployed skill.

Acerca de este ejemploAbout this sample

El ejemplo skills skillDialog incluye proyectos para dos bots:The skills skillDialog sample includes projects for two bots:

  • El bot raíz del diálogo, que utiliza una clase de diálogo de aptitud para consumir una aptitud.The dialog root bot, which uses a skill dialog class to consume a skill.
  • El bot de aptitud de diálogo, que utiliza un diálogo para controlar las actividades que provienen de los consumidores de aptitudes.The dialog skill bot, which uses a dialog to handle activities coming from skill consumers.

Este artículo se centra en el uso de una clase de diálogo de aptitud en un bot raíz para administrar la aptitud, enviar actividades de mensajes y eventos y cancelar la aptitud.This article focuses on how to use a skill dialog class in a root bot to manage the skill, to send message and event activities and to cancel the skill.

Para obtener información sobre otros aspectos de la creación de un consumidor de aptitudes, consulte cómo implementar un consumidor de aptitudes.For information about other aspects of creating a skill consumer, see how to implement a skill consumer.

Para obtener información sobre el bot de aptitudes de diálogo, consulte cómo usar diálogos dentro de una aptitud.For information about the dialog skill bot, see how to use dialogs within a skill.

RecursosResources

Para los bots implementados, la autenticación de bot a bot requiere que cada bot participante tenga un identificador de aplicación y una contraseña válidos.For deployed bots, bot-to-bot authentication requires that each participating bot has a valid app ID and password. Sin embargo, puede probar las aptitudes y los consumidores de aptitudes localmente con el emulador sin un identificador de aplicación y una contraseña.However, you can test skills and skill consumers locally with the Emulator without an app ID and password.

Configuración de aplicaciónApplication configuration

  1. Opcionalmente, agregue el identificador de aplicación y la contraseña del bot raíz al archivo de configuración.Optionally, add the root bot's app ID and password to the config file.
  2. Agregue el punto de conexión del host de aptitud (el servicio o la dirección URL de devolución de llamada) al que las aptitudes deben responder al consumidor de aptitudes.Add the skill host endpoint (the service or callback URL) to which the skills should reply to the skill consumer.
  3. Agregue una entrada para cada aptitud que usará el consumidor de aptitudes.Add an entry for each skill the skill consumer will use. Cada entrada incluye:Each entry includes:
    • Un identificador que el consumidor de aptitudes usará para identificar cada aptitud.An ID the skill consumer will use to identify each skill.
    • Opcionalmente, el identificador de aplicación de la aptitud.Optionally, the skill's app ID.
    • El punto de conexión de mensajería de la aptitud.The skill's messaging endpoint.

Nota

Si el consumidor de aptitudes o aptitudes usa un identificador de aplicación y una contraseña, ambos deben hacerlo.If either the skill or skill consumer uses an app ID and password, both must.

DialogRootBot\appsettings.jsonDialogRootBot\appsettings.json

Opcionalmente, agregue el identificador de aplicación y la contraseña del bot raíz y agregue el identificador de la aplicación para el bot de aptitud de eco a la BotFrameworkSkills matriz.Optionally, add the root bot's app ID and password and add the app ID for the echo skill bot to the BotFrameworkSkills array.

{
  "MicrosoftAppId": "TODO: Add here the App ID for the bot",
  "MicrosoftAppPassword": "TODO: Add here the password for the bot",

  "SkillHostEndpoint": "http://localhost:3978/api/skills/",
  "BotFrameworkSkills": [
    {
      "Id": "DialogSkillBot",
      "AppId": "TODO: Add here the App ID for the skill",
      "SkillEndpoint": "http://localhost:39783/api/messages"
    }
  ]
}

Lógica del diálogoDialog logic

El diálogo principal del bot incluye un diálogo de aptitud para cada aptitud que este bot consume.The bot's main dialog includes a skill dialog for each skill this bot consumes. El diálogo de aptitud administra la aptitud a través de los distintos objetos relacionados con las aptitudes, como el cliente de aptitudes y los objetos del generador de identificadores de conversación de aptitudes.The skill dialog manages the skill through the various skill-related objects for you, such as the skill client and the skill conversation ID factory objects. En el diálogo principal también se muestra cómo cancelar la aptitud (mediante el diálogo de aptitudes) en función de los datos proporcionados por el usuario.The main dialog also demonstrates how to cancel the skill (through the skill dialog) based on user input.

La aptitud que este bot usa es compatible con un par de características diferentes.The skill this bot uses supports a couple different features. Puede reservar un vuelo o conocer el tiempo de una ciudad.It can book a flight or get the weather for a city. Además, si recibe un mensaje fuera de estos contextos y se configura un reconocedor de LUIS, intenta interpretar la intención del usuario.In addition, if it receives a message outside either of these contexts and a LUIS recognizer is configured, it attempts to interpret the user's intent.

El manifiesto de aptitud (C#, JavaScript, Python) describe las acciones que puede realizar la aptitud, sus parámetros de entrada y salida y sus puntos de conexión.The skill manifest (C#, JavaScript, Python) describes the actions the skill can perform, its input and output parameters, and the skill's endpoints. Cabe destacar que la aptitud puede controlar un evento "BookFlight" o "GetWeather".Of note, the skill can handle a "BookFlight" or "GetWeather" event. También puede controlar los mensajes.It can also handle messages.

El diálogo principal incluye código para:The main dialog includes code to:

El diálogo principal hereda de la clase del diálogo de componente.The main dialog inherits from the component dialog class. Para obtener más información sobre los diálogos de componentes, vea cómo administrar la complejidad del diálogo.For more about component dialogs, see how to manage dialog complexity.

Inicialización del diálogo principalInitialize the main dialog

El diálogo principal incluye diálogos (para administrar el flujo de conversación fuera de la aptitud) y diálogos de aptitud (para administrar las aptitudes).The main dialog includes dialogs (for managing conversation flow outside the skill) and a skill dialogs (for managing the skills). La cascada incluye los pasos siguientes, que se describen con más detalle en las secciones que figuran a continuación.The waterfall includes the following steps, described in more detail in the next few sections.

  1. Pida al usuario que seleccione la aptitud que se va a usar.Prompt the user to select the skill to use. (El bot raíz consume una aptitud).(The root bot consumes 1 skill.)
  2. Pida al usuario que seleccione la acción que se va a usar para esa aptitud.Prompt the user to select the action to use for that skill. (El bot de aptitud define tres acciones).(The skill bot defines 3 actions.)
  3. Inicie la aptitud elegida con una actividad inicial en función de la acción elegida.Start the chosen skill with an initial activity based on the chosen action.
  4. Una vez completada la aptitud, muestre los resultados, si los hay.Once the skill completes, display the results, if any. A continuación, reinicie la aptitud.Then, restart the waterfall.

DialogRootBot\Dialogs\MainDialog.csDialogRootBot\Dialogs\MainDialog.cs

La clase MainDialog deriva de ComponentDialog.The MainDialog class derives from ComponentDialog. Además del estado de la conversación, el diálogo necesita el identificador de la aplicación del bot raíz y las referencias al generador de identificadores de conversación de aptitudes, el cliente HTTP de aptitud y los objetos de configuración de aptitud.In addition to conversation state, the dialog needs the root bot's app ID and references to the skill conversation ID factory, the skill HTTP client, and the skills configuration objects.

El constructor del diálogo comprueba sus parámetros de entrada, agrega diálogos de aptitud, agrega una solicitud y diálogos en cascada para administrar el flujo de conversación fuera de la aptitud y crea un descriptor de acceso de propiedad para realizar el seguimiento de la aptitud activa, si existe.The dialog constructor checks its input parameters, adds skills dialogs, adds prompt and a waterfall dialogs for managing conversation flow outside the skill, and creates a property accessor for tracking the active skill, if any.

El constructor llama a AddSkillDialogs, un método auxiliar, para crear un SkillDialog para cada aptitud que se incluye en el archivo de configuración, como se lee de este en un objeto SkillsConfiguration.The constructor calls AddSkillDialogs, a helper method, to create a SkillDialog for each skill that is included in the configuration file, as read from the configuration file into a SkillsConfiguration object.

// Helper method that creates and adds SkillDialog instances for the configured skills.
private void AddSkillDialogs(ConversationState conversationState, SkillConversationIdFactoryBase conversationIdFactory, SkillHttpClient skillClient, SkillsConfiguration skillsConfig, string botId)
{
    foreach (var skillInfo in _skillsConfig.Skills.Values)
    {
        // Create the dialog options.
        var skillDialogOptions = new SkillDialogOptions
        {
            BotId = botId,
            ConversationIdFactory = conversationIdFactory,
            SkillClient = skillClient,
            SkillHostEndpoint = skillsConfig.SkillHostEndpoint,
            ConversationState = conversationState,
            Skill = skillInfo
        };

        // Add a SkillDialog for the selected skill.
        AddDialog(new SkillDialog(skillDialogOptions, skillInfo.Id));
    }
}

Selección de una aptitudSelect a skill

En el primer paso, el diálogo principal solicita al usuario a qué aptitud le gustaría llamar y utiliza el mensaje de opción "SkllPrompt" para obtener la respuesta.In its first step, the main dialog prompts the user for which skill they'd like to call, and uses the "SkllPrompt" choice prompt to get the answer. (Este bot solo define una aptitud).(This bot defines only one skill.)

DialogRootBot\Dialogs\MainDialog.csDialogRootBot\Dialogs\MainDialog.cs

// Render a prompt to select the skill to call.
private async Task<DialogTurnResult> SelectSkillStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    // Create the PromptOptions from the skill configuration which contain the list of configured skills.
    var messageText = stepContext.Options?.ToString() ?? "What skill would you like to call?";
    var repromptMessageText = "That was not a valid choice, please select a valid skill.";
    var options = new PromptOptions
    {
        Prompt = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput),
        RetryPrompt = MessageFactory.Text(repromptMessageText, repromptMessageText, InputHints.ExpectingInput),
        Choices = _skillsConfig.Skills.Select(skill => new Choice(skill.Value.Id)).ToList()
    };

    // Prompt the user to select a skill.
    return await stepContext.PromptAsync("SkillPrompt", options, cancellationToken);
}

Selección de una acción de aptitudSelect a skill action

En el paso siguiente, el diálogo principal:In the next step, the main dialog:

  1. Guarda la información sobre la aptitud que el usuario seleccionó.Saves information about the skill the user selected.
  2. Solicita al usuario qué acción de aptitud le gustaría usar y utiliza el mensaje de opción "SkillActionPrompt" para obtener la respuesta.Prompts the user for which skill action they'd like to use, and uses the "SkillActionPrompt" choice prompt to get the answer.
    • Utiliza un método auxiliar para obtener una lista de acciones entre las que elegir.It uses a helper method to get a list of actions to choose from.
    • El validador de solicitud asociado a esta solicitud enviará de forma predeterminada un mensaje a la aptitud si la entrada del usuario no coincide con una de las opciones.The prompt validator associated with this prompt will default to sending the skill a message if the user's input doesn't match one of the choices.

Las opciones incluidas en este bot ayudan a probar las acciones definidas para esta aptitud.The choices included in this bot help test the actions defined for this skill. Lo más habitual es que lea las opciones del manifiesto de la aptitud y presente opciones al usuario en función de esa lista.More typically, you would read the options from the skill's manifest, and present options to the user based on that list.

DialogRootBot\Dialogs\MainDialog.csDialogRootBot\Dialogs\MainDialog.cs

// Render a prompt to select the action for the skill.
private async Task<DialogTurnResult> SelectSkillActionStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    // Get the skill info based on the selected skill.
    var selectedSkillId = ((FoundChoice)stepContext.Result).Value;
    var selectedSkill = _skillsConfig.Skills.FirstOrDefault(s => s.Value.Id == selectedSkillId).Value;

    // Remember the skill selected by the user.
    stepContext.Values[_selectedSkillKey] = selectedSkill;

    // Create the PromptOptions with the actions supported by the selected skill.
    var messageText = $"Select an action # to send to **{selectedSkill.Id}** or just type in a message and it will be forwarded to the skill";
    var options = new PromptOptions
    {
        Prompt = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput),
        Choices = GetSkillActions(selectedSkill)
    };

    // Prompt the user to select a skill action.
    return await stepContext.PromptAsync("SkillActionPrompt", options, cancellationToken);
}
// Helper method to create Choice elements for the actions supported by the skill.
private IList<Choice> GetSkillActions(BotFrameworkSkill skill)
{
    // Note: the bot would probably render this by reading the skill manifest.
    // We are just using hardcoded skill actions here for simplicity.

    var choices = new List<Choice>();
    switch (skill.Id)
    {
        case "DialogSkillBot":
            choices.Add(new Choice(SkillActionBookFlight));
            choices.Add(new Choice(SkillActionBookFlightWithInputParameters));
            choices.Add(new Choice(SkillActionGetWeather));
            break;
    }

    return choices;
}
// This validator defaults to Message if the user doesn't select an existing option.
private Task<bool> SkillActionPromptValidator(PromptValidatorContext<FoundChoice> promptContext, CancellationToken cancellationToken)
{
    if (!promptContext.Recognized.Succeeded)
    {
        // Assume the user wants to send a message if an item in the list is not selected.
        promptContext.Recognized.Value = new FoundChoice { Value = SkillActionMessage };
    }

    return Task.FromResult(true);
}

Inicio de una aptitudStart a skill

En el paso siguiente, el diálogo principal:In the next step, the main dialog:

  1. Recupera información sobre la aptitud y la actividad de aptitud que el usuario ha seleccionado.Retrieves information about the skill and skill activity the user selected.
  2. Utiliza un método auxiliar para crear la actividad que se va a enviar inicialmente a la aptitud.Uses a helper method to create the activity to initially send to the skill.
  3. Crea las opciones de diálogo con las que se inicia el diálogo de aptitud.Creates the dialog options with which to start the skill dialog. Esto incluye la actividad inicial que se va a enviar.This includes the initial activity to send.
  4. Guarda el estado antes de llamar a la aptitud.Saves state before calling the skill. (Esto es necesario, ya que la respuesta de la aptitud puede llegar a una instancia diferente del consumidor de aptitud).(This is necessary, as the skill response might come to a different instance of the skill consumer.)
  5. Comienza el diálogo de aptitud, pasando el identificador de aptitud para llamar y las opciones con las que se llama.Begins the skill dialog, passing in the skill ID to call and the options with which to call it.

DialogRootBot\Dialogs\MainDialog.csDialogRootBot\Dialogs\MainDialog.cs

// Starts the SkillDialog based on the user's selections.
private async Task<DialogTurnResult> CallSkillActionStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    var selectedSkill = (BotFrameworkSkill)stepContext.Values[_selectedSkillKey];

    Activity skillActivity;
    switch (selectedSkill.Id)
    {
        case "DialogSkillBot":
            skillActivity = CreateDialogSkillBotActivity(((FoundChoice)stepContext.Result).Value, stepContext.Context);
            break;

        // We can add other case statements here if we support more than one skill.
        default:
            throw new Exception($"Unknown target skill id: {selectedSkill.Id}.");
    }

    // Create the BeginSkillDialogOptions and assign the activity to send.
    var skillDialogArgs = new BeginSkillDialogOptions { Activity = skillActivity };

    // Save active skill in state.
    await _activeSkillProperty.SetAsync(stepContext.Context, selectedSkill, cancellationToken);

    // Start the skillDialog instance with the arguments. 
    return await stepContext.BeginDialogAsync(selectedSkill.Id, skillDialogArgs, cancellationToken);
}

Resumen del resultado de la aptitudSummarize the skill result

En el último paso, el diálogo principal:In the last step, the main dialog:

  1. Si la aptitud devolvió un valor, muestra el resultado al usuario.If the skill returned a value, display the result to the user.
  2. Borra la aptitud activa del estado del diálogo.Clears the active skill from dialog state.
  3. Quita la propiedad de la aptitud activa del estado de conversación.Removes the active skill property from conversation state.
  4. Se reinicia (el diálogo principal).Restarts itself (the main dialog).

DialogRootBot\Dialogs\MainDialog.csDialogRootBot\Dialogs\MainDialog.cs

// The SkillDialog has ended, render the results (if any) and restart MainDialog.
private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    var activeSkill = await _activeSkillProperty.GetAsync(stepContext.Context, () => null, cancellationToken);

    // Check if the skill returned any results and display them.
    if (stepContext.Result != null)
    {
        var message = $"Skill \"{activeSkill.Id}\" invocation complete.";
        message += $" Result: {JsonConvert.SerializeObject(stepContext.Result)}";
        await stepContext.Context.SendActivityAsync(MessageFactory.Text(message, message, inputHint: InputHints.IgnoringInput), cancellationToken: cancellationToken);
    }

    // Clear the skill selected by the user.
    stepContext.Values[_selectedSkillKey] = null;

    // Clear active skill in state.
    await _activeSkillProperty.DeleteAsync(stepContext.Context, cancellationToken);

    // Restart the main dialog with a different message the second time around.
    return await stepContext.ReplaceDialogAsync(InitialDialogId, $"Done with \"{activeSkill.Id}\". \n\n What skill would you like to call?", cancellationToken);
}

Permiso para que el usuario cancele la aptitudAllow the user to cancel the skill

El diálogo principal invalida el comportamiento predeterminado del método on continue dialog para permitir al usuario cancelar la aptitud actual, si existe.The main dialog overrides the default behavior of the on continue dialog method to allow the user to cancel the current skill, if any. El método:The method:

  • Si hay una aptitud activa y el usuario envía un mensaje de anulación, cancela todos los diálogos y pone en cola el diálogo principal para reiniciar desde el principio.If there is an active skill and the user sends an "abort" message, cancel all dialogs and queue the main dialog to restart from the beginning.
  • A continuación, llama a la implementación base del método on continue dialog para continuar procesando el turno actual.Then, call the base implementation of the on continue dialog method to continue processing the current turn.

DialogRootBot\Dialogs\MainDialog.csDialogRootBot\Dialogs\MainDialog.cs

protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
{
    // This is an example on how to cancel a SkillDialog that is currently in progress from the parent bot.
    var activeSkill = await _activeSkillProperty.GetAsync(innerDc.Context, () => null, cancellationToken);
    var activity = innerDc.Context.Activity;
    if (activeSkill != null && activity.Type == ActivityTypes.Message && activity.Text.Equals("abort", StringComparison.CurrentCultureIgnoreCase))
    {
        // Cancel all dialogs when the user says abort.
        // The SkillDialog automatically sends an EndOfConversation message to the skill to let the
        // skill know that it needs to end its current dialogs, too.
        await innerDc.CancelAllDialogsAsync(cancellationToken);
        return await innerDc.ReplaceDialogAsync(InitialDialogId, "Canceled! \n\n What skill would you like to call?", cancellationToken);
    }

    return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}

Lógica del controlador de actividadesActivity handler logic

Dado que un diálogo principal controla la lógica de aptitudes para cada turno, el controlador de actividades se parece mucho a otros ejemplos de diálogo.Since skill logic for each turn is handled by a main dialog, the activity handler looks much like it would for other dialog samples.

DialogRootBot\Bots\RootBot.csDialogRootBot\Bots\RootBot.cs

public class RootBot<T> : ActivityHandler
    where T : Dialog
private readonly ConversationState _conversationState;
private readonly Dialog _mainDialog;

public RootBot(ConversationState conversationState, T mainDialog)
{
    _conversationState = conversationState;
    _mainDialog = mainDialog;
}
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
    if (turnContext.Activity.Type != ActivityTypes.ConversationUpdate)
    {
        // Run the Dialog with the Activity.
        await _mainDialog.RunAsync(turnContext, _conversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
    }
    else
    {
        // Let the base class handle the activity.
        await base.OnTurnAsync(turnContext, cancellationToken);
    }

    // Save any state changes that might have occurred during the turn.
    await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
}

Registro de serviciosService registration

Los servicios necesarios para usar un diálogo de aptitud son los mismos que los que se necesitan para un consumidor de aptitudes en general.The services needed to use a skill dialog are the same as those needed for a skill consumer in general. Vea cómo implementar un consumidor de aptitudes para obtener una explicación de los servicios necesarios.See how to implement a skill consumer for a discussion of the required services.

Prueba del bot raízTest the root bot

Puede probar el consumidor de aptitudes en el emulador como si fuera un bot normal; sin embargo, tiene que ejecutar los bots de aptitud y del consumidor de aptitudes al mismo tiempo.You can test the skill consumer in the Emulator as if it were a normal bot; however, you need to run both the skill and skill consumer bots at the same time. Vea cómo usar diálogos dentro de una aptitud para obtener más información sobre cómo configurar la aptitud.See how to use dialogs within a skill for information on how to configure the skill.

Descargue e instale la versión más reciente de Bot Framework Emulator.Download and install the latest 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.Run the dialog skill bot and dialog root bot locally on your machine. Si necesita instrucciones, consulte el archivo README (LÉAME) del ejemplo de C#, JavaScript o Python.If you need instructions, refer to the README file for the C#, JavaScript or Python sample.
  2. Use el emulador para probar el bot.Use the Emulator to test the 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.When you first join the conversation, the bot displays a welcome message and asks you what skill you would like to call. El bot de aptitud de este ejemplo tiene solo una aptitud.The skill bot for this sample has just one skill.
    • Seleccione DialogSkillBot.Select DialogSkillBot.
  3. El bot siguiente le pide que elija una acción para la aptitud.The bot next asks you to choose an action for the skill. Elija "BookFlight".Choose "BookFlight".
    1. Responda a las solicitudes.Answer the prompts.
    2. La aptitud se completa y el bot raíz muestra los detalles de la reserva antes de volver a solicitar la aptitud a la que le gustaría llamar.The skill completes, and the root bot displays the booking details before prompting again for the skill you'd like to call.
  4. Vuelva a seleccionar DialogSkillBot y "BookFlight".Select DialogSkillBot again and "BookFlight".
    1. Responda a la primera solicitud y escriba "abort" para interrumpir la aptitud.Answer the first prompt, then enter "abort" to interrupt the skill.
    2. El bot raíz cancela la aptitud y solicita la aptitud a la que le gustaría llamar.The root bot cancels the skill and prompts for the skill you'd like to call.

Información adicionalAdditional information

Consulte cómo implementar un consumidor de aptitudes para saber cómo implementar un consumidor de aptitudes en general.See how to implement a skill consumer for how to implement a skill consumer in general.