Skip to main content

聊天机器人项目中使用Dialog实现循环任务

分类:  Azure机器人 标签:  #Azure Bot Framework SDK #Azure Bot Service #机器人 发布于: 2023-08-07 23:08:04

我们之前有三篇文章已经讲述了Dialog的基本使用和基本的原理以及应用场景,我们这一篇看看如何利用Dialog实现循环任务。阅读本章前,需要使用如下的文章先创建好项目:

请根据上述三篇文章一步一步完成,就有了一个实现了Dialog,并且实现了主线任务以及分支任务的代码了。

什么是循环任务?例如在聊天的会话中,我们需要对某些场景进行分页,显示更多的数据,或者需要用户重复的确认某一件合同,收集不同的信息,等等都可以实现为一个循环任务。我们本例继续延续之前的场景,当用户年龄大于35岁,询问他是否会购买保险,如果用户选择了购买保险,那么我们需要给用户看一个3页的保险说明书,案例中使用循环任务实现了从第一页保险说明循环到最后一页退出,或者用户选择特定的按钮退出循环。

开始之前一定要按照上面的三遍文章一步一步的创建好项目CoreBot, 我们现在继续实现循环任务。

开始代码

在已有的CoreBot项目中,我们已经有一个Dialog - MainDialog.cs, 为了完成循环任务我们需要另外一个新的Dialog, 我们可以命名为:InsuranceDialog, 专门用于处理保险的循环任务。

先给代码,再解释:

Dialogs\InsuranceDialog.cs:

using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs.Choices;

namespace CoreBot.Dialogs;

public class InsuranceDialog : ComponentDialog
{

    public InsuranceDialog()  : base(nameof(InsuranceDialog))
    {
        AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));

        AddDialog(new WaterfallDialog(
            nameof(WaterfallDialog),
            new WaterfallStep[]
            {
                ReadInsuranceDetail,
                CheckReadPage
            }
        ));

        InitialDialogId = nameof(WaterfallDialog);

    }


    public async Task<DialogTurnResult> ReadInsuranceDetail(WaterfallStepContext waterfallStepContext, CancellationToken cancellationToken)
    {

        waterfallStepContext.Values["PageOptions"] = null;

        var pageOptions = waterfallStepContext.Options as PageOptions ?? new PageOptions()
        {
            TotalPage = 3,
            CurrentPage = 1
        };


        var reply = waterfallStepContext.Context.Activity.CreateReply();

        reply.Text = $"保险协议,您当前阅读的在{pageOptions.CurrentPage}页";

        await waterfallStepContext.Context.SendActivityAsync(reply,cancellationToken);

        var options = new PromptOptions()
        {
            Prompt = MessageFactory.Text("请阅读保险协议"),
            RetryPrompt = MessageFactory.Text("请仔细阅读保险协议"),
            Choices = GetChoices(),
        };

        waterfallStepContext.Values["PageOptions"] = pageOptions;

        return await waterfallStepContext.PromptAsync(nameof(ChoicePrompt), options, cancellationToken);

    }


    public async Task<DialogTurnResult> CheckReadPage(WaterfallStepContext waterfallStepContext, CancellationToken cancellationToken)
    {
        var checkValue = ((FoundChoice)waterfallStepContext.Result).Value;

        if ( checkValue.Equals("下页"))
        {
            PageOptions pageOptions = (PageOptions)waterfallStepContext.Values["PageOptions"];

            if (pageOptions.TotalPage <= pageOptions.CurrentPage)
            {
                //直接退出了.
                var reply = waterfallStepContext.Context.Activity.CreateReply();
                reply.Text = "已经浏览完所有搜索结果。";

                await waterfallStepContext.Context.SendActivityAsync(reply, cancellationToken);
                return await waterfallStepContext.EndDialogAsync(null, cancellationToken);
            }
            else
            {
                pageOptions.CurrentPage += 1;
                return await waterfallStepContext.ReplaceDialogAsync(nameof(InsuranceDialog), pageOptions, cancellationToken);
            }
        }
        else
        {
            var reply = waterfallStepContext.Context.Activity.CreateReply();
            reply.Text = "退出浏览搜索结果。";
            await waterfallStepContext.Context.SendActivityAsync(reply, cancellationToken);
            return await waterfallStepContext.EndDialogAsync(null, cancellationToken);
        }
    }

    private IList<Choice> GetChoices()
    {
        var cardOptions = new List<Choice>()
            {
                new Choice() { Value = "下页", Synonyms = new List<string>() { "下一页" } },
                new Choice() { Value = "退出", Synonyms = new List<string>() { "退出"  } },
            };

        return cardOptions;
    }

}

代码比较长,但是核心的价值主要是两个:

  • 通过waterfallStepContext.Values数组在不同的Turn之间传递状态和状态数据。
  • 通过方法waterfallStepContext.ReplaceDialogAsync来显式的调用需要的Dialog, 注意根据DialogId来调用,也就是在构造函数里输入的nameof()

其他的更改

更改一下`MainDialog.cs':

更改构造函数,添加新的Dialog:

public class MainDialog: ComponentDialog
{
    public MainDialog() : base(nameof(MainDialog))
    {
        AddDialog(new TextPrompt("get-nick-name", NickNameNotNull));
        AddDialog(new NumberPrompt<int>("get-age"));
        AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));

        AddDialog(new InsuranceDialog());

        AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]{
            GetNickName,
            GetAge,
            SetAgeForHealth,
            ShowNickNameAndAge,
        }));

        InitialDialogId = nameof(WaterfallDialog);
    }

更改方法ShowNickNameAndAge:

    public async Task<DialogTurnResult> ShowNickNameAndAge(WaterfallStepContext waterfallStepContext, CancellationToken cancellationToken)
    {
        var Age = Convert.ToInt32(waterfallStepContext.Values["Age"]);
        var NickName = waterfallStepContext.Values["NickName"];


        var reply = waterfallStepContext.Context.Activity.CreateReply();
        
        reply.Text = $"您好,您输入的昵称是:{NickName}, 您输入的年龄是: {Age}";

        if (Age > 35)
        {
            var choice = ((FoundChoice)waterfallStepContext.Result).Value;

            if (choice.Equals("购买保险"))
            {
                reply.Text = "您已经购买了保险。";

                return await waterfallStepContext.BeginDialogAsync(nameof(InsuranceDialog), null, cancellationToken);
            }
            
        }

        await waterfallStepContext.Context.SendActivityAsync(reply, cancellationToken);
        return await waterfallStepContext.EndDialogAsync(null, cancellationToken);

    }

主要是更改这一句:return await waterfallStepContext.BeginDialogAsync(nameof(InsuranceDialog), null, cancellationToken);, 开始调用下一个Dialog, 这样我们就完成了整个的代码更改。

您可以尝试运行一下,查看结果:

dotnet run