【问题标题】:Bot Framework v4 - IndexOutOfRangeException when 2 tabs are openBot Framework v4 - 打开 2 个选项卡时出现 IndexOutOfRangeException
【发布时间】:2019-10-02 11:25:15
【问题描述】:

我使用 C# 使用 bot 框架 v4 制作了一个机器人,它位于网页 https://websitebotv2.azurewebsites.net/ 上,如果只有 1 个用户,它可以正常工作,但是当我在新选项卡上打开它时,它会在我开始时给出 IndexOutOfRangeException对话。

我需要做什么才能使其在打开多个选项卡的情况下工作?

当我的机器人加注星标时,它会创建一个瀑布对话框,询问用户姓名并问候用户:

public dialogBotBot(dialogBotAccessors accessors, LuisRecognizer luis, QnAMaker qna)
    {
        // Set the _accessors 
        _accessors = accessors ?? throw new ArgumentNullException(nameof(accessors));
        // The DialogSet needs a DialogState accessor, it will call it when it has a turn context.
        _dialogs = new DialogSet(accessors.ConversationDialogState);

        // This array defines how the Waterfall will execute.
        var waterfallSteps = new WaterfallStep[] {
            NameStepAsync,
            NameConfirmStepAsync,
        };

        // The incoming luis variable is the LUIS Recognizer we added above.
        this.Recognizer = luis ?? throw new System.ArgumentNullException(nameof(luis));

        // The incoming QnA variable is the QnAMaker we added above.
        this.QnA = qna ?? throw new System.ArgumentNullException(nameof(qna));

        // Add named dialogs to the DialogSet. These names are saved in the dialog state.
        _dialogs.Add(new WaterfallDialog("details", waterfallSteps));
        _dialogs.Add(new TextPrompt("name"));

    }

然后我将他的名字保存在 UserProfile 类中,其中包含字段 Name 和 Context,Context 的目的是保存对话。

这是第一次工作,但如果我打开一个新标签或刷新当前标签以进行新对话,机器人将获取第一个对话数据。

在 Startup.cs 中抛出异常:

services.AddBot<dialogBotBot>(options =>
       {
           options.CredentialProvider = new ConfigurationCredentialProvider(Configuration);

           // Catches any errors that occur during a conversation turn and logs them to currently
           // configured ILogger.
           ILogger logger = _loggerFactory.CreateLogger<dialogBotBot>();

           options.OnTurnError = async (context, exception) =>
           {
               logger.LogError($"Exception caught : {exception}");
               await context.SendActivityAsync(exception + "\nSorry, it looks like something went wrong.\n" + exception.Message);
           };


           // Create and add conversation state.
           var conversationState = new ConversationState(dataStore);
           options.State.Add(conversationState);

           // Create and add user state. 
           var userState = new UserState(dataStore);
           options.State.Add(userState);
       });

我的 onTurnAsync 方法是:

    public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
    {

        // Handle Message activity type, which is the main activity type for shown within a conversational interface
        // Message activities may contain text, speech, interactive cards, and binary or unknown attachments.
        // see https://aka.ms/about-bot-activity-message to learn more about the message and other activity types
        if (turnContext.Activity.Type == ActivityTypes.Message)
        {
            //Get the current user profile
            userProfile = await _accessors.UserProfile.GetAsync(turnContext, () => new UserProfile(), cancellationToken);

            userProfile.Contexto.Add(turnContext.Activity.Text);

            foreach (string s in userProfile.Contexto)
                await turnContext.SendActivityAsync(s);


            // Get the conversation state from the turn context.
            var state = await _accessors.CounterState.GetAsync(turnContext, () => new CounterState());

            // Bump the turn count for this conversation.
            state.TurnCount++;

            // Check LUIS model
            var recognizerResult = await this.Recognizer.RecognizeAsync(turnContext, cancellationToken);
            var topIntent = recognizerResult?.GetTopScoringIntent();
            // Get the Intent as a string
            string strIntent = (topIntent != null) ? topIntent.Value.intent : "";
            // Get the IntentScore as a double
            double dblIntentScore = (topIntent != null) ? topIntent.Value.score : 0.0;
            // Only proceed with LUIS if there is an Intent 
            // and the score for the Intent is greater than 95
            if (strIntent != "" && (dblIntentScore > 2))
            {
                switch (strIntent)
                {
                    case "None":
                        //add the bot response to contexto
                        await turnContext.SendActivityAsync("Desculpa, não percebi.");
                        break;
                    case "Utilities_Help":
                        //add the bot response to contexto
                        await turnContext.SendActivityAsync("Quero-te ajudar!\nO que precisas?");
                        break;
                    default:
                        // Received an intent we didn't expect, so send its name and score.
                        //add the bot response to contexto
                        await turnContext.SendActivityAsync($"Intent: {topIntent.Value.intent} ({topIntent.Value.score}).");
                        break;
                }
            }
            else
            {
                if (userProfile.Name == null)
                {
                    // Run the DialogSet - let the framework identify the current state of the dialog from the dialog stack and figure out what (if any) is the active dialog.
                    var dialogContext = await _dialogs.CreateContextAsync(turnContext, cancellationToken);
                    var results = await dialogContext.ContinueDialogAsync(cancellationToken);
                    // If the DialogTurnStatus is Empty we should start a new dialog.
                    if (results.Status == DialogTurnStatus.Empty)
                    {
                        await dialogContext.BeginDialogAsync("details", null, cancellationToken);
                    }
                }
                else
                {
                    var answers = await this.QnA.GetAnswersAsync(turnContext);
                    if (answers.Any() && answers[0].Score > 0.7)
                    {
                        // If the service produced one or more answers, send the first one.
                        await turnContext.SendActivityAsync(answers[0].Answer + "\n" + state.TurnCount);
                    }
                    else
                    {
                        var responseMessage = $"Ainda não sei a resposta mas vou averiguar\nPosso-te ajudar com mais alguma coisa?";

                        String connectionString = "Data Source=botdataserverv1.database.windows.net;" +
                                                 "Initial Catalog=botDataBase;" +
                                                 "User id=AzureAdmin@botdataserverv1.database.windows.net;" +
                                                 "Password=admin_123;";


                        SqlConnection connection = new SqlConnection(connectionString);

                        SqlDataAdapter adapter = new SqlDataAdapter();
                        SqlCommand command;

                        String sms = turnContext.Activity.Text;
                        float result = answers[0].Score;

                        String insertMessage = "insert into Mensagem(texto,contexto,grauCerteza)" +
                                               "values('" + sms + "', 'Falta apurar o contexto' ," + result + ")";

                        connection.Open();

                        command = new SqlCommand(insertMessage, connection);

                        adapter.InsertCommand = new SqlCommand(insertMessage, connection);
                        adapter.InsertCommand.ExecuteNonQuery();

                        command.Dispose();
                        connection.Close();


                        await turnContext.SendActivityAsync(responseMessage);
                    }
                }

                // Save the user profile updates into the user state.
                await _accessors.UserState.SaveChangesAsync(turnContext, false, cancellationToken);

                // Set the property using the accessor.
                await _accessors.CounterState.SetAsync(turnContext, state);

                // Save the new turn count into the conversation state.
                await _accessors.ConversationState.SaveChangesAsync(turnContext);
            }


        }
    }

【问题讨论】:

  • 抱歉,没有您的实施细节,我们无法帮助/理解/解释
  • 我用更多信息编辑我的问题,希望对您有所帮助。我真的不知道为什么会这样
  • 我在 5 个不同的选项卡中打开了您的机器人,它们似乎都可以正常工作。重现错误的步骤是什么?错误发生在哪一行代码?如果您可以链接到所有机器人代码,那将有所帮助。我在您包含的代码中没有看到任何会导致这种情况的内容。
  • 如果我猜的话,这发生在OnMembersAddedAsync,或类似的地方。
  • 我现在无法在 Visual Studio 中安装 github 扩展,我稍后再试。在我的浏览器中,当我打开托管机器人的网页时,它可以正常工作,但是当我刷新网页而不是重新启动对话框时,它会给我这个异常,尽管如果我继续从我的 QnA 提出问题,他会回复我. Startup.cs中抛出异常,我将代码放在问题中。

标签: botframework


【解决方案1】:

你的问题在这里:

float result = answers[0].Score;

你有:

// Check to see if we have any answers
if (answers.Any() && answers[0].Score > 0.7)
{
    [...]
    // This is fine
}
else // else, WE HAVE NO ANSWERS
{
    [...]
    // At this point, answers is an empty array, so answers[0] throws an IndexOutOfRangeException
    float result = answers[0].Score;

刷新时发生这种情况的原因是新标签页的用户使用相同的用户 ID。机器人已经知道他们的name,所以不显示对话框,当它调用await this.Recognizer.RecognizeAsync(turnContext, cancellationToken);时,用户还没有在新的turnContext中输入任何内容,所以它返回一个空数组。

旁注:您可以在 WebChat 中设置用户 ID:

window.WebChat.renderWebChat(
      {
          directLine: directLine,
          userID: "USER_ID" // Make is use Math.random() or something if you want it to be random for each refresh
      },
      this.botWindowElement.nativeElement
  );

【讨论】:

  • 谢谢,成功了!关于 UserID 的旁注,我应该把代码放在哪里?
  • @PedroRodrigues 抱歉。我应该看到您使用的是 iframe 而不是 WebChat。可以在url中指定userId:&lt;iframe src="https://webchat.botframework.com/embed/mybot?t=&lt;token&gt;&amp;username=&lt;userName&gt;&amp;userid=&lt;theUserId&gt;"&gt;&lt;/iframe&gt;
猜你喜欢
  • 2021-01-09
  • 2020-02-23
  • 1970-01-01
  • 1970-01-01
  • 2019-07-27
  • 1970-01-01
  • 1970-01-01
  • 2018-10-30
  • 1970-01-01
相关资源
最近更新 更多