使用nodejs BOT SDK开发问答类技能模板
2019.09.09 09:01浏览量:5740简介:问答技能模板 问答技能模板是针对问答类技能设计的模板,如知识问答、生活常识问题等。本文从问答类技能交互、部署讲述如何快速搭建问答类技能。 &
问答技能模板
问答技能模板是针对问答类技能设计的模板,如知识问答、生活常识问题等。本文从问答类技能交互、部署讲述如何快速搭建问答类技能。
问答技能模板的交互模型
问答类技能与用户的交互方式是技能从题库列表中选出一道题,并提供四个选项,其中有一个选项是正确的。用户通过说出正确选项的顺序来答题。
下面以古诗问答
技能为例,描述问答类技能与用户交互过程。技能从古诗列表中选取一首诗让用户说出诗的作者,并依次读出四个作者选项。用户说出正确的作者的选项顺序,回答正确后技能会记录用户得分。技能交互过程如下:
用户:打开古诗问答
技能:[技能欢迎语]。开始答题。第一题:离离原上草 一岁一枯荣的作者是谁。 1,李白 2,白居易 3,杜甫 4,柳宗元
用户:第二个
技能:回答正确,得一分。目前的积分是1分。第二题。。。
技能从题库中选取问题,可以杜绝题目重复和答案排列顺序重复的问题。开发者只需要更新题目列表和相应的技能配置信息,即可生成新的技能并在DuerOS DBP平台上发布。题库的存储方式如下,其中正确答案需要放在答案的第一个位置,格式如下。
{
'题目': [
'正确答案',
'错误答案',
'错误答案',
'错误答案',
],
},
上面古诗问答的例子在题库中的展现形式是:
{
'第一题:离离原上草 一岁一枯荣的作者是谁。': [
'白居易',
'李白',
'杜甫',
'柳宗元',
],
'第二题:白日依山尽,黄河入海流,出自那首诗。': [
'登鹳雀楼',
'...',
'...',
'...',
],
},
其中正确答案“白居易”和“登鹳雀楼”放在第一个位置,但是在给用户出题时,选项的顺序会调整。
模板的使用说明:
答案中必须有一个正确答案。
每道题的答案选项可以是3个,4个,5个,选项总数不收限制。
每个题目必须通过选项序号来作答,如用户必须说“第一个”、“第二个”,不能使用“是”、“对”、“错”、“不是”等进行回答。
使用模板开发技能的流程
请注意,下面的新建技能和配置意图过程可以通过在技能平台-->创建技能-->引用技能-->导入技能页面导入 http://dbp-cfc.cdn.bcebos.com/download/trivia.zip 实现。
新建技能
新建技能详情请参阅自定义技能创建
配置意图
意图配置详情请参阅意图、常用表达和槽位
问答技能模板需要创建两个意图,分别是回答问题意图和重新开始问答意图。 回答问题意图如下图所示:
重新开始问答意图如下图所示:
配置技能服务部署
问答技能模板使用CFC部署技能服务。使用CFC部署技能服务详情请参阅 百度云CFC
修改CFC函数代码
问答技能模板使用questions.js配置题库。开发者需要下载技能CFC函数完整zip程序包到本地进行开发,开发完成后上传函数zip包进行发布。具体流程如下:
- 在CFC控制台通过模板创建函数, 选择node.js DuerOS Bot SDK模板
- 函数生成后,在函数控制台点击
点击下载完整 ZIP 程序包
链接下载程序包 - 在本地解压程序包
- 将https://github.com/dueros/bot-sdk-node.js/blob/master/samples/trivia/questions.js文件拷贝到程序包文件夹中
- 使用https://github.com/dueros/bot-sdk-node.js/blob/master/samples/trivia/index.js替换程序包文件夹中的index.js文件
- 将程序包文件夹中的所有文件重新打包成zip文件
- 在函数控制台上传zip程序包并保存
CFC操作说明请参阅函数计算 CFC
完整代码
const Bot = require('bot-sdk');
const privateKey = require("./rsaKeys.js").privateKey;
const question_list = [
{
'远上还山石径斜,白云深处有人家': [
'正确的',
'杜甫',
'白居易',
'李白',
],
},
{
'离离原上草,一岁一枯荣': [
'正确的',
'杜甫',
'白居易',
'李白',
],
},
{
'举头望明月,低头思故乡': [
'正确的',
'杜甫',
'白居易',
'李白',
],
}, {
'锄禾日当午 汗滴禾下土': [
'正确的',
'杜甫',
'白居易',
'李白',
],
},
{
'白日依山尽,黄河入海流': [
'正确的',
'杜甫',
'白居易',
'李白',
],
},
{
'李白乘舟将欲行,忽闻岸上踏歌声': [
'正确的',
'杜甫',
'白居易',
'李白',
],
},
{
'横看成岭侧成峰,远近高低各不同': [
'正确的',
'杜甫',
'白居易',
'李白',
],
},
{
'人生自古谁无死,留取丹心照汗青': [
'文天祥',
'杜甫',
'白居易',
'李白',
],
}
];
//定义一轮问答中的问题数量
const GAME_LENGTH = 5;
//定义每个问题的答案数量
const ANSWER_COUNT = 3;
class InquiryBot extends Bot {
constructor(postData) {
super(postData);
this.addLaunchHandler(() => {
this.waitAnswer();
let speechOutput = '欢迎来到古诗问答。我将念两句古诗并给你三个诗人的名字。需要你告诉我哪一个是正确的作者。';
//初始化一轮中的问题列表和第一题的话术
let repromptText = this.startNewGame();
let card = new Bot.Card.TextCard(repromptText);
return {
card: card,
outputSpeech: speechOutput + repromptText
};
});
this.addSessionEndedHandler(() => {
this.endSession();
return {
outputSpeech: '谢谢使用!'
};
});
this.addIntentHandler('answer_intent', () => {
this.waitAnswer();
//确保获取到了用户的回答
let theAnswer = this.getSlot('theAnswer');
if (!theAnswer) {
this.nlu.ask('theAnswer');
return {
outputSpeech: '您的答案是哪个?'
};
}
//获取session中相关信息
let questionsList = this.getSessionAttribute('questionsList');
let score = this.getSessionAttribute('score');
let currentQuestionIndex = this.getSessionAttribute('currentQuestionIndex');
let correctAnswerIndex = this.getSessionAttribute('correctAnswerIndex');
let gameQuestions = this.getSessionAttribute('gameQuestions');
let correctAnswerText = this.getSessionAttribute('correctAnswerText');
let speechOutput = '';
if (theAnswer == correctAnswerIndex){
score += 1;
speechOutput = '回答正确,得一分。目前得分:' + score + '分。';
}else{
speechOutput = '很遗憾,回答错误。正确答案是' + correctAnswerText + '.目前得分:' + score + '分。';
}
//到达最后一题,用户选择重新开始一轮或者退出技能
if (currentQuestionIndex == GAME_LENGTH - 1){
speechOutput += '已经是最后一题了。您可以说重新开始来继续答题,或者说退出来退出技能。'
return {
outputSpeech: speechOutput
};
}
//获取下一题信息
currentQuestionIndex += 1;
correctAnswerIndex = Math.floor(Math.random() * (ANSWER_COUNT));
let spokenQuestion = Object.keys(questionsList[gameQuestions[currentQuestionIndex]])[0];
let roundAnswers = this.populateRoundAnswers(gameQuestions, currentQuestionIndex,correctAnswerIndex,questionsList);
let questionIndexForSpeech = currentQuestionIndex + 1;
let repromptText = '第' + questionIndexForSpeech + '题:\n' + spokenQuestion + '\n';
for (let i = 0; i < ANSWER_COUNT; i += 1) {
repromptText += `${i + 1}. ${roundAnswers[i]}. `;
}
speechOutput += repromptText;
let currentQuestion = questionsList[gameQuestions[currentQuestionIndex]];
this.setSessionAttribute('speechOutput',speechOutput);
this.setSessionAttribute('currentQuestionIndex',currentQuestionIndex);
this.setSessionAttribute('correctAnswerIndex',correctAnswerIndex + 1);
this.setSessionAttribute('gameQuestions',gameQuestions);
this.setSessionAttribute('questionsList',questionsList);
this.setSessionAttribute('score',score);
this.setSessionAttribute('correctAnswerText',currentQuestion[Object.keys(currentQuestion)[0]][0]);
let card = new Bot.Card.TextCard(repromptText);
return {
card: card,
outputSpeech: speechOutput
};
});
//重新开始答题,得分清零
this.addIntentHandler('newGame_intent', () => {
this.waitAnswer();
//初始化一轮中的问题列表和第一题的话术
let repromptText = this.startNewGame();
let card = new Bot.Card.TextCard(repromptText);
return {
card: card,
outputSpeech: '好的,重新开始。' + repromptText
};
});
/*
* 获取没有被意图解析的用户输入,并进行相关处理
* 缺省意图 https://developer.dueros.baidu.com/didp/doc/dueros-bot-platform/dbp-nlu/defaultIntent_markdown
*/
this.addIntentHandler('ai.dueros.common.default_intent', () => {
this.waitAnswer();
return {
outputSpeech: '您可以对我说第几个来告诉我您的答案。您也可以说重新开始重新玩,或者说退出来退出游戏。'
};
});
}
/**
* 获取新一轮问题列表和相应的信息,并将信息存入session中
*
* @return 新一轮答题话术
*/
startNewGame() {
let questionsList = question_list;
let gameQuestions = this.populateGameQuestions(questionsList);
let correctAnswerIndex = Math.floor(Math.random() * (ANSWER_COUNT));
console.log(correctAnswerIndex);
let roundAnswers = this.populateRoundAnswers(gameQuestions, 0,correctAnswerIndex,questionsList);
let currentQuestionIndex = 0;
let spokenQuestion = Object.keys(questionsList[gameQuestions[currentQuestionIndex]])[0];
let repromptText = '第1题:\n' + spokenQuestion + '\n';
for (let i = 0; i < ANSWER_COUNT; i += 1) {
repromptText += `${i + 1}. ${roundAnswers[i]}. `;
}
let currentQuestion = questionsList[gameQuestions[currentQuestionIndex]];
this.setSessionAttribute('currentQuestionIndex',currentQuestionIndex);
this.setSessionAttribute('correctAnswerIndex',correctAnswerIndex + 1);
this.setSessionAttribute('gameQuestions',gameQuestions);
this.setSessionAttribute('questionsList',questionsList);
this.setSessionAttribute('score',0);
this.setSessionAttribute('correctAnswerText',currentQuestion[Object.keys(currentQuestion)[0]][0]);
return repromptText;
}
/**
* 从问题列表中随机抽取问题。问题个数由变量GAME_LENGTH定义
* @param {list} translatedQuestions 所有问题列表
* @return 问题id列表
*/
populateGameQuestions(translatedQuestions) {
let gameQuestions = [];
let indexList = [];
let index = translatedQuestions.length;
if (GAME_LENGTH > index) {
throw new Error('Invalid Game Length.');
}
for (let i = 0; i < translatedQuestions.length; i += 1) {
indexList.push(i);
}
for (let j = 0; j < GAME_LENGTH; j += 1) {
let rand = Math.floor(Math.random() * index);
index -= 1;
let temp = indexList[index];
indexList[index] = indexList[rand];
indexList[rand] = temp;
gameQuestions.push(indexList[index]);
}
return gameQuestions;
}
/**
* 从问题列表中随机抽取问题。问题个数由变量GAME_LENGTH定义
* @param {list} gameQuestionIndexes 一轮问答中问题id列表
* @param {int} currentQuestionIndex 当前问题Index
* @param {int} correctAnswerTargetLocation 当前问题答案Index
* @param {list} translatedQuestions 所有问题列表
* @return 当前问题答案选项列表
*/
populateRoundAnswers(gameQuestionIndexes,currentQuestionIndex,correctAnswerTargetLocation,translatedQuestions) {
const answers = [];
const translatedQuestion = translatedQuestions[gameQuestionIndexes[currentQuestionIndex]];
const answersCopy = translatedQuestion[Object.keys(translatedQuestion)[0]].slice();
let index = answersCopy.length;
if (index < ANSWER_COUNT) {
throw new Error('Not enough answers for question.');
}
// 打乱当前问题答案列表顺序
for (let j = 1; j < answersCopy.length; j += 1) {
const rand = Math.floor(Math.random() * (index - 1)) + 1;
index -= 1;
const swapTemp1 = answersCopy[index];
answersCopy[index] = answersCopy[rand];
answersCopy[rand] = swapTemp1;
}
// 将正确答案放置到correctAnswerTargetLocation的位置
for (let i = 0; i < ANSWER_COUNT; i += 1) {
answers[i] = answersCopy[i];
}
const swapTemp2 = answers[0];
answers[0] = answers[correctAnswerTargetLocation];
answers[correctAnswerTargetLocation] = swapTemp2;
return answers;
}
}
exports.handler = function(event, context, callback) {
try {
let b = new InquiryBot(event);
// 0: debug 1: online
b.botMonitor.setEnvironmentInfo(privateKey, 0);
b.run().then(function(result) {
callback(null, result);
}).catch(callback);
} catch (e) {
callback(e);
}
}
测试技能
至此,问答技能就开发完成了。开发者可以在技能开放平台的模拟测试页面对技能进行测试。
丰富技能功能
问答技能模板只是实现了问答技能的基础功能。开发者可以在模板的基础上对技能进行更多的完善。如:
增加更多意图,比如放弃当前问题意图,下一题意图,帮助意图等等
使用数据库或其他方式存储用户信息和得分,以便增加更多功能。如用户抽奖功能,或者分享功能。
发表评论
登录后可评论,请前往 登录 或 注册