在开源bot框架Rasa中,在对话过程中通过使用
ActionQueryKnowledgeBase
利用知识库中的信息。
知识库操作使您能够处理以下类型的对话:
会话人工智能中的一个常见问题是,用户不仅通过名称引用特定对象,而且使用“第一个”或“它”等引用术语。我们需要跟踪所提供的信息,以解决这些提到的正确对象。
此外,用户可能希望在交谈中获得有关物品的详细信息,例如,餐厅是否有外置座位,或者价格如何。为了响应这些用户请求,需要了解餐厅定义域。由于信息可能会发生变化,因此硬编码信息并不是解决方案。
为了应对上述挑战,Rasa可以与知识库集成。要使用此集成,可以创建从ActionQueryKnowledgeBase
继承的自定义操作,这是一个预先编写的自定义操作,包为了查询含对象及其属性所进行的查询知识库的逻辑。
您可以在examples/knowledgebasebot
(knowledge base bot)中找到完整的示例,以及下面实现此自定义操作的说明。
Using ActionQueryKnowledgeBase
创建知识数据库(Create a Knowledge Base)
用于回答用户请求的数据将存储在知识库中。知识库可以用来存储复杂的数据结构。我们建议您开始使用InMemoryKnowledgeBase
。一旦您想开始处理大量数据,就可以切换到自定义知识库(请参阅创建自己的知识库)。
要初始化InMemoryKnowledgeBase
,需要在json文件中提供数据。以下示例包含有关餐厅和酒店的数据。json结构应该包含每个对象类型的键,即“restaurant
”和“hotel
”。每个对象类型都映射到一个对象列表–这里我们有一个包含3个餐厅和3个酒店的列表。
{
"restaurant": [
{
"id": 0,
"name": "Donath",
"cuisine": "Italian",
"outside-seating": true,
"price-range": "mid-range"
},
{
"id": 1,
"name": "Berlin Burrito Company",
"cuisine": "Mexican",
"outside-seating": false,
"price-range": "cheap"
},
{
"id": 2,
"name": "I due forni",
"cuisine": "Italian",
"outside-seating": true,
"price-range": "mid-range"
}
],
"hotel": [
{
"id": 0,
"name": "Hilton",
"price-range": "expensive",
"breakfast-included": true,
"city": "Berlin",
"free-wifi": true,
"star-rating": 5,
"swimming-pool": true
},
{
"id": 1,
"name": "Hilton",
"price-range": "expensive",
"breakfast-included": true,
"city": "Frankfurt am Main",
"free-wifi": true,
"star-rating": 4,
"swimming-pool": false
},
{
"id": 2,
"name": "B&B",
"price-range": "mid-range",
"breakfast-included": false,
"city": "Berlin",
"free-wifi": false,
"star-rating": 1,
"swimming-pool": false
},
]
}
一旦在json
文件(例如data.json
)中定义了数据,您就可以使用这个数据文件创建InMemoryKnowledgeBase
,它将被传递给查询知识库的操作。
知识库中的每个对象都应该至少有“name
”和“id
”字段以使用默认实现。如果没有,你就必须定制你的InMemoryKnowledgeBase
。
定义NLU数据(Define the NLU Data)
在本节中:
- 我们将介绍一个新的意图,
query_knowledge_base
- 我们将对提及(
mention
)实体进行注释,以便我们的模型能够检测到像“第一个”这样的间接提及对象 - 我们将广泛使用同义词
为了让bot理解用户想要从知识库中检索信息,您需要定义一个新的意图。我们称之为query_knowledge_base
。
我们将ActionQueryKnowledgeBase
可以处理的请求分为两类:(1)用户希望获得特定类型的对象列表;或者(2)用户希望了解对象的特定属性。意图应包含这两个请求的许多变体:
nlu:
- intent: query_knowledge_base
examples: |
- what [restaurants]{"entity": "object_type", "value": "restaurant"} can you recommend?
- list some [restaurants]{"entity": "object_type", "value": "restaurant"}
- can you name some [restaurants]{"entity": "object_type", "value": "restaurant"} please?
- can you show me some [restaurants]{"entity": "object_type", "value": "restaurant"} options
- list [German](cuisine) [restaurants]{"entity": "sobject_type", "value": "restaurant"}
- do you have any [mexican](cuisine) [restaurants]{"entity": "object_type", "value": "restaurant"}?
- do you know the [price range]{"entity": "attribute", "value": "price-range"} of [that one](mention)?
- what [cuisine](attribute) is [it](mention)?
- do you know what [cuisine](attribute) the [last one]{"entity": "mention", "value": "LAST"} has?
- does the [first one]{"entity": "mention", "value": "1"} have [outside seating]{"entity": "attribute", "value": "outside-seating"}?
- what is the [price range]{"entity": "attribute", "value": "price-range"} of [Berlin Burrito Company](restaurant)?
- what about [I due forni](restaurant)?
- can you tell me the [price range](attribute) of [that restaurant](mention)?
- what [cuisine](attribute) do [they](mention) have?
上面的例子只是展示了与餐馆领域相关的例子。您应该将知识库中存在的每个对象类型的示例添加到相同的query_knowledge_base
意图中。
除了为每个查询类型添加各种训练示例外,还需要在训练示例中指定和注释以下实体:
object_type
:每当培训示例引用知识库中的特定对象类型时,该对象类型应标记为实体。使用同义词,例如,将restaurants
映射到restaurant
,正确的对象类型列为知识库中的一个键。mention
:如果用户通过“第一个”、“那个”或“它”引用对象,则应将这些术语标记为mention
。我们还使用同义词将一些提及映射到符号。你可以在resolving mentions中了解到这一点。attribute
:知识库中定义的所有属性名称都应在NLU数据中标识为attribute
。同样,使用同义词将属性名称的变体映射到知识库中使用的名称。
请记住将这些实体添加到domain.yml
文件中(作为实体和插槽):
entities:
- object_type
- mention
- attribute
slots:
object_type:
type: unfeaturized
mention:
type: unfeaturized
attribute:
type: unfeaturized
构建自己的知识库查询操作(Create an Action to Query your Knowledge Base)
要创建自己的知识库操作,需要继承ActionQueryKnowledgeBase
,并将知识库传递给ActionQueryKnowledgeBase
的构造函数。
from rasa_sdk.knowledge_base.storage import InMemoryKnowledgeBase
from rasa_sdk.knowledge_base.actions import ActionQueryKnowledgeBase
class MyKnowledgeBaseAction(ActionQueryKnowledgeBase):
def __init__(self):
knowledge_base = InMemoryKnowledgeBase("data.json")
super().__init__(knowledge_base)
无论何时创建ActionQueryKnowledgeBase
,都需要将KnowledgeBase
传递给构造函数。它可以是InMemoryKnowledgeBase
,也可以是您自己的KnowledgeBase
实现(请参阅创建您自己的知识库)。您只能从一个知识库中提取信息,因为不支持同时使用多个知识库。
这是此操作的全部代码!操作的名称是action_query_knowledge_base
。不要忘记将其添加到domain.yml
文件:
actions:
- action_query_knowledge_base
注意
如果覆盖默认操作名称action_query_knowledge_base
,则需要向域文件中添加以下三个非特征化插槽:knowledge_base_objects
、knowledge_base_last_object
和knowledge_base_last_object_type
。ActionQueryKnowledgeBase
在内部使用这些插槽。如果保留默认操作名称,则会自动为您添加这些插槽。
您还需要确保将一个故事添加到您的故事文件中,其中包括意图query_knowledge_base
和操作action_query_knowledge_base
。例如:
stories:
- story: knowledge base happy path
steps:
- intent: greet
- action: utter_greet
- intent: utter_greet
- action: action_query_knowledge_base
- intent: goodbye
- action: utter_goodbye
最后一件事就是在你的域文件中定义响应utter_ask_rephrase
。如果操作不知道如何处理用户的请求,它将使用此响应请求用户重新措辞。例如,将以下响应添加到域文件:
responses:
utter_ask_rephrase:
- text: "Sorry, I'm not sure I understand. Could you rephrase it?"
- text: "Could you please rephrase your message? I didn't quite get that."
在添加所有相关的部分之后,该操作现在可以查询知识库。
如何工作(How It Works)
ActionQueryKnowledgeBase
查看请求中提取的实体以及之前设置的插槽,以决定要查询的内容。
根据对象查询知识库–Query the Knowledge Base for Objects
为了查询任何类型对象的知识库,用户的请求需要包含对象类型。让我们看一个例子:
你能说出一些餐馆的名字吗?
这个问题包括感兴趣的对象类型:“restaurant
”。bot需要获取这个实体以形成一个查询-否则操作将不知道用户感兴趣的对象是什么。
当用户说:
我在柏林有什么意大利餐馆可供选择?
用户希望获得一份餐厅列表,其中:(1)有意大利料理;(2)位于柏林。如果NER
(Named Entity Recognition
,命名实体识别)在用户的请求中检测到这些属性,该操作将使用这些属性来筛选知识库中找到的餐馆。
为了让bot检测这些属性,您需要在NLU数据中将“意大利料理”和“柏林”标记为实体:
intents:
- intent: query_knowledge_base
examples: |
- What [Italian](cuisine) [restaurant](object_type) options in [Berlin](city) do I have?.
属性的名称,“cuisine
”和“city
”应该与知识库中使用的名称相同。您还需要将这些作为实体和插槽添加到域文件中。
根据对象的某一属性查询知识库–Query the Knowledge Base for an Attribute of an Object
如果用户想要获得关于某个对象的特定信息,那么请求应该同时包含感兴趣的对象和属性。例如,如果用户提出如下问题:
柏林Burrito公司的菜式是什么?
用户希望获得餐厅“柏林Burrito公司”(兴趣对象)的“cuisine”(兴趣属性)。
感兴趣的属性和对象应标记为NLU训练数据中的实体:
intents:
- intent: query_knowledge_base
examples: |
- What is the [cuisine](attribute) of [Berlin Burrito Company](restaurant)?
确保将对象类型“restaurant
”作为实体和插槽添加到domain.yml
文件中。
Resolve Mentions
按照上面的例子,用户可能并不总是用他们的名字来指代餐馆。用户可以通过名称引用感兴趣的对象,例如“柏林Burrito公司”(对象的表示字符串),也可以通过提及引用先前列出的对象,例如:
你提到的第二家餐馆的菜是什么?
我们的action是能够解决这些提到的实际对象在知识库。更具体地说,它可以解析两种mention
类型:(1)有序提及,如“第一个”;(2)无序提及,如“它”或“那一个”。
有序提及(Ordinal Mentions)
当用户根据对象在列表中的位置引用对象时,称为顺序引用。举个例子:
- 用户:你知道柏林的什么餐馆?
- 机器人:找到了“餐厅”类型的以下对象:1:I due forni 2:PastaBar 3:Berlin Burrito Company
- 用户:第一个有外面的座位吗?
用户用“第一个”这个词来称呼“I due forni”。其他顺序提到可能包括“第二个”、“最后一个”、“任何”或“3”。
有序提及通常在向用户呈现对象列表时使用。为了解决对实际对象的提到,我们使用在KnowledgeBase
类中设置的序号提及映射。默认映射如下所示:
{
"1": lambda l: l[0],
"2": lambda l: l[1],
"3": lambda l: l[2],
"4": lambda l: l[3],
"5": lambda l: l[4],
"6": lambda l: l[5],
"7": lambda l: l[6],
"8": lambda l: l[7],
"9": lambda l: l[8],
"10": lambda l: l[9],
"ANY": lambda l: random.choice(l),
"LAST": lambda l: l[-1],
}
有序提及映射将字符串(如“1”)映射到列表中的对象,例如lambda l:l[0]
,表示索引0处的对象。
例如,由于序数提及映射不包含“第一个”的条目,因此使用实体同义词将NLU数据中的“第一个”映射到“1”是很重要的:
intents:
- intent: query_knowledge_base
examples: |
- Does the [first one]{entity: "mention", value": 1} have [outside seating]{entity: "attribute", value": "outside-seating"}
NER
(命名实体识别)将“first one”检测为mention
实体,但将“1”放入mention
槽中。因此,我们的操作可以将mention
槽与顺序mention
映射结合起来,将“第一个”解析为实际对象“I due forni
”。
您可以通过调用KnowledgeBase
中实现的函数set_ordinal_mention_mapping()
覆盖序数提及映射(请参见自定义InMemoryKnowledgeBase)。
其它提及(Other Mentions)
请看以下对话:
- 用户:PastaBar的菜肴是什么?
- 机器人:PastaBar有意大利菜。
- 用户:有wifi吗?
- 机器人:是的。
- 用户:你能给我一个地址吗?
在问题“Does it have wifi?”中,用户使用“it”一词来指代“PastaBar”。如果NER检测到实体mention
的“it”,那么知识库操作将把它解析为会话中最后提到的对象“PastaBar”。
在下一个输入中,用户间接引用对象“PastaBar”,而不是显式地提及它。知识库操作将检测到用户想要获取特定属性的值,在本例中是地址。如果NER未检测到任何mention
或对象,则该操作假定用户引用的是最近提及的对象“PastaBar”。
初始化操作时,可以通过将use_last_object_mention
设置为False
来禁用此行为。
自定义–Customization
Customizing ActionQueryKnowledgeBase
如果您想自定义bot对用户说的话,可以覆盖ActionQueryKnowledgeBase
的以下两个函数:
utter_objects()
utter_attribute_value()
当用户请求对象列表时,使用utter_objects()
。一旦bot从知识库中检索到对象,默认情况下,它将向用户发送一条消息,格式如下:
找到“餐厅”类型的以下对象:1: I due forni 2: PastaBar 3: Berlin Burrito Company
或者,如果找不到对象,
我找不到任何“餐馆”类型的东西。
如果要更改话语格式,可以重写操作中的方法utter_objects()
。
函数utter_attribute_value()
确定当用户请求有关对象的特定信息时bot发出的信息。
如果在知识库中找到感兴趣的属性,bot将用以下语句进行响应:
“Berlin Burrito Company”的“cuisine”属性值为“墨西哥菜”。
如果找不到请求属性的值,bot将用
找不到对象“Berlin Burrito Company”的属性“cuisine”的有效值。
如果要更改bot语句,可以重写方法utter_attribute_value()
。
注意
在我们的博客上有一个关于如何在自定义操作中使用知识库的教程。本教程详细解释了ActionQueryKnowledgeBase
背后的实现。
Creating Your Own Knowledge Base Actions
ActionQueryKnowledgeBase
应该允许您轻松开始将知识库集成到操作中。但是,该操作只能处理两种用户请求:
- 用户希望从知识库中获取对象列表
- 用户希望获取特定对象的属性值
操作无法比较对象或考虑知识库中对象之间的关系。此外,在谈话中将任何mention
解析为最后提到的对象可能并不总是最佳的。
如果您想处理更复杂的用例,可以编写自己的自定义操作。我们向rasa_sdk.knowledge_base.utils ()
添加了一些帮助函数link to code,以帮助您实现自己的解决方案。我们建议使用KnowledgeBase
接口,以便您仍然可以在新的自定义操作的同时使用ActionQueryKnowledgeBase
。
如果你写了一个知识库操作来处理上面的一个用例或者一个新的用例,一定要在论坛上告诉我们!
Customizing the InMemoryKnowledgeBase
类InMemoryKnowledgeBase
继承KnowledgeBase
。您可以通过重写以下函数自定义InMemoryKnowledgeBase
:
-
get_key_attribute_of_object()
:为了跟踪用户最后谈论的对象,我们将key属性的值存储在一个特定的槽中。每个对象都应该有一个唯一的键属性,类似于关系数据库中的主键。默认情况下,每个对象类型的键属性的名称都设置为id
。通过调用get_key_attribute_of_object()
,可以重写特定对象类型的键属性的名称。 -
get_representation_function_of_object()
:让我们关注以下餐厅:{ "id": 0, "name": "Donath", "cuisine": "Italian", "outside-seating": true, "price-range": "mid-range" }
当用户要求bot列出任何意大利餐馆时,它不需要餐馆的所有细节。相反,您希望提供一个有意义的名称来标识餐厅——在大多数情况下,对象的名称就可以了。函数
get_representation_function_of_object()
返回一个lambda
函数,该函数将上述餐厅对象映射到其名称。lambda obj: obj["name"]
每当bot谈论一个特定的对象时,就使用这个函数,这样用户就可以得到一个有意义的对象名。
默认情况下,lambda函数返回对象的“name”属性的值。如果对象没有“name
”属性,或者对象的“name
”不明确,则应通过调用set_representation_function_of_object()
为该对象类型设置新的lambda函数。 -
set_ordinal_mention_mapping()
:将ordinal mention
(如“第二个”)解析为列表中的对象需要ordinal mention mapping
(有序提及映射)。默认情况下,ordinal mention mapping
如下所示:{ "1": lambda l: l[0], "2": lambda l: l[1], "3": lambda l: l[2], "4": lambda l: l[3], "5": lambda l: l[4], "6": lambda l: l[5], "7": lambda l: l[6], "8": lambda l: l[7], "9": lambda l: l[8], "10": lambda l: l[9], "ANY": lambda l: random.choice(l), "LAST": lambda l: l[-1], }
您可以通过调用函数
set_ordinal_mention_mapping()
来覆盖它。如果您想了解有关如何使用此映射的更多信息,请查看Resolve Mentions。
有关InMemoryKnowledgeBase
的示例实现,请参见示例,该InMemoryKnowledgeBase
使用方法set_representation_function_of_object()
覆盖对象类型“hotel
”的默认表示形式。InMemoryKnowledgeBase
本身的实现可以在rasa-sdk包中找到。
Creating Your Own Knowledge Base
如果您有更多的数据,或者想要使用更复杂的数据结构(例如,涉及不同对象之间的关系),则可以创建自己的知识库实现。只需继承KnowledgeBase
并实现方法get_objects()
、get_object()
和get_attributes_of_object()
。知识库代码提供了关于这些方法应该做什么的更多信息。
您还可以通过调整定制InMemoryKnowledgeBase
一节中提到的方法,进一步定制知识库。