使用nodejs BOT SDK开发问答类技能模板
DuerOS-王超 发布于2018-10-17 12:19 浏览:1467 回复:2
1
收藏

问答技能模板

问答技能模板是针对问答类技能设计的模板,如知识问答、生活常识问题等。本文从问答类技能交互、部署讲述如何快速搭建问答类技能。

问答技能模板的交互模型

问答类技能与用户的交互方式是技能从题库列表中选出一道题,并提供四个选项,其中有一个选项是正确的。用户通过说出正确选项的顺序来答题。

下面以古诗问答技能为例,描述问答类技能与用户交互过程。技能从古诗列表中选取一首诗让用户说出诗的作者,并依次读出四个作者选项。用户说出正确的作者的选项顺序,回答正确后技能会记录用户得分。技能交互过程如下:

用户:打开古诗问答 

技能:[技能欢迎语]。开始答题。第一题:离离原上草 一岁一枯荣的作者是谁。 1,李白 2,白居易 3,杜甫 4,柳宗元 

用户:第二个 

技能:回答正确,得一分。目前的积分是1分。第二题。。。

技能从题库中选取问题,可以杜绝题目重复和答案排列顺序重复的问题。开发者只需要更新题目列表和相应的技能配置信息,即可生成新的技能并在DuerOS DBP平台上发布。题库的存储方式如下,其中正确答案需要放在答案的第一个位置,格式如下。

{
  '题目': [
    '正确答案',
    '错误答案',
    '错误答案',
    '错误答案',
  ],
},

上面古诗问答的例子在题库中的展现形式是:

{
  '第一题:离离原上草 一岁一枯荣的作者是谁。': [
    '白居易',
    '李白',
    '杜甫',
    '柳宗元',
  ],
  '第二题:白日依山尽,黄河入海流,出自那首诗。': [
    '登鹳雀楼',
    '...',
    '...',
    '...',
  ],
},

其中正确答案“白居易”和“登鹳雀楼”放在第一个位置,但是在给用户出题时,选项的顺序会调整。

模板的使用说明

  1. 答案中必须有一个正确答案。
  2. 每道题的答案选项可以是3个,4个,5个,选项总数不收限制。
  3. 每个题目必须通过选项序号来作答,如用户必须说“第一个”、“第二个”,不能使用“是”、“对”、“错”、“不是”等进行回答。

使用模板开发技能的流程

请注意,下面的新建技能和配置意图过程可以通过在技能平台-->创建技能-->引用技能-->导入技能页面导入 http://dbp-cfc.cdn.bcebos.com/download/trivia.zip 实现。

新建技能

新建技能详情请参阅自定义技能创建

配置意图

意图配置详情请参阅意图、常用表达和槽位

问答技能模板需要创建两个意图,分别是回答问题意图和重新开始问答意图。 回答问题意图如下图所示:

回答问题意图

重新开始问答意图如下图所示:

重新开始问答意图

配置技能服务部署

问答技能模板使用CFC部署技能服务。使用CFC部署技能服务详情请参阅 百度云CFC

修改CFC函数代码

问答技能模板使用questions.js配置题库。开发者需要下载技能CFC函数完整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);
    }
}

测试技能

至此,问答技能就开发完成了。开发者可以在技能开放平台的模拟测试页面对技能进行测试。

丰富技能功能

问答技能模板只是实现了问答技能的基础功能。开发者可以在模板的基础上对技能进行更多的完善。如:

  • 增加更多意图,比如放弃当前问题意图,下一题意图,帮助意图等等
  • 使用数据库或其他方式存储用户信息和得分,以便增加更多功能。如用户抽奖功能,或者分享功能。
收藏
点赞
1
个赞
共2条回复 最后由kusoft8181回复于2019-05-12 00:00
#2大象无形回复于2018-11-13

好啊😁 

0
#3kusoft8181回复于2019-05-12

第一张图,回答问题意图的设置,显示不完整

0
TOP