通过对话使用技能

适用于:SDK v4

本文演示如何在技能使用者内使用技能对话。 技能对话将活动从父机器人发布到技能机器人,并将技能响应返回给用户。 此使用者访问的技能机器人可以处理消息和事件活动。 如需示例技能清单并了解如何实现技能,请参阅如何在技能内使用对话

有关使用对话以外的技能机器人的信息,请参阅如何实现技能使用者

注意

Bot Framework JavaScript、C# 和 Python SDK 将继续受支持,但 Java SDK 即将停用,最终长期支持将于 2023 年 11 月结束。

使用 Java SDK 构建的现有机器人将继续正常运行。

要生成新的机器人,请考虑使用 Microsoft Copilot Studio 并阅读选择正确的助理解决方案

有关详细信息,请参阅机器人构建的未来

先决条件

关于此示例

skills skillDialog 示例包含下面的两个机器人的项目:

  • 对话根机器人,通过技能对话类使用技能。
  • 对话技能机器人,使用对话处理技能使用者中的活动。

本文重点介绍如何在根机器人中使用技能对话类来管理技能、发送消息和事件活动以及取消技能。

有关创建技能使用者的其他方面的信息,请参阅如何实现技能使用者

有关对话技能机器人的信息,请参阅如何在技能内使用对话

资源

对于部署的机器人,机器人到机器人身份验证要求每个参与的机器人都有有效的标识。 但是,可以使用 Bot Framework Emulator 在本地测试技能和技能使用者,而无需提供标识信息。

应用程序配置

  1. (可选)将根机器人的标识信息添加到配置文件。
  2. 添加技能主机终结点(服务或回调 URL),技能应将该主机回复给技能使用者。
  3. 针对技能使用者将要使用的每项技能添加一个条目。 每个条目包含:
    • 一个 ID,供技能使用者用来标识每项技能。
    • (可选)技能机器人的应用或客户端 ID。
    • 技能的消息传送终结点。

注意

如果技能或技能使用者指定标识,则两者都必须指定。

DialogRootBot\appsettings.json

(可选)添加根机器人的标识信息,并为回显技能机器人添加应用或客户端 ID 到 BotFrameworkSkills 数组。

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

  "SkillHostEndpoint": "http://localhost:3978/api/skills/",
  "BotFrameworkSkills": [
    {
      "Id": "DialogSkillBot",
      "AppId": "",
      "SkillEndpoint": "http://localhost:39783/api/messages"
    }
  ]
}

对话逻辑

机器人的主对话包括此机器人使用的各个技能的技能对话。 技能对话可通过各种技能相关对象为你管理技能,如技能客户端和技能对话 ID 工厂对象。 主对话还演示了如何取消基于用户输入的技能(通过技能对话)。

此机器人使用的技能支持两种不同的功能。 它可以预订航班或获取城市的天气状况。 此外,如果它在这些上下文中的任何一个之外收到消息,并且配置了 LUIS 识别器,它将尝试解释用户的意图。

注意

语言理解 (LUIS) 将于 2025 年 10 月 1 日停用。 从 2023 年 4 月 1 日开始,将无法创建新的 LUIS 资源。 语言理解的较新版本现已作为 Azure AI 语言的一部分提供。

对话语言理解(CLU)是 Azure AI 语言的一项功能,是 LUIS 的更新版本。 有关 Bot Framework SDK 中的语言理解支持的详细信息,请参阅自然语言理解

技能清单(C#JavaScriptJavaPython)描述了技能可以执行的操作、其输入和输出参数以及技能的终结点。 注意,技能可以处理“BookFlight”或“GetWeather”事件。 它还可以处理消息。

主对话包含以下代码,可:

主对话继承自组件对话类。 有关组件对话的详细信息,请参阅如何管理对话复杂性

初始化主对话

主对话包含对话(用于管理技能以外的对话流)和技能对话(用于管理技能)。 瀑布图包含以下步骤,接下来的几节将对此进行详细介绍。

  1. 提示用户选择要使用的技能。 (根机器人使用 1 种技能。)
  2. 提示用户选择要用于此技能的操作。 (技能机器人定义 3 项操作。)
  3. 基于所选操作,从初始活动启用选择的技能。
  4. 技能完成后,将显示结果(如果有)。 然后,重启瀑布图。

DialogRootBot\Dialogs\MainDialog.cs

MainDialog 类从 ComponentDialog 派生。 除了对话状态外,该对话还需要根机器人的标识和对技能对话 ID 中心、技能 HTTP 客户端和技能配置对象的引用。

对话构造函数检查其输入参数、添加技能对话、添加提示和瀑布图对话,以便管理技能以外的对话流,并创建属性访问器以跟踪活动技能(如果有)。

构造函数调用 AddSkillDialogs(帮助程序方法),用于在从配置文件读取到 SkillsConfiguration 对象时,为配置文件中包含的每项技能创建 SkillDialog

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

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

选择技能

在第一步中,主对话会提示用户选择他们想要调用的技能,并使用“SkillPrompt”选项提示获取答案。 (此机器人仅定义一种技能。)

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

选择技能操作

在下一步中,主对话:

  1. 保存用户选择的技能相关信息。
  2. 提示用户选择他们想要使用的技能操作,并使用“SkillActionPrompt”选项提示获取答案。
    • 它使用帮助程序方法获取要从中选择的操作列表。
    • 如果用户的输入不匹配其中一个选项,则与此提示相关联的提示验证程序将默认为向这个技能发送一条消息。

此机器人中包含的选项有助于测试为此技能定义的操作。 更常见的情况是,可以从技能清单中读取选项,并根据该列表向用户提供相应的选项。

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

启用技能

在下一步中,主对话:

  1. 检索用户选择的技能和技能活动的相关信息。
  2. 使用帮助程序方法创建最初发送到技能的活动。
  3. 创建用于启用技能对话的对话选项。 其中包括要发送的初始活动。
  4. 在调用技能之前保存状态。 (这是必需的,因为技能响应可能会成为技能使用者的不同实例。)
  5. 开始技能对话,传入要调用的技能 ID 以及用于调用它的选项。

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

汇总技能结果

在上一步中,主对话:

  1. 如果技能返回值,则将结果显示给用户。
  2. 从对话状态中清除活动的技能。
  3. 从对话状态中删除活动的技能属性。
  4. 自行重启(主对话)。

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

允许用户取消技能

主对话将覆盖“在继续对话上”方法的默认行为,以允许用户取消当前技能(如果有)。 在方法内:

  • 如果有活动的技能,并且用户发送“中止”消息,则取消所有对话,并使主对话排队,以便从头开始重新启动。
  • 然后,调用“在继续对话上”方法的基实现来继续处理当前轮。

DialogRootBot\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.OrdinalIgnoreCase))
    {
        // 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);
}

活动处理程序逻辑

由于每轮的技能逻辑均由主对话处理,因此,活动处理程序看起来与其他对话示例的处理程序非常类似。

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

服务注册

使用技能对话所需的服务与一般技能使用者所需的服务相同。 有关所需服务的讨论,请参阅如何实现技能使用者

测试根机器人

可以在 Emulator 中测试技能使用者,就像它是普通机器人一样;但是,你需要同时运行技能和技能使用者机器人。 有关如何配置技能的信息,请参阅如何在技能内使用对话

下载并安装最新的 Bot Framework Emulator

  1. 在计算机上以本地方式运行对话技能机器人和对话根机器人。 如需说明,请参阅 C#JavaScriptJavaPython 的示例的 README
  2. 使用模拟器测试机器人。
    • 第一次加入对话时,机器人会显示欢迎消息,并询问你要调用的技能。 此示例的技能机器人只包含一项技能。
    • 选择“DialogSkillBot”。
  3. 然后,机器人要求你为技能选择一项操作。 选择“BookFlight”。
    1. 回答提示。
    2. 技能完成后,根机器人会显示预订详细信息,然后会再次提示你要调用的技能。
  4. 再次选择“DialogSkillBot”和“BookFlight”。
    1. 回答第一条提示,然后输入“abort”以使技能中断。
    2. 根机器人取消了技能,并提示你要调用的技能。

有关调试的更多信息

由于技能与技能使用者之间的流量已经过身份验证,因此调试此类机器人时会执行额外的步骤。

  • 技能使用者及其直接或间接使用的所有技能都必须处于运行中。
  • 如果机器人在本地运行,并且任何机器人有应用 ID 和密码,则所有机器人都必须具有有效的 ID 和密码。
  • 如果所有机器人都已部署,请参阅如何使用 devtunnel 从任何通道调试机器人
  • 如果某些机器人在本地运行,并且部署了一些机器人,请参阅如何调试技能或技能使用者

或者,可以像调试其他机器人一样调试技能使用者或技能。 有关详细信息,请参阅调试机器人使用 Bot Framework Emulator 执行调试

其他信息

有关如何实现一般技能使用者,请参阅如何实现技能使用者