中间件

适用于: SDK v4

中间件只是一个位于适配器和机器人逻辑之间的类,它是在初始化期间添加到适配器的中间件集合的。 SDK 可让用户自行编写中间件或添加其他人创建的中间件。 进出机器人的每个活动都流经中间件。

适配器处理传入的活动并通过机器人中间件管道将其定向到机器人的逻辑,然后再返回。 当每个活动流入和流出机器人时,每个中间件都可以在机器人逻辑运行前后对其进行检查或执行操作。

在深入了解中间件之前,有必要先了解机器人概要以及它们如何处理活动

中间件用法

经常会遇到这样的问题:“与使用正常的机器人逻辑相比,我应该在什么时候以中间件的形式实施操作?”中间件为你提供了额外的机会,让你可以在处理对话的每个回合回合前后与用户的对话流进行交互。 中间件还允许你存储和检索有关聊天的信息,并在需要时调用其他处理逻辑。 下面是一些常见的场景,展示了中间件可能有用的地方。

观察或操作每个活动

很多情况下,需要机器人对每个活动或某种类型的每个活动执行某些操作。 例如,你可能希望记录机器人收到的每一条消息活动,或者在机器人本回合没有生成响应时提供回退响应。 中间件能够在机器人逻辑的其他部分执行之前和之后采取行动,是此类流程的理想位置。

修改或增强回合上下文

如果机器人知道的信息比活动中提供的多,这会让某些聊天将更富成效。 在这种情况下,中间件可以查看截至目前的聊天状态信息、查询外部数据源,还可在将执行传递给机器人逻辑之前将其附加到轮次上下文对象。

SDK 定义了可记录传入和传出活动的日志记录中间件,但你也可以定义自己的中间件。

机器人中间件管道

对于每个活动,适配器都按照其添加顺序调用中间件。 适配器传入回合的上下文对象和下一个委托,而中间件会调用该委托,将控制权传递给管道中的下一个中间件 。 在完成方法之前,中间件仍有机会在下一个委托返回后执行操作 。 你可以将其视为每个中间件对象始终都有机会对管道中的下一个中间件对象执行操作。

例如:

  • 第一个中间件对象的回合处理程序在调用 next 之前执行代码。
    • 第二个中间件对象的回合处理程序在调用 next 之前执行代码。
      • 机器人的轮次处理程序执行操作并返回结果。
    • 第二个中间件对象的回合处理程序会在返回前执行所有剩余代码。
  • 第一个中间件对象的回合处理程序会在返回前执行所有剩余代码。

如果中间件没有调用下一个委托,适配器就不会调用任何后续的中间件或机器人回合处理程序,并且管道就会短路。

机器人中间件管道完成后,回合结束,且回合上下文超出范围。

中间件或机器人可以生成响应并注册响应事件处理程序,但请记住,响应是在单独的进程中处理的。

中间件的顺序

由于添加中间件的顺序决定了中间件处理活动的顺序,因此有必要确定添加中间件的顺序。

注意

这意味着为你提供一种适用于大多数机器人的通用模式,但一定要根据自己的情况考虑每个中间件将如何与其他中间件进行交互。

应首先在中间件管道中添加处理每个机器人最低级别任务的中间件。 相关示例包括日志记录、异常处理和转换。 根据需要排序,例如是否希望在存储之前先翻译收到的消息,或者是否应先存储消息,这可能意味着存储的消息不会被翻译。

特定于机器人的中间件应最后添加到中间件管道中,这些中间件用于对发送给机器人的每条消息进行处理。 如果中间件使用状态信息或机器人上下文中设置的其他信息,请将其添加到中间件管道中用于修改状态或上下文的中间件的后面。

短路

有关中间件和响应处理程序的一个重要概念是“短路”。 如果要通过后续的层继续执行,需使用中间件(或响应处理程序)调用该执行的下一个委托,将执行传递下去。 如果下一个委托没有在该中间件(或响应处理程序)中被调用,则相关的流水线就会短路,后续层也不会被执行。 这意味着将跳过所有机器人逻辑以及管道中后面的所有中间件。 中间件和响应处理程序短路之间有着细微的区别。

当中间件短路一个回合时,你的机器人回合处理程序将不会被调用,但管道中在此之前执行的所有中间件代码仍将运行完成。

对于事件处理程序而言,不调用 next 就意味着取消事件,这与中间件跳过逻辑截然不同。 如果不处理事件的其余部分,则适配器永远不会发送该事件。

提示

如果响应事件(如 SendActivities)短路,请确保这是意料中的行为。 否则,它可能会导致难以修复 bug。

响应事件处理程序

除了应用程序和中间件逻辑以外,还可将响应处理程序(有时也称为事件处理程序或活动事件处理程序)添加到上下文对象中。 在执行实际响应之前,当前上下文对象上出现相关响应时,将调用这些处理程序。 当知道要在实际事件之前或之后对其余当前响应的该类型的所有活动执行某些操作时,这些处理程序非常有用。

警告

注意,请勿从它的相应响应事件处理程序中调用活动响应方法,例如,从发送活动处理程序中调用发送活动方法。 执行此操作可以生成一个无限循环。

请记住,每个新活动都会获得一个要执行的新线程。 创建处理活动的线程后,该活动的处理程序列表将复制到该新线程。 不会针对该特定活动事件执行在此之后添加的任何处理程序。 在上下文对象上注册的处理程序的处理方式与适配器管理中间件管道的方式类似。 也就是说,处理程序按照它们添加的顺序进行调用,并且调用下一个委托将控制权传递给下一个已注册的事件处理程序。 如果处理程序没有调用下一个委托,则后续的事件处理程序都不会被调用,事件就会短路,适配器也不会向通道发送响应。

处理中间件中的状态

保存状态的一种常用方法是在轮次处理程序的末尾调用 save changes 方法。 下面是以调用为重点的示意图。

机器人回合的序列图,包含机器人回合处理程序保存的状态。

这种方法的问题在于,在机器人的回合处理程序返回后,来自自定义中间件的任何状态更新都不会保存到持久存储中。 解决方法是通过将“自动保存更改”中间件的实例添加到中间件堆栈的开头,或至少添加到可能更新状态的任一中间件之前,在自定义中间件完成后,将调用转移到 save changes 方法。 执行情况如下所示。

机器人回合的序列图,其中保存了中间件的状态。

添加需要更新机器人状态集对象的状态管理对象,然后在创建“自动保存更改”中间件时使用这些对象。

其他资源

可以查看一下用 Bot Framework SDK [C# | JS] 实现的脚本记录器中间件。