jjzjj

c# - Bot Framework 搞乱了对话框状态

coder 2024-05-20 原文

我目前正在使用 Microsoft 的 Bot Framework 制作聊天机器人。在我的流程中,我有一个最后的对话框,让用户知道他们正在参加比赛。还有一种针对未知输入的错误处理方法。这两种方法见此处:

[Serializable]
public class ConcertCityDialog : AbstractBasicDialog<DialogResult>
{
    private static FacebookService FacebookService => new FacebookService(new FacebookClient());

    [LuisIntent("ConcertCity")]
    public async Task ConcertCityIntent(IDialogContext context, LuisResult result)
    {
        var fbAccount = await FacebookService.GetAccountAsync(context.Activity.From.Id);

        var selectedCityName = result.Entities.FirstOrDefault()?.Entity;

        concert_city selectedCity;
        using (var concertCityService = new ConcertCityService())
        {
            selectedCity = concertCityService.FindConcertCity(selectedCityName);
        }

        if (selectedCity == null)
        {
            await NoneIntent(context, result);
            return;
        }

        user_interaction latestInteraction;
        using (var userService = new MessengerUserService())
        {
            var user = userService.FindByFacebookIdIncludeInteractions(context.Activity.From.Id);
            latestInteraction = user.user_interaction.MaxBy(e => e.created_at);
        }

        latestInteraction.preferred_city_id = selectedCity.id;
        latestInteraction.gif_created = true;

        using (var userInteractionService = new UserInteractionService())
        {
            userInteractionService.UpdateUserInteraction(latestInteraction);
        }

        var shareIntroReply = context.MakeMessage();
        shareIntroReply.Text = "Great choice! You are now participating in the competition. If you dare then pass your message \uD83D\uDE0E";

        await context.PostAsync(shareIntroReply);

        var reply = await MessageUtility.MakeShareMessageCard(context, fbAccount, latestInteraction, false);

        await context.PostAsync(reply);

        context.Done(DialogResult.Done);
    }

    [LuisIntent("")]
    [LuisIntent("None")]
    public async Task NoneIntent(IDialogContext context, LuisResult result)
    {
        messenger_user user;
        using (var userService = new MessengerUserService())
        {
            user = userService.FindByFacebookId(context.Activity.From.Id);
        }

        var phrase = CreateMisunderstoodPhrase(user, result.Query);

        using (var misunderstoodPhraseService = new MisunderstoodPhraseService())
        {
            misunderstoodPhraseService.CreatePhrase(phrase);
        }

        List<concert_city> concertCities;
        using (var concertCityService = new ConcertCityService())
        {
            concertCities = concertCityService.GetUpcomingConcertCities().ToList();
        }

        // Prompt city
        var reply = context.MakeMessage();
        reply.Text = "I'm not sure what you mean \uD83E\uDD14<br/>Which Grøn Koncert would you like to attend?";

        reply.SuggestedActions = new SuggestedActions
        {
            Actions = concertCities.Select(e => MessageUtility.MakeQuickAnswer(e.name)).ToList()
        };

        await context.PostAsync(reply);

        context.Wait(MessageReceived);
    }

    protected override void OnDeserializedCustom(StreamingContext context)
    {
    }
}

下面是 AbstractBasicDialog 实现:

[Serializable]
public abstract class AbstractBasicDialog<T> : LuisDialog<T>
{
    protected AbstractBasicDialog() : base(new LuisService(new LuisModelAttribute(
        ConfigurationManager.AppSettings["LuisAppId"],
        ConfigurationManager.AppSettings["LuisAPIKey"],
        domain: ConfigurationManager.AppSettings["LuisAPIHostName"])))
    {
    }

    [LuisIntent("Cancel")]
    public virtual async Task CancelIntent(IDialogContext context, LuisResult result)
    {
        var randomQuotes = new List<string>
        {
            "If you say so, I'll leave you alone for now",
            "alright then, I'll leave you alone",
            "Okay then, I won't bother you anymore"
        };

        await context.PostAsync(MessageUtility.RandAnswer(randomQuotes));

        context.Done(DialogResult.Cancel);
    }

    [LuisIntent("Start")]
    public virtual async Task StartIntent(IDialogContext context, LuisResult result)
    {
        context.Done(DialogResult.Restart);
    }

    [LuisIntent("CustomerSupport")]
    public async Task CustomerSupportIntent(IDialogContext context, LuisResult result)
    {
        using (var userService = new MessengerUserService())
        {
            var user = userService.FindByFacebookId(context.Activity.From.Id);
            if (user != null)
            {
                user.receiving_support = true;
                userService.UpdateUser(user);
            }
        }

        await context.PostAsync("I'll let customer service know, that you want to talk to them. They will get back to you within 24 hours.<br/>If at any time you want to return to me, and start passing a message, just type \"Stop customer support\".");

        context.Call(new CustomerSupportDialog(), ResumeAfterCustomerSupport);
    }

    private async Task ResumeAfterCustomerSupport(IDialogContext context, IAwaitable<DialogResult> result)
    {
        context.Done(await result);
    }

    protected misunderstood_phrase CreateMisunderstoodPhrase(messenger_user user, string phrase)
    {
        return new misunderstood_phrase
        {
            phrase = phrase,
            dialog = GetType().Name,
            messenger_user_id = user.id
        };
    }

    [OnDeserialized]
    private void OnDeserialized(StreamingContext context)
    {
        OnDeserializedCustom(context);
    }

    protected abstract void OnDeserializedCustom(StreamingContext context);
}

调用链从这个对话框开始:

[Serializable]
public class BasicLuisDialog : LuisDialog<DialogResult>
{
    private static FacebookService FacebookService => new FacebookService(new FacebookClient());

    public BasicLuisDialog() : base(new LuisService(new LuisModelAttribute(
        ConfigurationManager.AppSettings["LuisAppId"],
        ConfigurationManager.AppSettings["LuisAPIKey"],
        domain: ConfigurationManager.AppSettings["LuisAPIHostName"])))
    {
    }

    [LuisIntent("")]
    [LuisIntent("None")]
    public async Task NoneIntent(IDialogContext context, LuisResult result)
    {
        var facebookAccount = await FacebookService.GetAccountAsync(context.Activity.From.Id);

        RegisterUser(facebookAccount, null, out var user);

        var phrase = CreateMisunderstoodPhrase(user, result.Query);
        using (var misunderstoodPhraseService = new MisunderstoodPhraseService())
        {
            misunderstoodPhraseService.CreatePhrase(phrase);
        }

        var reply = context.MakeMessage();
        reply.SuggestedActions = new SuggestedActions
        {
            Actions = new List<CardAction>
            {
                new CardAction { Title = "Get started", Type = ActionTypes.ImBack, Value = "Get started" },
                new CardAction { Title = "Customer support", Type = ActionTypes.ImBack, Value = "Customer support" }
            }
        };

        var name = string.IsNullOrEmpty(facebookAccount.FirstName) ? "" : $"{facebookAccount.FirstName} ";
        reply.Text = $"Hm, I'm not sure what you mean {name} \uD83E\uDD14 Here are some ways you can interact with me:";

        await context.PostAsync(reply);
        context.Wait(MessageReceived);
    }

    [LuisIntent("Greeting")]
    [LuisIntent("Positive")]
    [LuisIntent("Start")]
    public async Task GreetingIntent(IDialogContext context, LuisResult result)
    {
        var rnd = new Random();
        var facebookAccount = await FacebookService.GetAccountAsync(context.Activity.From.Id);

        // Initial Greeting
        var greetings = new List<string>
        {
            "Well hello there",
            "Hi there"
        };

        if (!string.IsNullOrEmpty(facebookAccount.FirstName))
        {
            greetings.Add("Hi {0}");
            greetings.Add("Hello {0}");
            greetings.Add("Welcome {0}");
        }

        if (facebookAccount.Gender == "male")
            greetings.Add("Hey handsome");
        else if (facebookAccount.Gender == "female")
            greetings.Add("Hi gorgeous");

        var randIndex = rnd.Next(greetings.Count);

        var greeting = string.Format(greetings[randIndex], facebookAccount.FirstName);

        await context.PostAsync(greeting);

        await MessageUtility.StartTyping(context, 300);

        country country;
        using (var countryService = new CountryService())
        {
            country = countryService.FindCountry(facebookAccount.Locale);
        }
        var userHasCountry = RegisterUser(facebookAccount, country, out var user);

        // If user contry not found prompt for answer
        if (!userHasCountry)
        {
            var countryReply = context.MakeMessage();
            countryReply.Text = "You are hard to keep track of - where are you from?";
            countryReply.SuggestedActions = new SuggestedActions
            {
                Actions = new List<CardAction>
                {
                    MessageUtility.MakeQuickAnswer("Denmark"),
                    MessageUtility.MakeQuickAnswer("Norway"),
                    MessageUtility.MakeQuickAnswer("Sweden"),
                    MessageUtility.MakeQuickAnswer("Other")
                }
            };

            await context.PostAsync(countryReply);

            context.Call(new CountryDialog(), AfterCountryDialog);
        }
        else
        {
            await FunPrompt(context, country);
        }
    }

    private async Task AfterCountryDialog(IDialogContext countryContext, IAwaitable<country> countryAwaitable)
    {
        var country = await countryAwaitable;

        var facebookAccount = await FacebookService.GetAccountAsync(countryContext.Activity.From.Id);

        using (var userService = new MessengerUserService())
        {
            var user = userService.FindByFacebookId(facebookAccount.Id);

            user.country = country;
            userService.UpdateUser(user);
        }

        var reply = countryContext.MakeMessage();
        reply.Text = "That's cool \uD83D\uDE0E";

        await countryContext.PostAsync(reply);

        await MessageUtility.StartTyping(countryContext, 350);

        await FunPrompt(countryContext, country);
    }

    private async Task FunPrompt(IDialogContext context, country country)
    {
        if (country?.name == "norway" && DateTime.Now < new DateTime(2018, 8, 13))
        {
            var reply = context.MakeMessage();
            reply.Text = "Unfortunately the competition isn't open in Norway yet. You can still talk to customer support if you want to";
            reply.SuggestedActions = new SuggestedActions
            {
                Actions = new List<CardAction>
                {
                    MessageUtility.MakeQuickAnswer("Customer support")
                }
            };

            await context.PostAsync(reply);

            context.Wait(MessageReceived);
        }
        else if ((country?.name == "denmark" && DateTime.Now >= new DateTime(2018, 7, 29)) ||
                 (country?.name == "norway" && DateTime.Now >= new DateTime(2018, 10, 21)))
        {
            var reply = context.MakeMessage();
            reply.Text = "The competition has ended. You can still talk to customer support if you want to";
            reply.SuggestedActions = new SuggestedActions
            {
                Actions = new List<CardAction>
                {
                    MessageUtility.MakeQuickAnswer("Customer support")
                }
            };

            await context.PostAsync(reply);

            context.Wait(MessageReceived);
        }
        else
        {
            await context.PostAsync("Are you up for some fun?");

            context.Call(new IntroductionDialog(), ResumeAfterDialog);
        }
    }

    [LuisIntent("CustomerSupport")]
    public async Task CustomerSupportIntent(IDialogContext context, LuisResult result)
    {
        using (var userService = new MessengerUserService())
        {
            var user = userService.FindByFacebookId(context.Activity.From.Id);
            if (user != null)
            {
                user.receiving_support = true;
                userService.UpdateUser(user);
            }
        }

        await context.PostAsync("I'll let customer support know, that you want to talk to them. They should be messaging you shortly.<br/>You can end your conversation with customer support at any time by typing \"Stop customer support\".");

        context.Call(new CustomerSupportDialog(), ResumeAfterDialog);
    }

    private async Task ResumeAfterDialog(IDialogContext context, IAwaitable<DialogResult> result)
    {
        var resultState = await result;
        if (resultState == DialogResult.Restart)
            await GreetingIntent(context, null);
        else if (resultState == DialogResult.CustomerSupport)
            await ResumeAfterCustomerSupport(context);
        else if (resultState == DialogResult.Done || resultState == DialogResult.Cancel)
            context.Done(resultState);
        else
            context.Wait(MessageReceived);
    }

    private async Task ResumeAfterCustomerSupport(IDialogContext context)
    {
        using (var userService = new MessengerUserService())
        {
            var user = userService.FindByFacebookId(context.Activity.From.Id);
            if (user != null)
            {
                user.receiving_support = false;
                userService.UpdateUser(user);
            }
        }

        await context.PostAsync("I hope you got the help you needed. Would you like to pass a message to a friend?");

        context.Call(new IntroductionDialog(), ResumeAfterDialog);
    }

    private bool RegisterUser(FacebookAccount fbAccount, country country, out messenger_user user)
    {
        if (string.IsNullOrEmpty(fbAccount?.Id))
        {
            user = null;
            return false;
        }

        using (var userService = new MessengerUserService())
        {
            user = userService.FindByFacebookId(fbAccount.Id);

            if (user != null)
                return user.country != null;

            user = new messenger_user
            {
                id = fbAccount.Id,
                country = country
            };

            userService.CreateUser(user);

            return user.country != null;
        }
    }

    protected misunderstood_phrase CreateMisunderstoodPhrase(messenger_user user, string phrase)
    {
        return new misunderstood_phrase
        {
            phrase = phrase,
            dialog = GetType().Name,
            messenger_user_id = user.id
        };
    }
}

这在大多数情况下都有效。用户被告知他们的注册是成功的,流程通过 context.Done() 调用退出。然而,有时聊天机器人不会将对话注册为退出,如下所示:

如您所见,即使我调用了 Done() 方法,聊天机器人仍在同一个对话框中。这是我的聊天机器人中的一个普遍问题,因为它有时会在我所有的对话中发生。

您对可能出现的问题有任何意见吗?

编辑: 调试时,我在每次调用 context.Call 时都添加了断点。当我的问题出现时,它会停止击中这些断点。这可能是某些 DI 或其他东西的副作用吗?这是我的 DI 代码:

Conversation.UpdateContainer(builder =>
{
    builder.RegisterModule(new DialogModule());
    builder.RegisterModule(new ReflectionSurrogateModule());
    builder.RegisterModule(new DialogModule_MakeRoot());
    builder.RegisterModule(new AzureModule(Assembly.GetExecutingAssembly()));

    var store = new TableBotDataStore(ConfigurationManager.ConnectionStrings["StorageConnectionString"].ConnectionString);

    builder.Register(c => store)
        .Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
        .AsSelf()
        .SingleInstance();

    builder.Register(c => new CachingBotDataStore(store,
            CachingBotDataStoreConsistencyPolicy
                .ETagBasedConsistency))
        .As<IBotDataStore<BotData>>()
        .AsSelf()
        .InstancePerLifetimeScope();

    builder.RegisterType<BasicLuisDialog>().As<LuisDialog<DialogResult>>().InstancePerDependency();
});

最佳答案

我想我终于找到了问题所在。在我的代码中,我在静态类中实现了一个辅助方法,该方法将发送键入响应并等待一定时间。看到上下文被传递到这个静态方法中,这似乎导致了一些问题。

将方法更改为 LuisDialog 的扩展方法后,我不再遇到此问题。

如果有人能详细说明为什么这可能是个问题,我将不胜感激。

编辑:有问题的方法:

public static async Task StartTyping(IDialogContext context, int sleep)
{
    var typingMsg = context.MakeMessage();
    typingMsg.Type = ActivityTypes.Typing;

    await context.PostAsync(typingMsg);
    await Task.Delay(sleep);
}

关于c# - Bot Framework 搞乱了对话框状态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50568479/

有关c# - Bot Framework 搞乱了对话框状态的更多相关文章

  1. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  2. ruby-on-rails - 跳过状态机方法的所有验证 - 2

    当我的预订模型通过rake任务在状态机上转换时,我试图找出如何跳过对ActiveRecord对象的特定实例的验证。我想在reservation.close时跳过所有验证!叫做。希望调用reservation.close!(:validate=>false)之类的东西。仅供引用,我们正在使用https://github.com/pluginaweek/state_machine用于状态机。这是我的预订模型的示例。classReservation["requested","negotiating","approved"])}state_machine:initial=>'requested

  3. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  4. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

  5. ruby - 字符串文字中的转义状态作为 `String#tr` 的参数 - 2

    对于作为String#tr参数的单引号字符串文字中反斜杠的转义状态,我觉得有些神秘。你能解释一下下面三个例子之间的对比吗?我特别不明白第二个。为了避免复杂化,我在这里使用了'd',在双引号中转义时不会改变含义("\d"="d")。'\\'.tr('\\','x')#=>"x"'\\'.tr('\\d','x')#=>"\\"'\\'.tr('\\\d','x')#=>"x" 最佳答案 在tr中转义tr的第一个参数非常类似于正则表达式中的括号字符分组。您可以在表达式的开头使用^来否定匹配(替换任何不匹配的内容)并使用例如a-f来匹配一

  6. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  7. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  8. ruby-on-rails - 为模型创建状态属性 - 2

    我想为我的Task模型创建一个status属性,该属性将按以下顺序指示它在三部分进度中的位置:打开=>进行中=>完成。它的工作方式类似于亚马逊包裹的交付方式:已订购=>已发货=>已交付。我想知道设置此属性的最佳方法是什么。我可能是错的,但创建三个独立的bool属性似乎有点多余。实现此目标的最佳方法是什么? 最佳答案 Rails4有一个内置的enummacro.它使用单个整数列并映射到键列表。classOrderenumstatus:[:ordered,:shipped,:delivered]end状态映射如下:{ordered:0,

  9. ruby - 是否可以在不实际发送或读取数据的情况下查明 ruby​​ 套接字是否处于 ESTABLISHED 或 CLOSE_WAIT 状态? - 2

    s=Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0)s.connect(Socket.pack_sockaddr_in('port','hostname'))ssl=OpenSSL::SSL::SSLSocket.new(s,sslcert)ssl.connect从这里开始,如果ssl连接和底层套接字仍然是ESTABLISHED,或者它是否在默认值7200之后进入CLOSE_WAIT,我想检查一个线程几秒钟甚至更糟的是在实际上不需要.write()或.read()的情况下关闭。是用select()、IO.select()还是其他方法完成

  10. ruby - 在 ruby​​ 中生成一个进程,捕获 stdout,stderr,获取退出状态 - 2

    我想从ruby​​rake脚本运行一个可执行文件,比如foo.exe我希望将foo.exe的STDOUT和STDERR输出直接写入我正在运行rake任务的控制台.当进程完成时,我想将退出代码捕获到一个变量中。我如何实现这一目标?我一直在玩backticks、process.spawn、system但我无法获得我想要的所有行为,只有部分更新:我在Windows上,在标准命令提示符下,而不是cygwin 最佳答案 system获取您想要的STDOUT行为。它还返回true作为零退出代码,这可能很有用。$?填充了有关最后一次system调

随机推荐