【问题标题】:Bot Framework: How do I stop a waterfall dialog which is waiting for a prompt response from re-prompting after an interruption?Bot Framework:如何停止瀑布对话框,该对话框正在等待中断后重新提示的及时响应?
【发布时间】:2021-05-18 04:53:42
【问题描述】:

预期功能

我创建了一项技能,旨在让用户开始与真人对话。为了做到这一点,他们会看到一个带有 1 个选项的提示:“连接”。当他们单击“连接”时,技能将移动到下一个瀑布步骤,该步骤执行代码以启动现场人员对话。我遇到的问题是,如果他们没有点击“连接”而是输入其他内容,那么他​​们得到的任何响应都会跟着相同的“连接”提示。这是可能发生的正常情况。他们可能不想与真人交谈,他们可能想继续与机器人交谈。

“连接”按钮实际上只是为了执行代码以开始对话。除了将它放在瀑布对话框中并让下一步是我想要执行的代码之外,我不知道有任何其他方法可以做到这一点。

复制

我在这个 repo 中创建了一个类似的例子:LoopingPrompt Repo

在我的仓库中有一个名为“ComposerMultiSkillDialog”的机器人

它包含一些技能和一些意图。

运行项目

为了运行这个项目,你需要 Visual Studio 和Bot Composer

打开解决方案文件:MultiSkillComposerBot.sln

确保 Bot.Skills 是启动项目。

按 F5 运行项目。它应该从端口 36352 开始。

然后打开Bot Composer到文件夹:ComposerMultiSkillDialog

启动 Bot,然后使用“打开网络聊天”

键入“loop”以查看带有“Handle Prompt”选项的提示

然后键入“呼叫技能1”以引起中断。

请注意,“呼叫技能1”完成后,再次出现“处理提示”的提示。

这将继续发生,因为用户说了更多的话,直到他们点击“处理提示”按钮。

目标

我们的目标是防止这种行为,并且只在第一次出现“句柄提示”。如果有中断,则不会再次出现。

尝试解决

到目前为止我尝试过的是:

  • 找到一种方法来添加“最大转数”,这是 Bot Composer 中的一个可用选项。尽管据我所知,这不是 stepContext.PromptAsync 中的可用选项

  • 当第二个“句柄提示”出现时,我通过代码进行了调试。代码通过控制器,进入 LoopingPromptDialog 的构造函数并进入 AddAdditionalDialogs 方法。我希望它会进入 PromptStepAsync ,在那里我可以放置某种计数器来检测是否已经达到并停止它,但 PromptStepAsync 永远不会被调用。我不确定“句柄提示”实际上是如何再次发送回聊天的。

  • 我无法让在“处理提示”之后调用的代码成为它自己的意图。如果用户输入了触发该意图的内容,我不想立即与现场人员开始聊天。所以不能有一个“连接”意图来开始与现场人聊天。

  • 我试图检查一个动作是否可以以某种方式链接到英雄卡响应,该响应将执行代码,但找不到类似的东西。

任何帮助表示赞赏!感谢您的宝贵时间。

【问题讨论】:

    标签: botframework


    【解决方案1】:

    我能够找到一个可行的解决方案来解决这个问题。更改的详细信息可以在此Commit

    中找到

    我不得不重写声明类型“Microsoft.BeginSkill”的默认功能。具体来说,我覆盖了“RepromptDialogAsync”方法。

    我采取的步骤:

    添加 BeginSkillNonRePrompting

    此类覆盖声明性类型“BeginSkill”的默认行为,即 .dialog 文件中的 $kind = Microsoft.BeginSkill。它检查技能的配置以确定是否应允许重新提示,如果不允许则阻止。

    using System;
    using System.Runtime.CompilerServices;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.Bot.Builder;
    using Microsoft.Bot.Builder.Dialogs;
    using Microsoft.Bot.Builder.Dialogs.Adaptive.Actions;
    using Microsoft.Bot.Builder.Skills;
    using Microsoft.Extensions.Configuration;
    
    namespace Microsoft.BotFramework.Composer.WebApp.Dialogs
    {
        public class BeginSkillNonRePrompting : BeginSkill
        {
            private readonly string _dialogOptionsStateKey = $"{typeof(BeginSkill).FullName}.DialogOptionsData";
    
            public BeginSkillNonRePrompting([CallerFilePath] string callerPath = "", [CallerLineNumber] int callerLine = 0)
                : base(callerPath, callerLine)
            {
            }
    
            public override Task RepromptDialogAsync(ITurnContext turnContext, DialogInstance instance, CancellationToken cancellationToken = default)
            {
                //get the skill endpoint - this contains the skill name from configuration - we need this to get the related skill:{SkillName}:disableReprompt value
                var skillEndpoint = ((SkillDialogOptions)instance.State["Microsoft.Bot.Builder.Dialogs.Adaptive.Actions.BeginSkill.DialogOptionsData"]).Skill.SkillEndpoint;
    
                //get IConfiguration so that we can use it to determine if this skill should be reprompting or not
                var config = turnContext.TurnState.Get<IConfiguration>() ?? throw new NullReferenceException("Unable to locate IConfiguration in HostContext");
    
                //the id looks like this:
                //BeginSkillNonRePrompting['=settings.skill['testLoopingPrompt'].msAppId','']
                //parse out the skill name
                var startingSearchValue = "=settings.skill['";
                var startOfSkillName = instance.Id.IndexOf(startingSearchValue) + startingSearchValue.Length;
                var endingOfSkillName = instance.Id.Substring(startOfSkillName).IndexOf("']");
                var skillName = instance.Id.Substring(startOfSkillName, endingOfSkillName);
    
                //if we do not want to reprompt call EndDialogAsync instead of RepromptDialogAsync
                if (Convert.ToBoolean(config[$"skill:{skillName}:disableReprompt"]))
                {
                    //this does not actually appear to remove the dialog from the stack
                    //if I call "Call Skill 1" again then this line is still hit
                    //so it seems like the dialog hangs around but shouldn't actually show to the user
                    //not sure how to resolve this but it's not really an issue as far as I can tell
                    return EndDialogAsync(turnContext, instance, DialogReason.EndCalled, cancellationToken);
                }
                else
                {
                    LoadDialogOptions(turnContext, instance);
                    return base.RepromptDialogAsync(turnContext, instance, cancellationToken);
                }
            }
    
            private void LoadDialogOptions(ITurnContext context, DialogInstance instance)
            {
                var dialogOptions = (SkillDialogOptions)instance.State[_dialogOptionsStateKey];
    
                DialogOptions.BotId = dialogOptions.BotId;
                DialogOptions.SkillHostEndpoint = dialogOptions.SkillHostEndpoint;
                DialogOptions.ConversationIdFactory = context.TurnState.Get<SkillConversationIdFactoryBase>() ?? throw new NullReferenceException("Unable to locate SkillConversationIdFactoryBase in HostContext");
                DialogOptions.SkillClient = context.TurnState.Get<BotFrameworkClient>() ?? throw new NullReferenceException("Unable to locate BotFrameworkClient in HostContext");
                DialogOptions.ConversationState = context.TurnState.Get<ConversationState>() ?? throw new NullReferenceException($"Unable to get an instance of {nameof(ConversationState)} from TurnState.");
                DialogOptions.ConnectionName = dialogOptions.ConnectionName;
    
                // Set the skill to call
                DialogOptions.Skill = dialogOptions.Skill;
            }
        }
    }
    

    添加 AdaptiveComponentRegistrationCustom

    该类允许将 AdaptiveBotComponentCustom 添加到 Startup.cs 中的 ComponentRegistration

    using System;
    using System.Linq;
    using Microsoft.Bot.Builder.Dialogs.Adaptive;
    using Microsoft.Bot.Builder.Dialogs.Adaptive.Actions;
    using Microsoft.Bot.Builder.Dialogs.Declarative;
    using Microsoft.Bot.Builder.Dialogs.Declarative.Obsolete;
    using Microsoft.BotFramework.Composer.WebApp.Dialogs;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    
    namespace Microsoft.BotFramework.Composer.WebApp.Components
    {
        /// <summary>
        /// <see cref="ComponentRegistration"/> implementation for adaptive components.
        /// </summary>
        [Obsolete("Use `AdaptiveBotComponent` instead.")]
        public class AdaptiveComponentRegistrationCustom : DeclarativeComponentRegistrationBridge<AdaptiveBotComponentCustom>
        {
        }
    }
    

    添加 AdaptiveBotComponentCustom

    这个类覆盖了默认的 AdaptiveBotComponent 行为并用 DeclarativeType 替换了 DeclarativeType 中的依赖注入

    using System;
    using System.Linq;
    using Microsoft.Bot.Builder.Dialogs.Adaptive;
    using Microsoft.Bot.Builder.Dialogs.Adaptive.Actions;
    using Microsoft.Bot.Builder.Dialogs.Declarative;
    using Microsoft.Bot.Builder.Dialogs.Declarative.Obsolete;
    using Microsoft.BotFramework.Composer.WebApp.Dialogs;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    
    namespace Microsoft.BotFramework.Composer.WebApp.Components
    {
        public class AdaptiveBotComponentCustom : AdaptiveBotComponent
        {
            public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
            {
                base.ConfigureServices(services, configuration);
    
                //this is the replace the DeclarativeType BeginSkill so that we can handle the reprompting situation differently
                //in some cases we don't want to reprompt when there has been an interruption
                //this will be configured in appSettings.json under skill:{SkillName}:disableReprompt
                var beginSkillDI = services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(DeclarativeType<BeginSkill>));
                if (beginSkillDI != null)
                {
                    services.Remove(beginSkillDI);
                }
    
                //adding the cust class which will deal with use the configuration setting skill:{SkillName}:disableReprompt
                services.AddSingleton<DeclarativeType>(sp => new DeclarativeType<BeginSkillNonRePrompting>(BeginSkill.Kind));
            }
        }
    }
    

    添加 appSettings.json

    添加将触发功能以防止重新提示的设置

    {
      "skill": {
        "testLoopingPrompt": {
          "disableReprompt": "true"
        }
      }
    }
    

    修改 Program.cs

    添加了一行来加载 appSettings.json 文件,其中包含阻止重新提示特定技能的设置。

    builder.AddJsonFile("appSettings.json");
    

    修改 Startup.cs

    更改配置 ComponentRegistration 的部分。这将使用新的 BeginSkillNonRePrompting 类而不是普通的 BeginSkill 类。

    ComponentRegistration.Add(new AdaptiveComponentRegistration());
    

    ComponentRegistration.Add(new AdaptiveComponentRegistrationCustom());
    

    遗留问题

    虽然这解决了用户方面的问题,但“RepromptDialogAsync”中的“EndDialogAsync”实际上并没有使对话框脱离堆栈。它仍然继续进入“RepromptDialogAsync”方法,尽管从用户的角度来看它总是什么都不做。我现在只有简短的对话。我将很快进入与 if / switch / 多个技能 / 多个用户提示的更长对话,因此需要注意与此更改相关的任何问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-10-11
      • 2019-11-22
      • 1970-01-01
      • 1970-01-01
      • 2020-08-20
      • 1970-01-01
      • 2017-07-18
      相关资源
      最近更新 更多