【问题标题】:Botframework v4: string "No" in confirmPrompt is being recognized as cancel in luis.aiBotframework v4:confirmPrompt 中的字符串“No”在 luis.ai 中被识别为取消
【发布时间】:2026-01-06 15:25:02
【问题描述】:

当我用确认提示或是或否提示提示用户时,您好。 Luis 将“否”检测为取消意图,这会取消我的整个对话。然后我从取消意图中删除了“否”,但现在“没有被 luis 检测为问候意图。问候意图中甚至没有“否”字。我尽可能不想禁用 luis因为用户可以随时取消。我该如何解决这个问题?谢谢!

这里是代码。

       public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken)
    {
        var dc = await _dialogs.CreateContextAsync(turnContext, cancellationToken);
        var activity = turnContext.Activity;

        var userstate = await _basicAccessors.BasicUserStateAccessor.GetAsync(turnContext, () => new BasicUserState(), cancellationToken);
        var state = await _basicAccessors.BasicStateAccessor.GetAsync(turnContext, () => new BasicState(), cancellationToken);

        if (turnContext.Activity.Type == ActivityTypes.Message)
        {
            turnContext.TurnState.Add("BasicAccessors", _basicAccessors);
            string text = string.IsNullOrEmpty(turnContext.Activity.Text) ? string.Empty : turnContext.Activity.Text.ToLower();

            var luisResults = await _services.LuisServices[LuisConfiguration].RecognizeAsync(dc.Context, cancellationToken);

            var topScoringIntent = luisResults?.GetTopScoringIntent();
            var topIntent = topScoringIntent.Value.intent;

            string userName = string.Empty;
            if (activity.From.Name != null)
            {
                userName = activity.From.Name;
            }
            userstate.Name = userName;

            await _basicAccessors.BasicUserStateAccessor.SetAsync(turnContext, userstate);
            await _basicAccessors.BasicStateAccessor.SetAsync(turnContext, state);

            var interrupted = await IsTurnInterruptedAsync(dc, topIntent);
            if (interrupted)
            {
                // Bypass the dialog.
                // Save state before the next turn.
                await _basicAccessors.ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
                await _basicAccessors.UserState.SaveChangesAsync(turnContext, false, cancellationToken);
                return;
            }

            // Continue the current dialog
            var dialogResult = await dc.ContinueDialogAsync();

            // if no one has responded,
            if (!dc.Context.Responded)
            {
                // examine results from active dialog
                switch (dialogResult.Status)
                {
                    case DialogTurnStatus.Empty:
                        switch (topIntent)
                        {
                            case GreetingIntent:
                                await dc.BeginDialogAsync(MainDialogId);
                                break;

                            case "loan calculator":
                            case "loan calc":
                                await dc.BeginDialogAsync(MainDialogId);
                                break;

                            case NoneIntent:
                            default:

                                await dc.Context.SendActivityAsync("I didn't understand what you just said to me.");
                                break;
                        }

                        break;

                    case DialogTurnStatus.Waiting:
                        // The active dialog is waiting for a response from the user, so do nothing.
                        break;

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

                    default:
                        await dc.CancelAllDialogsAsync();
                        break;
                }
            }
        }
        else if (activity.Type == ActivityTypes.ConversationUpdate)
        {
            if (activity.MembersAdded != null)
            {
                // Iterate over all new members added to the conversation.
                foreach (var member in activity.MembersAdded)
                {
                    // Greet anyone that was not the target (recipient) of this message.
                    // To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards for more details.
                    if (member.Id != activity.Recipient.Id)
                    {
                        await SendWelcomeMessageAsync(turnContext, cancellationToken);
                    }
                }
            }
        }

        await _basicAccessors.ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
        await _basicAccessors.UserState.SaveChangesAsync(turnContext, false, cancellationToken);
    }

 private async Task<bool> IsTurnInterruptedAsync(DialogContext dc, string topIntent)
    {
        // See if there are any conversation interrupts we need to handle.
        if (topIntent.Equals(CancelIntent))
        {
            if (dc.ActiveDialog != null)
            {
                await dc.CancelAllDialogsAsync();
                await dc.Context.SendActivityAsync("Ok. I've canceled our last activity.");
            }
            else
            {
                await dc.Context.SendActivityAsync("I don't have anything to cancel.");
            }

            return true;        // Handled the interrupt.
        }

        if (topIntent.Equals(HelpIntent))
        {
            await dc.Context.SendActivityAsync("Let me try to provide some help.");
            await dc.Context.SendActivityAsync("I understand greetings, being asked for help, or being asked to cancel what I am doing.");
            if (dc.ActiveDialog != null)
            {
                await dc.RepromptDialogAsync();
            }

            return true;        // Handled the interrupt.
        }

【问题讨论】:

  • 没有意图吗?
  • 并非没有意图。只有问候、取消、帮助和无意图。
  • 那么可能需要创建一个无意图。您需要以某种方式让 LUIS 了解 No 与 cancel 不同
  • 但是 luis 是有代价的。创建一个无意图可能会使执行 luis 调用的频率更高,从而导致更多成本?
  • 无法理解,当您再创建一个意图时,这将如何导致更多费用或更多调用。您认为“否”可以映射到其他意图吗?

标签: c# botframework azure-language-understanding


【解决方案1】:

这似乎是一个设计难题。我的建议是重新排列机器人的优先级,以便在您的对话未得到正确响应时调用 LUIS。这可以通过将IsTurnInterruptedAsyncOnTurnAsync 移动到验证器方法来完成。验证器方法可以作为参数传递给提示的构造函数:

_dialogs.Add(new ConfirmPrompt(CONFIRMPROMPT, ValidateAsync));

您需要确保可以从任何地方访问您的首要意图,以防您的提示属于不同的类。您可以通过将最高意图添加到您的回合状态来做到这一点,该状态仅持续一个回合的范围。在OnTurnAsync 中包含此代码:

var topScoringIntent = luisResults?.GetTopScoringIntent();
var topIntent = topScoringIntent.Value.intent;

// Include this:
turnContext.TurnState.Add("topIntent", topIntent);

您的ValidateAsync 方法可能如下所示:

private async Task<bool> ValidateAsync(PromptValidatorContext<bool> promptContext, CancellationToken cancellationToken)
{
    if (promptContext.Recognized.Succeeded)
    {
        return true;
    }

    await IsTurnInterruptedAsync(promptContext.Context);

    return false;
}

由于IsTurnInterruptedAsync 现在可能属于不同的类,它需要通过您的轮次状态访问最高意图。如果_dialogs 超出范围,您也可以将其添加到您的回合状态,或者将其设为公共/静态属性。

private async Task<bool> IsTurnInterruptedAsync(ITurnContext turnContext)
{
    var dc = await _dialogs.CreateContextAsync(turnContext);
    var topIntent = turnContext.TurnState.Get<string>("topIntent");

    // See if there are any conversation interrupts we need to handle.
    if (topIntent.Equals(CancelIntent))
    {
        // . . .

这样,只有当话语对对话无效时才会发生中断。

即使话语对对话有效,您也可能希望允许打断。当然,任何内容都适用于文本提示,但您可能仍不希望允许用户键入“我该怎么做?”作为他们的名字。如果您想针对某些类型的对话框首先将话语发送到 LUIS,然后针对其他类型的对话框首先将话语发送到对话框,您可以像这样检查堆栈顶部的对话框的类型:

var activeDialog = _dialogs.Find(dc.ActiveDialog.Id);
if (activeDialog is ConfirmPrompt || activeDialog is ChoicePrompt)

【讨论】:

  • 谢谢先生,我一回家就试试这个。谢谢。
  • 先生,您能解释一下“turnContext.TurnState.Add("topIntent", topIntent) 和“var topIntent = turnContext.TurnState.Get("topIntent")”还有 turncontext, 瀑布吗stepcontext、prompt context 和 turnstate?如果可以的话,我想进一步了解它们。
  • 如果提示在另一个类对话框中怎么办。我怎么称呼你加起来的那些代码?
  • 先生,这个问题怎么样。 *.com/questions/54885861/…
  • 它看起来正在被照顾