Luis 在 azure 中发布时仅获得 2 个意图,但在 bot 模拟器中运行完美

Luis only picking up 2 intent when published in azure but is working perfectly in bot emulator

我有一个机器人,它的所有 LUIS、QnAMaker 和 Dispatch 在 Bot 模拟器中运行良好。我有很多意图,包括帮助,它们在 Bot Emulator 中都可以完美运行,但是当发布在 azure 和 Messenger 上时,我输入的任何内容都只有 returns 两个意图,取消和有时 None 意图。

机器人模拟器

在 Azure 中发布后在 Messenger 中

我的 LUIS 意图

MainDialog:(Dialog A 和 Dialog B 与 maindialog 相同,但继承了 ComponentDialog 而不是 InterruptDialog)

    public class MainDialog : InterruptDialog
    {
        private const string InitialId = nameof(MainDialog);
        public MainDialog()
            : base(nameof(MainDialog))
        {
            InitialDialogId = InitialId;
            WaterfallStep[] waterfallSteps = new WaterfallStep[]
             {
                 FirstStepAsync,
                 SecondStepAsync,
             };
            AddDialog(new WaterfallDialog(InitialId, waterfallSteps));
            AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
            AddDialog(new TextPrompt(nameof(TextPrompt)));
            AddDialog(new DialogA());
            AddDialog(new DialogB());
        }

        private async Task<DialogTurnResult> FirstStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            await stepContext.Context.SendActivityAsync("<Start of MainDialog>");
            return await stepContext.PromptAsync(
             nameof(ChoicePrompt),
             new PromptOptions
             {
                 Prompt = MessageFactory.Text($"What do you want to do next?"),
                 Choices = new List<Choice>
                 {
                        new Choice
                        {
                            Value = "Nothing",
                            Synonyms = new List<string>
                            {
                                "nothing",
                            },
                        },
                 },
                 RetryPrompt = MessageFactory.Text($"Please choose one of the options."),
             });
        }

        private static async Task<DialogTurnResult> SecondStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
        {
            await stepContext.Context.SendActivityAsync("End of Main");
            return await stepContext.EndDialogAsync();
        }

中断对话:

    public class InterruptDialog : ComponentDialog
    {
        public InterruptDialog(string id)
            : base(id)
        {
        }

        protected override async Task<DialogTurnResult> OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default(CancellationToken))
        {
            var result = await IsTurnInterruptedAsyncHelpAndCancel(innerDc, cancellationToken);
            if (result != null)
            {
                return result;
            }

            return await base.OnBeginDialogAsync(innerDc, options, cancellationToken);
        }

        protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
        {
            var result = await IsTurnInterruptedAsyncHelpAndCancel(innerDc, cancellationToken);
            if (result != null)
            {
                return result;
            }

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

        private async Task<DialogTurnResult> IsTurnInterruptedAsyncHelpAndCancel(DialogContext innerDc, CancellationToken cancellationToken)
        {
            var recognizerResult = innerDc.Context.TurnState.Get<RecognizerResult>("recognizerResult");
            string topIntent = string.Empty;

            var luisResult = recognizerResult.Properties["luisResult"] as LuisResult;
            var result = luisResult.ConnectedServiceResult;
            if(result != null)
            {
                topIntent = result.TopScoringIntent.Intent;
            }

            if (topIntent.Equals("MainDialog"))
            {
                if (innerDc.ActiveDialog != null)
                {
                    await innerDc.CancelAllDialogsAsync();
                    await innerDc.BeginDialogAsync(nameof(MainDialog));
                }
                else
                {
                    await innerDc.BeginDialogAsync(nameof(MainDialog));
                }

                return new DialogTurnResult(DialogTurnStatus.Waiting);
            }

            if (topIntent.Equals("DialogB"))
            {
                if (innerDc.ActiveDialog != null)
                {
                    await innerDc.CancelAllDialogsAsync();
                    await innerDc.BeginDialogAsync(nameof(DialogB));
                }
                else
                {
                    await innerDc.BeginDialogAsync(nameof(DialogB));
                }

                return new DialogTurnResult(DialogTurnStatus.Waiting);
            }

            if (topIntent.Equals("DialogA"))
            {
                if (innerDc.ActiveDialog != null)
                {
                    await innerDc.CancelAllDialogsAsync();
                    await innerDc.BeginDialogAsync(nameof(DialogA));
                }
                else
                {
                    await innerDc.BeginDialogAsync(nameof(DialogA));
                }

                return new DialogTurnResult(DialogTurnStatus.Waiting);
            }

            if (topIntent.Equals("Cancel"))
            {
                if (innerDc.ActiveDialog != null)
                {
                    await innerDc.CancelAllDialogsAsync();
                    await innerDc.Context.SendActivityAsync(" Ok. I've cancelled our last activity.");
                }
                else
                {
                    await innerDc.Context.SendActivityAsync("I don't have anything to cancel.");
                }

                return new DialogTurnResult(DialogTurnStatus.Waiting);
            }

            if (topIntent.Equals("Help"))
            {
                await innerDc.Context.SendActivityAsync("Let me help you. ");

                if (innerDc.ActiveDialog != null)
                {
                    await innerDc.RepromptDialogAsync();
                }

                return new DialogTurnResult(DialogTurnStatus.Waiting);
            }

            return null;
        }
    }
}

机器人服务

  public class BotServices : IBotServices
  {
    public BotServices(IConfiguration configuration)
    {
        DispatchService = new LuisRecognizer(new LuisApplication(
        configuration["DispatchLuisAppId"],
        configuration["DispatchLuisAPIKey"],
        $"https://{configuration["DispatchLuisAPIHostName"]}"),
        new LuisPredictionOptions { IncludeAllIntents = true, IncludeInstanceData = true },
        true);

        LuisService = new LuisRecognizer(new LuisApplication(
        configuration["LuisAppId"],
        configuration["LuisAPIKey"],
        $"https://{configuration["LuisAPIHostName"]}"),
        new LuisPredictionOptions { IncludeAllIntents = true, IncludeInstanceData = true },
        true);

        QnaService = new QnAMaker(new QnAMakerEndpoint
        {
            KnowledgeBaseId = configuration["QnAKnowledgebaseId"],
            EndpointKey = configuration["QnAEndpointKey"],
            Host = configuration["QnAEndpointHostName"]
        });
    }

    public LuisRecognizer DispatchService { get; private set; }
    public LuisRecognizer LuisService { get; private set; }
    public QnAMaker QnaService { get; private set; }
}

DialogBot

 public class DialogBot<T> : ActivityHandler
    where T : Dialog
 {
    public readonly IStatePropertyAccessor<DialogState> _dialogAccessor;
    protected readonly Dialog Dialog;
    protected readonly BotState ConversationState;
    protected readonly BotState UserState;
    protected readonly ILogger Logger;
    private readonly IBotServices BotServices;

    private DialogSet Dialogs { get; set; }

    public DialogBot(IBotServices botServices, ConversationState conversationState, UserState userState, T dialog, ILogger<DialogBot<T>> logger)
    {
        ConversationState = conversationState;
        UserState = userState;
        Dialog = dialog;
        Logger = logger;
        BotServices = botServices;
        Dialogs = new DialogSet(conversationState.CreateProperty<DialogState>(nameof(DialogBot<T>)));
        RegisterDialogs(Dialogs);
    }

    public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
    { 
        await base.OnTurnAsync(turnContext, cancellationToken);

        var recognizerResult = turnContext.TurnState.Get<RecognizerResult>("recognizerResult");
        var topIntent = turnContext.TurnState.Get<string>("topIntent");

        var dc = await Dialogs.CreateContextAsync(turnContext, cancellationToken);
        var dialogResult = await dc.ContinueDialogAsync();

        if (!dc.Context.Responded)
        {
            switch (dialogResult.Status)
            {
                case DialogTurnStatus.Empty:
                    await DispatchToTopIntentAsync(turnContext, topIntent, recognizerResult, cancellationToken);
                    break;

                case DialogTurnStatus.Waiting:
                    break;

                case DialogTurnStatus.Complete:
                    await dc.EndDialogAsync();
                    break;

                default:
                    await dc.CancelAllDialogsAsync();
                    break;
            }
        }
    }

    protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
    {
        Logger.LogInformation("Running dialog with Message Activity.");

        string text = string.IsNullOrEmpty(turnContext.Activity.Text) ? string.Empty : turnContext.Activity.Text.ToLower();

        if (!string.IsNullOrEmpty(text))
        {
            var recognizerResult = await BotServices.DispatchService.RecognizeAsync(turnContext, cancellationToken);
            var topIntent = recognizerResult.GetTopScoringIntent();

            var luisResult = recognizerResult.Properties["luisResult"] as LuisResult;
            var result = luisResult.ConnectedServiceResult;
            var topLuisIntent = result.TopScoringIntent.Intent;

            turnContext.TurnState.Add("topIntent", topIntent.intent);
            turnContext.TurnState.Add("recognizerResult", recognizerResult);

        }

        await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
    }

    private void RegisterDialogs(DialogSet dialogs)
    {
        dialogs.Add(new MainDialog());
        dialogs.Add(new DialogA());
        dialogs.Add(new DialogB());
    }

    private async Task DispatchToTopIntentAsync(ITurnContext turnContext, string intent, RecognizerResult recognizerResult, CancellationToken cancellationToken)
    {
        switch (dispatch)
        {
            case "q_thisAzureBotQna":
                await DispatchToQnAMakerAsync(turnContext, cancellationToken);
                break;

            case "l_thisAzureBot-953e":
                await DispatchToLuisModelAsync(turnContext, recognizerResult.Properties["luisResult"] as LuisResult, cancellationToken);
                break;
        }
    }

    private async Task DispatchToQnAMakerAsync(ITurnContext turnContext, CancellationToken cancellationToken)
    {
        if (!string.IsNullOrEmpty(turnContext.Activity.Text))
        {
            var results = await BotServices.QnaService.GetAnswersAsync(turnContext);
            if (results.Any())
            {
                await turnContext.SendActivityAsync(MessageFactory.Text(results.First().Answer), cancellationToken);
            }
            else
            {
                await turnContext.SendActivityAsync(MessageFactory.Text("Sorry, could not find an answer in the Q and A system."), cancellationToken);
            }
        }
    }

    private async Task DispatchToLuisModelAsync(ITurnContext turnContext, LuisResult luisResult, CancellationToken cancellationToken)
    {
        var result = luisResult.ConnectedServiceResult;
        var topIntent = result.TopScoringIntent.Intent;

        switch (topIntent)
        {
            case "Greeting":
                Random r = new Random();
                var x = r.Next(1, 3);

                switch (x)
                {
                    case 1:
                        await turnContext.SendActivityAsync(MessageFactory.Text($"Hi!"));
                        break;

                    case 2:
                        await turnContext.SendActivityAsync(MessageFactory.Text($"Hello!"));
                        break;

                    case 3:
                        await turnContext.SendActivityAsync(MessageFactory.Text($"Hey!"));
                        break;

                    default:
                        break;
                }

                break;

            default:
                break;
        }
    }

    }
 }

启动:

    public class Startup
    {
        public Startup()
        {

        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>();

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

            services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();

            services.AddSingleton<IStorage, MemoryStorage>();

            services.AddSingleton<UserState>();

            services.AddSingleton<ConversationState>();

            services.AddSingleton<IBotServices, BotServices>();

            services.AddTransient<MainDialog>();

            services.AddTransient<IBot, DialogBot<MainDialog>>();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

            app.UseDefaultFiles();
            app.UseStaticFiles();

            app.UseMvc();
        }
    }
}

好的,所以我认为 issue/bug 在 configuration/appsettings 中,而不是 botservice。

现在可以在 Messenger 中正常检测意图

修复是对键进行硬编码而不是使用配置:

    namespace SabikoBotV2.Services
{
    public class BotServices : IBotServices
    {
        public BotServices(IConfiguration configuration)
        {
            DispatchService = new LuisRecognizer(new LuisApplication(
            "f2833d51-b9d2-4420xxxxxx",
            "4e17f6cf0574497c85cxxxxxxx",
            $"https://southeastasia.api.cognitive.microsoft.com"),
            new LuisPredictionOptions { IncludeAllIntents = true, IncludeInstanceData = true },
            true);

            LuisService = new LuisRecognizer(new LuisApplication(
            "a17b5589-80a4-4245-bfdf-xxxxxx",
            "4e17f6cf0574497c85c260bxxxxxxx",
            $"https://southeastasia.api.cognitive.microsoft.com"),
            new LuisPredictionOptions { IncludeAllIntents = true, IncludeInstanceData = true },
            true);

            QnaService = new QnAMaker(new QnAMakerEndpoint
            {
                KnowledgeBaseId = configuration["QnAKnowledgebaseId"],
                EndpointKey = configuration["QnAEndpointKey"],
                Host = configuration["QnAEndpointHostName"]
            });
        }

        public LuisRecognizer DispatchService { get; private set; }
        public LuisRecognizer LuisService { get; private set; }
        public QnAMaker QnaService { get; private set; }
    }
}

@user10860492,您是我们最活跃的支持用户之一,我们很乐意为您提供帮助。但是,我觉得我们对您的问题提供的答案太容易实现,实际上并没有让您更好地理解 Bot Framework 或编码,这对您是一种伤害。

因此,这个答案将与大多数人有所不同。我会为您提供指导,而不仅仅是答案。我希望它更像是一个来回,而不仅仅是一个你可以接受和 运行 的答案。

我会带你开始,我们可以在评论中继续,最终打开 Whosebug 聊天来完全解决这个问题。


所以首先,我会回答

So i managed to fixed this by not using the injected BotService and just by manually doing the luisRecognizer code in the DialogBot's OnMessageActivityAsync directly

看看您的代码有何不同。您为 BotService 发布的代码与您在答案中发布的代码之间存在 非常 的关键区别。我不会直接给出答案,但我会暗示它与依赖注入或任何类型的高级 BotFramework 或 dotnet 无关。

我还会暗示这直接与您的"No such host is known" error有关。

您遇到的另一个问题是您对 Dispatch 的看法是错误的。我看不到你的代码,但这可能与你的 "Cancel is the only intent" 问题有关,尽管也可能是 "No such host is known".

的一部分

Read here about how Dispatch works

我还建议深入研究 how this sample works。这是一个快速入门:

如果您已完成阅读,您应该会看到 dispatch 是这样工作的:

  1. 您的机器人仅调用 Parent Dispatch LUIS 应用
  2. Parent Dispatch LUIS 应用程序查看其意图,以查看是否有任何匹配其连接的任何子应用程序(LUIS 或 QnAMaker)
  3. 如果它与 LUIS 匹配,则它 returns 来自该子应用的首要意图。如果它匹配 QnAMaker,它 returns QnAMaker 意图和 your code should call QnAMaker

我暗暗怀疑您的 LUIS 应用布局不正确。您的 Parent Dispatch 应用实际上应该只包含指向子应用的意图。您的子应用程序应该包含其他所有内容。您还应该只需要为 LUIS 调用 Parent Dispatch 应用程序,并且只需要在 Parent Dispatch 应用程序 returns QnA 意图时调用 QnA。


我知道我们处于不同的时区,所以您可能要到明天早上才能看到这个信息——我这个周末不工作,但会在太平洋标准时间星期一早上 8 点之前恢复在线以提供帮助。


回复 1:

Thank you i would love that. Sometimes i make things work by just doing trial and error and not really learning how and why. mainly because i am not that good and the project needs to finish fast.

太棒了!我们都去过那里!我认为整个团队都支持这种方法。我相信它会对您有更多帮助,同时也是我们开始为其他人寻求帮助时提供更多概念性答案的好途径。

Also i think i understand now (though i have not tested it yet) i think the key difference is that i hard coded the keys and not call them by the configuration. and that botservice has nothing to do with it?

关闭!没错,botservice/DI 与它无关。这不是因为硬编码与非硬编码。如果您的 appsettings.json 有:

"DispatchLuisAPIHostName": "https://southeastasia.api.cognitive.microsoft.com"

...为什么对 Botservice.cs 不起作用?仔细看看 Botservice.cs注意:我不知道为什么这会在 Emulator 中工作而不是实时...除非 Emulator 进行某种 URL 解析以防止此错误。那是一个奇怪的人。不过,有时 Emulator 会缓存旧值,因此您需要重新启动它 and/or 重新启动 IIS 服务以确保它使用正确的值。

I have updated the codes in my question. I now only call dispatch, save the the recognizerResult in turnState for me to use it in interruptDialog in a different class. Is it now the correct way sir? Also does this mean it is now only 1 endpoint hit per reply of user? because i read somewhere with dispatch it is 2 endpoint hits per call of luis.

我相信您的代码看起来应该如此。未经测试很难说。它的表现是否符合您的预期?

你是对的:使用调度意味着每次调度调用你只需要支付一个端点调用费用(而不是额外的一个用于调度调用每个子应用程序 - 但如果它必须在获得 QnA 意图后调用 QnA,您需要为额外的 QnA 调用付费)。 编辑:已与 LUIS 团队确认