gpt4 book ai didi

botframework - 聊天机器人中的空响应和跨 session 问题

转载 作者:行者123 更新时间:2023-12-04 13:38:36 25 4
gpt4 key购买 nike

我正在使用 V4 的机器人框架。使用 API 调用,我试图获取数据并将其显示给用户,我定义了一个自定义方法来从 API 捕获数据并对其进行预处理,然后通过瀑布对话框将其发送给用户,并且我将此方法设为异步,并且在调用它的地方使用 await。

我面临的问题有两种情况 -

  • 当两个用户在一个实例中发送问题时,其中一个响应被捕获为 null 而不是从 API 获取的值。
  • 我们使用提示卡来显示结果,当用户点击按钮时,偶尔会观察到交叉 session 。
    非常感谢这方面的帮助。

  • 定义调用 API 和获取数据的自定义方法:


    public static async Task<string>
    GetEPcallsDoneAsync(ConversationData conversationData)
    {
    LogWriter.LogWrite("Info: Acessing end-points");
    string responseMessage = null;
    try
    {
    conversationData.fulFillmentMap = await
    AnchorUtil.GetFulfillmentAsync(xxxxx);//response from API call to get data

    if (conversationData.fulFillmentMap == null || (conversationData.fulFillmentMap.ContainsKey("status") && conversationData.fulFillmentMap["status"].ToString() != "200"))
    {
    responseMessage = "Sorry, something went wrong. Please try again later!";
    }
    else
    {
    conversationData.NLGresultMap = await
    AnchorUtil.GetNLGAsync(conversationData.fulFillmentMap ,xxxx);//API call to get response to be displayed


    if (conversationData.errorCaptureDict.ContainsKey("fulfillmentError") || conversationData.NLGresultMap.ContainsKey("NLGError"))
    {
    responseMessage = "Sorry, something went wrong:( Please try again later!!!";
    }
    else
    {
    responseMessage = FormatDataResponse(conversationData.NLGresultMap["REPLY"].ToString()); //response message
    }
    }
    return responseMessage;
    }
    catch (HttpRequestException e)
    {
    LogWriter.LogWrite("Error: " + e.Message);
    System.Console.WriteLine("Error: " + e.Message);
    return null;
    }
    }


    以及调用上述函数的对话框类的瀑布步骤:


    private async Task<DialogTurnResult> DoProcessInvocationStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {
    conversationData.index = 0; //var for some other purpose
    conversationData.result = await
    AnchorUtil.GetEPcallsDoneAsync(conversationData);
    await _conversationStateAccessor.SetAsync(stepContext.Context, conversationData, cancellationToken);

    return await stepContext.NextAsync(cancellationToken);
    }


    ConversationData 包含通过瀑布对话框处理数据所需的变量,对象的值已在每个步骤中通过访问器设置和访问,如下所示:

    在对话框类中,

       public class TopLevelDialog : ComponentDialog
    {

    private readonly IStatePropertyAccessor<ConversationData> _conversationStateAccessor;
    ConversationData conversationData;

    public TopLevelDialog(ConversationState conversationState)
    : base(nameof(TopLevelDialog))
    {
    _conversationStateAccessor = conversationState.CreateProperty<ConversationData>(nameof(ConversationData));

    AddDialog(new TextPrompt(nameof(TextPrompt)));
    AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
    AddDialog(new ReviewSelectionDialog(conversationState));
    AddDialog(new ESSelectionDialog());

    AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
    {
    StartSelectionStepAsync,
    GetESResultStep,
    DoProcessInvocationStep,
    ResultStepAsync,
    IterationStepAsync
    }));

    InitialDialogId = nameof(WaterfallDialog);
    }

    private async Task<DialogTurnResult> StartSelectionStepAsync (WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {
    conversationData = await _conversationStateAccessor.GetAsync(stepContext.Context, () => new ConversationData());
    //code for functionality
    await _conversationStateAccessor.SetAsync(stepContext.Context, conversationData, cancellationToken);
    return await stepContext.NextAsync(null, cancellationToken);
    }
    //other dialog steps
    }

    最佳答案

    您的两个问题可能都源于同一件事。您不能申报 conversationData作为类级属性。您会遇到这样的并发问题,因为每个用户都会覆盖 conversationData对于每个其他用户。您必须重新申报 conversationData在每个步骤函数中。

    例如,
    User A启动瀑布对话框并进行到一半。 conversationData在这一点上是正确的,并且完全代表了它应该做的。

    现在 User B开始对话。在 StartSelectionStepAsync ,他们只是重置 conversationData因为conversationData = await _conversationStateAccessor.GetAsync(stepContext.Context, () => new ConversationData());为了大家并且所有用户共享相同的 conversationData因为 ConversationData conversationData; .

    所以现在,当 User A继续他们的谈话,conversationData将为空/空。

    应该如何保存状态

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License.

    const { Channels, MessageFactory } = require('botbuilder');
    const {
    AttachmentPrompt,
    ChoiceFactory,
    ChoicePrompt,
    ComponentDialog,
    ConfirmPrompt,
    DialogSet,
    DialogTurnStatus,
    NumberPrompt,
    TextPrompt,
    WaterfallDialog
    } = require('botbuilder-dialogs');
    const { UserProfile } = require('../userProfile');

    const ATTACHMENT_PROMPT = 'ATTACHMENT_PROMPT';
    const CHOICE_PROMPT = 'CHOICE_PROMPT';
    const CONFIRM_PROMPT = 'CONFIRM_PROMPT';
    const NAME_PROMPT = 'NAME_PROMPT';
    const NUMBER_PROMPT = 'NUMBER_PROMPT';
    const USER_PROFILE = 'USER_PROFILE';
    const WATERFALL_DIALOG = 'WATERFALL_DIALOG';

    /**
    * This is a "normal" dialog, where userState is stored properly using the accessor, this.userProfile.
    * In this dialog example, we create the userProfile using the accessor in the first step, transportStep.
    * We then pass prompt results through the remaining steps using step.values.
    * In the final step, summaryStep, we save the userProfile using the accessor.
    */
    class UserProfileDialogNormal extends ComponentDialog {
    constructor(userState) {
    super('userProfileDialogNormal');

    this.userProfileAccessor = userState.createProperty(USER_PROFILE);

    this.addDialog(new TextPrompt(NAME_PROMPT));
    this.addDialog(new ChoicePrompt(CHOICE_PROMPT));
    this.addDialog(new ConfirmPrompt(CONFIRM_PROMPT));
    this.addDialog(new NumberPrompt(NUMBER_PROMPT, this.agePromptValidator));
    this.addDialog(new AttachmentPrompt(ATTACHMENT_PROMPT, this.picturePromptValidator));

    this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
    this.transportStep.bind(this),
    this.nameStep.bind(this),
    this.nameConfirmStep.bind(this),
    this.ageStep.bind(this),
    this.pictureStep.bind(this),
    this.confirmStep.bind(this),
    this.saveStep.bind(this)
    ]));

    this.initialDialogId = WATERFALL_DIALOG;
    }

    /**
    * The run method handles the incoming activity (in the form of a TurnContext) and passes it through the dialog system.
    * If no dialog is active, it will start the default dialog.
    * @param {*} turnContext
    * @param {*} accessor
    */
    async run(turnContext, accessor) {
    const dialogSet = new DialogSet(accessor);
    dialogSet.add(this);

    const dialogContext = await dialogSet.createContext(turnContext);
    const results = await dialogContext.continueDialog();
    if (results.status === DialogTurnStatus.empty) {
    await dialogContext.beginDialog(this.id);
    }
    }

    async transportStep(step) {
    // Get the userProfile if it exists, or create a new one if it doesn't.
    const userProfile = await this.userProfileAccessor.get(step.context, new UserProfile());

    // Pass the userProfile through step.values.
    // This makes it so we don't have to call this.userProfileAccessor.get() in every step.
    step.values.userProfile = userProfile;

    // Skip this step if we already have the user's transport.
    if (userProfile.transport) {
    // ChoicePrompt results will show in the next step with step.result.value.
    // Since we don't need to prompt, we can pass the ChoicePrompt result manually.
    return await step.next({ value: userProfile.transport });
    }

    // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
    // Running a prompt here means the next WaterfallStep will be run when the user's response is received.
    return await step.prompt(CHOICE_PROMPT, {
    prompt: 'Please enter your mode of transport.',
    choices: ChoiceFactory.toChoices(['Car', 'Bus', 'Bicycle'])
    });
    }

    async nameStep(step) {
    // Retrieve the userProfile from step.values.
    const userProfile = step.values.userProfile;
    // Set the transport property of the userProfile.
    userProfile.transport = step.result.value;

    // Pass the userProfile through step.values.
    // This makes it so we don't have to call this.userProfileAccessor.get() in every step.
    step.values.userProfile = userProfile;

    // Skip the prompt if we already have the user's name.
    if (userProfile.name) {
    // We pass in a skipped bool so we know whether or not to send messages in the next step.
    return await step.next({ value: userProfile.name, skipped: true });
    }

    return await step.prompt(NAME_PROMPT, 'Please enter your name.');
    }

    async nameConfirmStep(step) {
    // Retrieve the userProfile from step.values and set the name property
    const userProfile = step.values.userProfile;

    // If userState is working correctly, we'll have userProfile.transport from the previous step.
    if (!userProfile || !userProfile.transport) {
    throw new Error(`transport property does not exist in userProfile.\nuserProfile:\n ${ JSON.stringify(userProfile) }`);
    }
    // Text prompt results normally end up in step.result, but if we skipped the prompt, it will be in step.result.value.
    userProfile.name = step.result.value || step.result;
    // step.values.userProfile.name is already set by reference, so there's no need to set it again to pass it to the next step.

    // We can send messages to the user at any point in the WaterfallStep. Only do this if we didn't skip the prompt.
    if (!step.result.skipped) {
    await step.context.sendActivity(`Thanks ${ step.result }.`);
    }

    // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
    // Skip the prompt if we already have the user's age.
    if (userProfile.age) {
    return await step.next('yes');
    }
    return await step.prompt(CONFIRM_PROMPT, 'Do you want to give your age?', ['yes', 'no']);
    }

    async ageStep(step) {
    // Retrieve the userProfile from step.values
    const userProfile = step.values.userProfile;

    // If userState is working correctly, we'll have userProfile.name from the previous step.
    if (!userProfile || !userProfile.name) {
    throw new Error(`name property does not exist in userProfile.\nuserProfile:\n ${ JSON.stringify(userProfile) }`);
    }

    // Skip the prompt if we already have the user's age.
    if (userProfile.age) {
    // We pass in a skipped bool so we know whether or not to send messages in the next step.
    return await step.next({ value: userProfile.age, skipped: true });
    }

    if (step.result) {
    // User said "yes" so we will be prompting for the age.
    // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
    const promptOptions = { prompt: 'Please enter your age.', retryPrompt: 'The value entered must be greater than 0 and less than 150.' };

    return await step.prompt(NUMBER_PROMPT, promptOptions);
    } else {
    // User said "no" so we will skip the next step. Give -1 as the age.
    return await step.next(-1);
    }
    }

    async pictureStep(step) {
    // Retrieve the userProfile from step.values and set the age property
    const userProfile = step.values.userProfile;
    // We didn't set any additional properties on userProfile in the previous step, so no need to check for them here.

    // Confirm prompt results normally end up in step.result, but if we skipped the prompt, it will be in step.result.value.
    userProfile.age = step.result.value || step.result;
    // step.values.userProfile.age is already set by reference, so there's no need to set it again to pass it to the next step.

    if (!step.result.skipped) {
    const msg = userProfile.age === -1 ? 'No age given.' : `I have your age as ${ userProfile.age }.`;

    // We can send messages to the user at any point in the WaterfallStep. Only send it if we didn't skip the prompt.
    await step.context.sendActivity(msg);
    }

    // Skip the prompt if we already have the user's picture.
    if (userProfile.picture) {
    return await step.next(userProfile.picture);
    }

    if (step.context.activity.channelId === Channels.msteams) {
    // This attachment prompt example is not designed to work for Teams attachments, so skip it in this case
    await step.context.sendActivity('Skipping attachment prompt in Teams channel...');
    return await step.next(undefined);
    } else {
    // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
    var promptOptions = {
    prompt: 'Please attach a profile picture (or type any message to skip).',
    retryPrompt: 'The attachment must be a jpeg/png image file.'
    };

    return await step.prompt(ATTACHMENT_PROMPT, promptOptions);
    }
    }

    async confirmStep(step) {
    // Retrieve the userProfile from step.values and set the picture property
    const userProfile = step.values.userProfile;
    // If userState is working correctly, we'll have userProfile.age from the previous step.
    if (!userProfile || !userProfile.age) {
    throw new Error(`age property does not exist in userProfile.\nuserProfile:\n ${ JSON.stringify(userProfile) }`);
    }
    userProfile.picture = (step.result && typeof step.result === 'object' && step.result[0]) || 'no picture provided';
    // step.values.userProfile.picture is already set by reference, so there's no need to set it again to pass it to the next step.

    let msg = `I have your mode of transport as ${ userProfile.transport } and your name as ${ userProfile.name }`;
    if (userProfile.age !== -1) {
    msg += ` and your age as ${ userProfile.age }`;
    }

    msg += '.';
    await step.context.sendActivity(msg);
    if (userProfile.picture && userProfile.picture !== 'no picture provided') {
    try {
    await step.context.sendActivity(MessageFactory.attachment(userProfile.picture, 'This is your profile picture.'));
    } catch (err) {
    await step.context.sendActivity('A profile picture was saved but could not be displayed here.');
    }
    }

    // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
    return await step.prompt(CONFIRM_PROMPT, { prompt: 'Would you like me to save this information?' });
    }

    async saveStep(step) {
    if (step.result) {
    // Get the current profile object from user state.
    const userProfile = step.values.userProfile;

    // Save the userProfile to userState.
    await this.userProfileAccessor.set(step.context, userProfile);

    await step.context.sendActivity('User Profile Saved.');
    } else {
    // Ensure the userProfile is cleared
    await this.userProfileAccessor.set(step.context, {});
    await step.context.sendActivity('Thanks. Your profile will not be kept.');
    }

    // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is the end.
    return await step.endDialog();
    }

    async agePromptValidator(promptContext) {
    // This condition is our validation rule. You can also change the value at this point.
    return promptContext.recognized.succeeded && promptContext.recognized.value > 0 && promptContext.recognized.value < 150;
    }

    async picturePromptValidator(promptContext) {
    if (promptContext.recognized.succeeded) {
    var attachments = promptContext.recognized.value;
    var validImages = [];

    attachments.forEach(attachment => {
    if (attachment.contentType === 'image/jpeg' || attachment.contentType === 'image/png') {
    validImages.push(attachment);
    }
    });

    promptContext.recognized.value = validImages;

    // If none of the attachments are valid images, the retry prompt should be sent.
    return !!validImages.length;
    } else {
    await promptContext.context.sendActivity('No attachments received. Proceeding without a profile picture...');

    // We can return true from a validator function even if Recognized.Succeeded is false.
    return true;
    }
    }
    }

    module.exports.UserProfileDialogNormal = UserProfileDialogNormal;

    关于botframework - 聊天机器人中的空响应和跨 session 问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60303969/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com