1. 先解释一下FormAction
FormAction:在Rasa Core中,当我们执行一个action需要同时填充多个slot时,可以使用FormAction来实现,因为FormAction会遍历监管的所有slot,当发现相关的slot未被填充时,就会向用户主动发起询问,直到所有slot被填充完毕,才会执行接下来的业务逻辑。
2. 下面介绍Form Action的实现
Form Action的实现主要包含四步
- 构造story
- 添加form字段到Domain
- 配置FormPolicy
- Form Action 函数的具实现
前面三步请移步官网和这篇文章, 我这里提到这些主要是为讲解后面的request_next_slot()
函数做铺垫。
以官网中实现的restaurant_form
为例,restaurant_form
是我们要实现的 form action 的名字,要实现一个完整的 form action 我们至少需要定义以下三个函数:
-
name
: action 的名字 -
required_slots
: 要使Submit方法起作用,需要填充的slots列表。 -
submit
: 填充完所有的slots后,在form末尾需要做的事情。
def name(self) -> Text:
"""Unique identifier of the form"""
return "restaurant_form"
@staticmethod
def required_slots(tracker: Tracker) -> List[Text]:
"""A list of required slots that the form has to fill"""
return ["cuisine", "num_people", "outdoor_seating", "preferences", "feedback"]
def submit(
self,
dispatcher: CollectingDispatcher,
tracker: Tracker,
domain: Dict[Text, Any],
) -> List[Dict]:
"""Define what the form has to do
after all required slots are filled"""
# utter submit template
dispatcher.utter_message(template="utter_submit")
return []
对于我们要实现的restaurant_form
来说:
class RestaurantForm(FormAction):
"""Example of a custom form action."""
def name(self) -> Text:
"""Unique identifier of the form."""
return "restaurant_form"
@staticmethod
def required_slots(tracker: Tracker) -> List[Text]:
"""A list of required slots that the form has to fill."""
return ["cuisine", "num_people", "outdoor_seating", "preferences", "feedback"]
def submit(
self,
dispatcher: CollectingDispatcher,
tracker: Tracker,
domain: Dict[Text, Any],
) -> List[Dict]:
"""Define what the form has to do after all required slots are filled."""
# utter submit template
dispatcher.utter_message(template="utter_submit")
return []
class RestaurantForm
继承了rasa中的FormAction
这个类,因此如果我们想要实现一些复杂功能和逻辑,就可以重写父类FormAction
中的某些方法,比如validate_{slot-name}
, slot_mappings
等,这其中当然也包括我们要讲的request_next_slot()
函数, (终于到重点了),那么request_next_slot()
函数是用来干嘛的呢?
官网是这么说的
如果slots中充满了你确定无法处理的东西,并且你想直接停用表单(deactivate the form),那么可以重写
request_next_slot()
方法。
下面还是以restaurant_form
中cuisine
slot 为例进行解释,但是我们可以使用任何想要触发停用的逻辑(trigger deactivation):
def request_next_slot(
self,
dispatcher: "CollectingDispatcher",
tracker: "Tracker",
domain: Dict[Text, Any],
) -> Optional[List[EventType]]:
"""Request the next slot and utter template if needed,
else return None"""
for slot in self.required_slots(tracker):
if self._should_request_slot(tracker, slot):
## Condition of validated slot that triggers deactivation:触发停用的条件
if slot == "cuisine" and tracker.get_slot("cuisine") == "caribbean":
dispatcher.utter_message(text="Sorry, I can't help you with that")
return self.deactivate()
## For all other slots, continue as usual
logger.debug(f"Request next slot '{slot}'")
dispatcher.utter_message(
template=f"utter_ask_{slot}", **tracker.slots
)
return [SlotSet(REQUESTED_SLOT, slot)]
return None
在父类FormAction
中,request_next_slot()
方法是这样的:
# noinspection PyUnusedLocal
def request_next_slot(
self,
dispatcher: "CollectingDispatcher",
tracker: "Tracker",
domain: Dict[Text, Any],
) -> Optional[List[EventType]]:
"""Request the next slot and utter template if needed,
else return None"""
for slot in self.required_slots(tracker):
if self._should_request_slot(tracker, slot):
logger.debug(f"Request next slot '{slot}'")
dispatcher.utter_message(template=f"utter_ask_{slot}", **tracker.slots)
return [SlotSet(REQUESTED_SLOT, slot)]
# no more required slots to fill
return None
两者的区别之处在于restaurant_form
中增加了一个判断cuisine
是否等于caribbean
的逻辑
我写这个是因为我在项目中遇到了一个这样的逻辑:在反问用户填充槽位时,如果某个槽位三次都没有填对,那么就结束本次对话,并向用户发出提示信息重新开始对话。
很明显只要在
...
# 触发停用的经过验证的插槽的条件
....
之下加上自己的判断逻辑就好了,具体怎么加有一定的小技巧,我暂时先不放出代码,但是整个问题的解决思路已经很清晰了。
以上~
参考:
网友评论