这是一个很好的问题,在设计好的对话时需要考虑。
我将忽略数据库查询,因为听起来您可以控制这部分,并且它与会话部分没有直接关系。
重述问题
要重新表述您想要做的事情,您希望以相同的方式处理以下所有短语:
- 2018年有多少人?
- 2019年,有多少人?
- 有多少人?
同样,这些都应该是相同的:
- 2018年有多少男性和女性?
- 2017年,男女一共有多少?
- 有多少男性和女性?
- 你能按男人和女人来分解吗?
ie - 用户应该能够提出问题,既可以指定年份,也可以不指定年份。
在对话期间,如果他们指定了年份,则该值应该是为进一步问题假定的值,除非他们指定了不同的年份。这是很自然的——人类在我们的对话中建立了短期的上下文。
这些还有一个您没有在问题中提出的问题 - 如果他们尚未指定年份,如何处理问题。好的对话设计会建议你询问年份,然后执行最初提出的问题。所以对话可能是这样的:
User: How many people are there?
Agent: What year would you like that for?
User: 2018
Agent: There were 2 people in 2018
总体方法:上下文
幸运的是,Dialogflow 直接通过Contexts支持这个概念。这使您可以在用户的语句之间保存参数,因此这是存储年份以供以后问题的好方法。
您还可以制作仅在特定上下文处于活动状态时才能触发的意图。这对于确保哪些 Intent 在对话的某些部分有意义是很有用的。
对于这类问题,有两种使用上下文的好方法。尽管每个都有取舍,但您使用哪一个很大程度上取决于个人风格。如果我们还没有年份,您还可以使用上下文来支持您需要询问年份的场景。
方法 1:每个问题的单一意图
使用此方案,您将拥有一个响应用户的每个问题的 Intent。您的履行将查看是否设置了年份参数,如果设置了,则使用该参数作为年份并将其设置在上下文的参数中。如果未设置 - 然后使用上下文中参数的值。
所以我们的“askPeople”意图可能有我们上面谈到的短语:
- [年] 有多少人?
- [年],有多少人?
- 有多少人?
我们将“年”定义@sys.number-integer
为例如的参数。(其他实体类型也是可能的,但现在就可以了。)
如果您使用 dialogflow-fulfillment 库,我们的处理函数可能如下所示:
function askPeople( agent ){
// Try to get the year from the context first
let context = agent.context.get('RememberYear');
let year = context && context.parameters && context.parameters.year;
// If we don't have a year, get it from the phrase parameters
year = year || agent.parameters.year;
if( year ){
// If we do have a value, get the result, reply,
// and save the year in the context.
return getPeople( year )
.then( count => replyPeople( agent, year, count ) );
} else {
// We don't have a value
// FIXME: We'll discuss what to do about this below
}
}
您需要编写getPeople()
函数以返回一个 Promise,该 Promise 解析为您的数据库中的结果。replyPeople()
我也故意拉出了这个功能。它可能看起来像这样
function replyPeople( agent, year, count ){
agent.context.set({
name: 'RememberYear',
lifespan: 99,
parameters:{
year: year
}
});
agent.add( `The total count for ${year} was ${count}.` );
}
方法 2:每个问题的多个意图
有了这个,我们将有两个不同的意图来处理这个问题。一个接受带有年份的短语,而另一个则处理不带年份的短语。最大的区别在于,训练短语中没有年份的将需要设置“RememberYear”上下文才能触发。
基本意图(“askPeopleYear”)是相当熟悉的训练短语,例如
而“年份”参数的定义方式与上述相同。
我们的另一个意图(“askPeopleNoYear”)将设置“RememberYear”的输入上下文并有一个训练短语,例如
并且不会有任何参数。
如果没有设置“RememberYear”上下文,我们可能需要第三个 Intent,或者至少是额外的方法来处理,但他们会说那个短语。我们将在下面讨论这个。
实现代码需要两个不同的处理函数,看起来像这样
function askPeopleYear( agent ){
// We know the year is in the parameter
let year = agent.parameters.year;
// Get the result, reply, and set the context
return getPeople( year )
.then( count => replyPeople( agent, year, count ) );
}
function askPeopleNoYear( agent ){
// We know the year is in the context
let context = agent.context.get('RememberYear');
let year = context && context.parameters && context.parameters.year;
// Get the result, reply, and set the context
return getPeople( year )
.then( count => replyPeople( agent, year, count ) );
}
getPeople()
和函数将replyPeople()
与我们之前的方法相同。
处理无年份设置
那么如果他们说“有多少人”,但我们没有设置“RememberYear”上下文的值,会发生什么?
在第一种方法中,这达到了else
我们用“FIXME”标记的条件。在第二种方法中,如果我们不放置其他东西,则会触发一个 Fallback Intent,这并不能真正帮助用户。
无论哪种情况,我们应该做的是询问用户他们想要的年份并创建一个 Intent 来捕获年份。由于我们可能需要针对不同的问题类型执行此操作,因此我们还应该将要执行的函数存储在……您猜对了……上下文中。因此,假设我们设置了一个“NeedsYear”上下文,并带有一个名为“functionName”的参数来跟踪我们需要调用哪个函数。
此 Intent(我们将其命名为“provideYear”)将需要“NeedsYear”输入上下文,并且可能具有训练短语,例如:
并取一个“年份”参数,与我们上面定义的相同。(我们甚至可以将其标记为必需。)
这个处理程序可能看起来像
function provideYear( agent ){
// We know we have the parmeter
let year = agent.parameters.year;
// We also know we have the context with the function name
let context = agent.context.get('NeedsYear');
let functionName = context && context.parameters && context.parameters.functionName;
// We should clear this context, since we no longer need the year
agent.context.set({
name: 'NeedsYear',
lifespan: 0
};
// And then call whichever function is appropriate
if( functionName === 'people' ){
return getPeople( year )
.then( count => replyPeople( agent, year, count ) );
} else if( functionName === 'gender' ){
// ...
}
}
概括
使用上下文。
上下文是可以触发 Intent 的良好守门人,也是在对话轮次之间存储值的好方法。
上下文是强大的。