3

我有一个问题......不幸的是,网络上的所有样本都太浅了,并没有很好地涵盖这一点:

我有一个扩展 LuisDialog 的 RootDialog。这个 RootDialog 负责弄清楚用户想要做什么。这可能是多件事,但其中之一将是启动新订单。为此,RootDialog 会将调用转发给 NewOrderDialog,NewOrderDialog 的职责是弄清楚一些基本细节(用户想要订购什么,他喜欢使用哪个地址),最后它会确认订购并返回到 RootDialog。

RootDialog 的代码非常简单:

[Serializable]
public class RootDialog : LuisDialog<object>
{
    public RootDialog() : base(new LuisService(new LuisModelAttribute(ConfigurationManager.AppSettings["LuisAppId"], ConfigurationManager.AppSettings["LuisAPIKey"], domain: "westus.api.cognitive.microsoft.com")))
    {
    }

    [LuisIntent("Order.Place")]
    public async Task PlaceOrderIntent(IDialogContext context, LuisResult result)
    {
        await context.Forward(new NewOrderDialog(), OnPlaceOrderIntentCompleted, context.Activity, CancellationToken.None);

        context.Wait(MessageReceived);
    }

    private async Task OnPlaceOrderIntentCompleted(IDialogContext context, IAwaitable<object> result)
    {
        await context.PostAsync("Your order has been placed. Thank you for shopping with us.");

        context.Wait(MessageReceived);
    }
}

我还为 NewOrderDialog 考虑了一些代码:

[Serializable]
public class NewOrderDialog : LuisDialog<object>
{
    private string _product;
    private string _address;

    public NewOrderDialog() : base(new LuisService(new LuisModelAttribute(ConfigurationManager.AppSettings["LuisAppId"], ConfigurationManager.AppSettings["LuisAPIKey"], domain: "westus.api.cognitive.microsoft.com")))
    {
    }

    [LuisIntent("Order.RequestedItem")]
    public async Task RequestItemIntent(IDialogContext context, LuisResult result)
    {
        EntityRecommendation item;
        if (result.TryFindEntity("Item", out item))
        {
            _product = item.Entity;
            await context.PostAsync($"Okay, I understood you want to order: {_product}.");
        }
        else
        {
            await context.PostAsync("I couldn't understand what you would like to buy. Can you try it again?");
        }

        context.Wait(MessageReceived);
    }

    [LuisIntent("Order.AddedAddress")]
    public async Task AddAddressIntent(IDialogContext context, LuisResult result)
    {
        EntityRecommendation item;
        if (result.TryFindEntity("Address", out item))
        {
            _address = item.Entity;
            await context.PostAsync($"Okay, I understood you want to ship the item to: {_address}.");
        }
        else
        {
            await context.PostAsync("I couldn't understand where you would like to ship the item. Can you try it again?");
        }

        context.Wait(MessageReceived);
    }
}

列出的代码不起作用。进入 Order.Place 意图后,它立即执行“成功”回调,然后抛出此异常:

异常:IDialog 方法执行已通过 IDialogStack 指定的多个恢复处理程序完成。[“文本/纯文本”类型的文件]

所以我有几个问题:

  1. 如何解决我得到的错误?
  2. 我如何在进入 NewOrderDialog 时检查我们是否已经知道产品和地址是什么,如果没有提示他们输入正确的信息?
  3. 为什么即使我没有调用 context.Done() 之类的东西,NewOrderDialog 也会关闭?我只希望在收集完所有信息并确认订单后关闭它。
4

2 回答 2

3

因此,第一个问题是您在“Order.Place”中执行 acontext.Forward和 a context.Wait,根据定义,这是错误的。您需要选择:转发到新对话框或在当前等待。根据您的帖子,您想转发,所以只需删除等待呼叫。

除此之外,您有 1 个 LUIS 对话框,并且您正在尝试转发到一个新的 LUIS 对话框...我怀疑这是否行不通;我可以想象这是两个不同的 LUIS 模型,否则它就是错误的。

根据您的评论,我现在了解您要对第二个对话框执行的操作。问题(这与您的第二个问题有关)是以这种方式使用 LUIS 可能会令人困惑。例如:

  • 用户:我要下单
  • bot => 转发到新对话框。由于它是前向,activity.Text可能会再次转到 LUIS(到第二个对话框的模型)并且不会检测到任何内容。第二个对话框将处于Wait状态,供用户输入。

现在,用户如何知道他需要输入地址或产品?你在哪里提示用户?看到问题了吗?

我怀疑您的第三个问题是您在 #1 中遇到的错误的副作用,我已经为此提供了解决方案。

如果你再澄清一点,我可能会更有帮助。您在第二个对话框中尝试对 LUIS 执行的操作看起来不太好,但也许有解释可能有意义。

通常的情况是:我从LUIS("Order.Place") 获得意图,然后我启动一个 FormFlow 或一组提示来获取下订单的信息(地址、产品等),或者如果你想保留使用LUIS你可能想检查Luis Action Binding您可以在https://blog.botframework.com/2017/04/03/luis-action-binding-bot/上阅读更多内容。

于 2017-11-06T11:54:49.413 回答
-1

您知道Microsoft Bot Framework 的 Bing 位置控制吗?它可用于处理获取和验证用户地址时的“他喜欢使用哪个地址”部分。

这是一些示例代码:

[LuisModel("xxx", "yyy")]
[Serializable]
public class RootDialog : LuisDialog<object>
{
    ...

    [LuisIntent("Find Location")]
    public async Task FindLocationIntent(IDialogContext context, LuisResult result)
    {
        try
        {
            context.Call(new FindUserLocationDialog(), ResumeAfterLocationDialog);
        }
        catch (Exception e)
        {
            // handle exceptions
        }
    }

    public async Task ResumeAfterLocationDialog(IDialogContext context, IAwaitable<object> result)
    {
        var resultLocation = await result;

        await context.PostAsync($"Your location is {resultLocation}");

        // do whatever you want

        context.Wait(this.MessageReceived);
    }
}

对于“FindUserLocationDialog()”,您可以从第 58 行开始遵循示例。

编辑:

1)您可以尝试使用:

[LuisIntent("Order.Place")]
public async Task PlaceOrderIntent(IDialogContext context, LuisResult result)
{
    context.Forward(new NewOrderDialog(), OnPlaceOrderIntentCompleted, context.Activity, CancellationToken.None);

    // or this
    // context.Call(new NewOrderDialog(), OnPlaceOrderIntentCompleted);
}

2)我会说这取决于你如何构建你的意图。您的“Order.Place”意图是否包括实体?意思是说,如果您的用户说“我想订购地址 Y 的产品X ,您的意图是否已经选择了这些实体?

我建议您在“Order.Place”意图下检查产品和地址。获取并验证产品和地址后,您可以将其转发到另一个 Dialog(非 LUIS)来处理订单的其余部分。

[LuisIntent("Order.Place")]
public async Task PlaceOrderIntent(IDialogContext context, LuisResult result)
{
    EntityRecommendation item;
    if (result.TryFindEntity("Item", out item))
    {
        _product = item.Entity;
    }

    if (result.TryFindEntity("Address", out item))
    {
        _address = item.Entity;
    }

    if (_product == null)
    {
        PromptDialog.Text(context, this.MissingProduct, "Enter the product");   
    }
    else if (_address == null)
    {
        PromptDialog.Text(context, this.MissingAddress, "Enter the address");   
    }

    // both product and address present
    context.Forward(new NewOrderDialog(), OnPlaceOrderIntentCompleted, **object with _product and _address**, CancellationToken.None);
}

private async Task MissingProduct(IDialogContext context, IAwaitable<String> result)
{
    _product = await result;
    // perform some validation

    if (_address == null)
    {
        PromptDialog.Text(context, this.MissingAddress, "Enter the address");
    }
    else
    {
        // both product and address present
        context.Forward(new NewOrderDialog(), OnPlaceOrderIntentCompleted, **object with _product and _address**, CancellationToken.None);
    }
}

private async Task MissingAddress(IDialogContext context, IAwaitable<String> result)
{
    _address = await result;
    // perform some validation

    // both product and address present
    context.Forward(new NewOrderDialog(), OnPlaceOrderIntentCompleted, **object with _product and _address**, CancellationToken.None);
}

沿着这些思路。可能需要包括 try/catch。

3) 我认为这与您在“Order.Place”LUIS 意图中的“context.Wait(MessageReceived)”有关

于 2017-11-03T05:48:57.443 回答