/ 产品和服务

多轮对话设计器: 实现天气查询机器人的过程

随着语音识别(ASR)和文字转语音(TTS)技术的成熟,语音交互逐渐成为人机交互重要方式,AI音箱开始流行,各种手机厂商也推出手机智能语音助手,在消费者领域,各种技能快速增长。另外一方面,消费者也趋于使用即时通讯软件来取代电话和短信。

2016-2018年亚马逊Alexa技能数量增长图
2016-2018年亚马逊Alexa技能数量增长图

在企业中,客服系统、工作台和办公软件也逐渐走向更加智能化和自动化的方案。不管是中小型企业或者大型公司,都在踊跃尝试AI技术飞跃带来的福利。企业IT系统将迎来更多升级,机器学习会刷新每一个软件。图片,视频和文本是人工智能技术重点发挥优势的地方。人机对话系统将迎来革命,企业会普遍应用对话应用,一种更快捷,更智能的服务方式:使用对话机器人完成业务流程,比如智能会签、智能请假、智能审批和智能客服等。

但是目前,企业实现对话应用的方式还很笨拙:

1)上线周期长:由于缺少实践经验,难以掌握人工智能技术的边界,往往难以做好功能设计,在开始后还会返工。

2)需要一个华丽的团队:需要招聘目前紧俏的AI产品经理、机器学习工程师和自然语言处理工程师,亦或是标注人员和测试人员。

3)需要接入第三方服务:比如语音识别、文字到语音和机器学习硬件依赖等。

然而,在Chatopera,我们回顾一些对话应用,比如机器人客服场景时,和机器人相关的模块可以独立为一个产品,经过了仔细的分析,我们认为,可以开发两个产品:多轮对话设计器智能问答引擎。前者面向企业的业务人员,撰写聊天机器人脚本;后者面向企业的IT人员,是问答服务的运行环境。

本文重点介绍使用多轮对话设计器实现“天气查询机器人”的过程,我们选择这个场景并不是因为它简单,而是因为它容易理解,使用多轮对话设计器可以实现更复杂,更有价值的应用。我们先一睹为快,这个机器人是什么样子的。

天气查询机器人Demo

是不是很实用?如果你掌握了下面的技巧,它可以帮助做更多的事情。

需求分析

首先,我们需要梳理一下需求:

  1. 我想知道任意城市的天气信息,比如“今天上海天气怎么样”;
  2. 我还比较关心空气,我可以通过“今天上海空气怎么样”获得空气质量信息;
  3. 我想知道今天适不适合户外运动,就问“今天上海适合运动么”;
  4. 如果我问了一个城市的天气状况,我还想继续询问这个城市更多信息,这样我不用每次都告诉机器人城市名称。

当然,我的每个意图都有多种表述方式,机器人能支持一些变化的问法。如果我的问题不够严谨,机器人还应该提醒我合理的表达。

准备开发环境

下载多轮对话设计器

多轮对话设计器的下载地址是:
https://www.chatopera.com/product/conversation-designer

如果您打开后,下载按钮显示未“待发步”,是因为我们还在紧张的测试,预计2018年7月21日可以发布v1.0版本,请填写“建立联系”表单,我们发布后第一时间与您联系。

多轮对话设计器下载页面
多轮对话设计器下载页面

目前,多轮对话设计器支持的操作系统包括Windows和Mac OSX,在安装过程和启动过程中,如果遇到问题请通过微信公众号找到我们:

微信公众号:获得支持和帮助
微信公众号:获得支持和帮助

调研提供天气信息查询的API

现在很多服务以API的形式提供,从搜索引擎中查找“天气查询服务 API”,我们就能得到一些供应商,经过一些比较,我选择了和风天气,它数据丰富,免费额度大方。

Screen-Shot-2018-07-10-at-6.29.45-PM

AI音箱

Chatopera与Sayinfo达成战略合作关系,所以,我们的对话系统产品与任你说音箱可以直接集成。

Screen-Shot-2018-07-10-at-6.32.08-PM

第一条规则

第一次打开多轮对话设计器后,我们看到如下的面板,我们称之为主面板

Screen-Shot-2018-07-10-at-6.03.56-PM

点击新建按钮,弹出创建机器人的表单:

Screen-Shot-2018-07-10-at-6.05.48-PM

填入“小叮当”,当前多轮对话设计器支持中文(zh_CN)和英文(en_US),我们选择“zh_CN”,点击“确认”。然后我们就得到了一个聊天机器人。

Screen-Shot-2018-07-10-at-6.10.06-PM

在操作中,有几个按钮:

  • 管理:管理聊天机器人的多轮对话。
  • 版本管理:管理不同版本的机器人,导出机器人和在不同版本之间进行比对。
  • 环境变量:机器人函数中依赖的全局变量,这些变量在“设计对话”的阶段和在IT人员“部署到生产环境”下的值是不同的,比如一些接口服务的认证键值对。
  • 发布:发布当前机器人为最新版本。
  • 删除:将机器人删除。

点击“管理”,进入多轮对话管理页面,点击“新建对话”,在弹出的窗口中,填写“对话名称”为“weather”,点击“确认”。这时,我们看到了新建的对话,我们将修改它的内容完成天气对话服务。

Screen-Shot-2018-07-10-at-6.16.54-PM

点击“编辑”,进入对话编辑窗口。在左侧的“脚本区域”,写下第一条规则。

+ 今天 (*) 天气 [怎么样]
- {keep} <cap1>天气挺好的

点击“保存”,这时右侧的“逻辑区域”有了变化,出现了一个线条,在线条左右两端分别是问题和答案。在“对话区域”,我们输入“今天北京天气怎么样”,点击发送,这时机器人回复了。

Screen-Shot-2018-07-10-at-6.36.00-PM

从我们需求上看,这没什么用,但是它工作了,我们就一点点优化它。对于在这条规则中,我们使用的语法,(*)代表一个槽位,<cap1>代表在回复中取槽位的值,[怎么样]是可有的字符串,{keep}代表这条规则始终生效,keep涉及到对话的状态管理,我们将在文档中提供更多规则的描述,现在,读者看懂本示例就可以了。

添加函数

多轮对话设计器中,怎么请求和风天气的数据呢?使用函数函数是多轮对话支持的使用JavaScript实现的程序。

我们在“对话编辑窗口”点击函数,粘贴如下代码:

var WForewast = function (apiKey) {
    if (!apiKey) throw new Error('Invalid token, get it from http://www.heweather.com/my/service');
    this.key = apiKey;
}


WForewast.prototype.getWeatherByCity = function (city) {
    return new Promise((resolve, reject)=>{
        let url =  "https://free-api.heweather.com/v5/weather?city=" + encodeURIComponent(city) + "&key=" + this.key
        http
            .get(url)
            .then((res)=>{
                resolve(res.data.HeWeather5[0].suggestion);
            })
            .catch(function (err) {
                if (err) return reject(err);
            });
    })
}

const wf = new WForewast('182f1b6826d94c6285a489d2414f3ad0');


exports.getWeatherByCity = function(city, cb){
    debug("getWeatherByCity: %s", city);
    wf.getWeatherByCity(city)
        .then((suggestions)=>{
            cb(null, {
                text: suggestions["comf"]["txt"]
            })
        }, (err)=>{
            debug("error:%j", err)
            cb(null, {
                text: `很抱歉,没有获得${city}的天气信息。`
            })
        })
}

exports.getAirByCity = function(city, cb){
    debug("getAirByCity: %s", city);
    wf.getWeatherByCity(city)
        .then((suggestions)=>{
            cb(null, {
                text: suggestions["air"]["txt"]
            })
        }, (err)=>{
            cb(null, {
                text: `很抱歉,没有获得${city}的空气信息。`
            })
        })
}


exports.getSportByCity = function(city, cb){
    debug("getSportByCity: %s", city);
    wf.getWeatherByCity(city)
        .then((suggestions)=>{
            cb(null, {
                text: suggestions["sport"]["txt"]
            })
        }, (err)=>{
            cb(null, {
                text: `很抱歉,没有获得${city}的信息。`
            })
        })
}

exports.getDresscodeByCity = function(city, cb){
    debug("getDresscodeByCity: %s", city);
    wf.getWeatherByCity(city)
        .then((suggestions)=>{
            cb(null, {
                text: suggestions["drsg"]["txt"]
            })
        }, (err)=>{
            cb(null, {
                text: `很抱歉,没有获得${city}的信息。`
            })
        })
}

在函数中,我们实现了根据城市请求天气、空气质量、着装建议和运动建议的接口,分别是getWeatherByCitygetAirByCitygetDresscodeByCitygetSportByCity

细心的读者会发现,在函数中,多轮对话设计器直接支持了httpdebug作为工具类,发起网络请求和输出日志信息。这两个接口极大的扩展了函数的能力,我们也会在函数中详细描述它们的使用。

然后,回到“脚本区域”,修改一下规则,更新如下:

+ 今天 (*) 天气 [怎么样]
- {keep} ^getWeatherByCity(<cap1>)

在回复中,我们调用了getWeatherByCity,并且传入了城市名称。接着,在“对话区域”,输入“今天北京天气怎么样”,回复与上次不一样了。

Screen-Shot-2018-07-10-at-6.54.12-PM

这次,我们看到了期望的回复,正是从和风天气返回的北京今天的天气状况。

使用环境变量

在上面的函数中,我们有一个敏感的信息:和风天气的API密钥。在实际应用中,我们希望设计阶段部署阶段,它的值是不同的。这时,就需要使用环境变量,环境变量正是为解决这个问题而设计的。

Screen-Shot-2018-07-10-at-7.02.30-PM

回到主面板,在“小叮当”操作中,点击环境变量,创建如下键值对:

Screen-Shot-2018-07-10-at-7.04.06-PM

读者可以从和风天气获得该密钥,为验证用途,可以粘贴下面的值:

        "HEWEATHER_URL": "https://free-api.heweather.com/v5",
        "HEWEATHER_KEY": "182f1b6826d94c6285a489d2414f3ad0"

保存后,回到天气对话脚本的“对话编辑窗口”,在函数中,使用下面的脚本:

/**
 * Plugin
 */

var WForewast = function (apiKey) {
    if (!apiKey) throw new Error('Invalid token, get it from http://www.heweather.com/my/service');
    this.key = apiKey;
}


WForewast.prototype.getWeatherByCity = function (city) {
    return new Promise((resolve, reject)=>{
        let url = config["HEWEATHER_URL"] + "/weather?city=" + encodeURIComponent(city) + "&key=" + this.key
        http
            .get(url)
            .then((res)=>{
                resolve(res.data.HeWeather5[0].suggestion);
            })
            .catch(function (err) {
                if (err) return reject(err);
            });
    })
}

const wf = new WForewast(config["HEWEATHER_KEY"]);


exports.getWeatherByCity = function(city, cb){
    debug("getWeatherByCity: %s", city);
    wf.getWeatherByCity(city)
        .then((suggestions)=>{
            cb(null, {
                text: suggestions["comf"]["txt"]
            })
        }, (err)=>{
            debug("error:%j", err)
            cb(null, {
                text: `很抱歉,没有获得${city}的天气信息。`
            })
        })
}



exports.getAirByCity = function(city, cb){
    debug("getAirByCity: %s", city);
    wf.getWeatherByCity(city)
        .then((suggestions)=>{
            cb(null, {
                text: suggestions["air"]["txt"]
            })
        }, (err)=>{
            cb(null, {
                text: `很抱歉,没有获得${city}的空气信息。`
            })
        })
}


exports.getSportByCity = function(city, cb){
    debug("getSportByCity: %s", city);
    wf.getWeatherByCity(city)
        .then((suggestions)=>{
            cb(null, {
                text: suggestions["sport"]["txt"]
            })
        }, (err)=>{
            cb(null, {
                text: `很抱歉,没有获得${city}的信息。`
            })
        })
}

exports.getDresscodeByCity = function(city, cb){
    debug("getDresscodeByCity: %s", city);
    wf.getWeatherByCity(city)
        .then((suggestions)=>{
            cb(null, {
                text: suggestions["drsg"]["txt"]
            })
        }, (err)=>{
            cb(null, {
                text: `很抱歉,没有获得${city}的信息。`
            })
        })
}

这次,代码内容和前一版本相比,使用了config对象,config是一个包含环境变量的JSON数据。所以,我们更佳利于将来部署对话应用了。

支持更多对话

回想我们需要的几种天气信息,我们根据需求变更脚本,一个满足需求的脚本呈现如下:

/**
 * 一个能回答天气的对话脚本
 * author: Hai Liang Wang<hain@chatopera.com>
 * date:   2018-06-20
 */


// 技能介绍

+ 你知道哪些天气信息
- 我知道今天的空气,着装建议和适不适合运动

// 天气

+ 今天 (*) 天气 [怎么样]
- {keep} ^getWeatherByCity(<cap1>)

+ [今天] (天气|气候) [怎么样]
- {@__wf_guide_}

+ (*) 今天天气 [怎么样]
- {keep} ^getWeatherByCity(<cap1>)

    + (*) 空气 (*)
    % ^getWeatherByCity(<cap1>)
    - {keep} ^getAirByCity(<p1cap1>)


+ __wf_guide_
- {keep} 添加城市名哦,比如“今天北京天气怎么样”或者“北京天气怎么样”
- 我需要知道城市名称,比如“今天北京天气怎么样”或者“北京天气怎么样”
- 要告诉我城市名,比如“今天北京天气怎么样”或者“北京天气怎么样”


// 空气

+ [今天] 空气 [怎么样]
- {@__wf_guide_air}

+ (*) 今天空气 [怎么样]
- {keep} ^getAirByCity(<cap1>)

+ 今天 (*) 空气 [怎么样]
- {keep} ^getAirByCity(<cap1>)

+ __wf_guide_air
- {keep} 添加城市名哦,比如“今天北京空气怎么样”或者“北京空气怎么样”
- 我需要知道城市名称,比如“今天北京空气怎么样”或者“北京空气怎么样”
- 要告诉我城市名,比如“今天北京空气怎么样”或者“北京空气怎么样”


// 运动

+ [今天] 适(合|宜)运动(么|吗)
- {@__wf_guide_sport}

+ (*) 今天适(合|宜)运动(么|吗)
- {keep} ^getSportByCity(<cap1>)

+ 今天 (*) 适(合|宜)运动(么|吗)
- {keep} ^getSportByCity(<cap1>)

+ __wf_guide_sport
- {keep} 添加城市名哦,比如“今天北京适合运动么”或者“北京今天适合运动么”
- 我需要知道城市名称,比如“今天北京适合运动么”或者“北京今天适合运动么”
- 要告诉我城市名,比如“今天北京适合运动么”或者“北京今天适合运动么”


// 衣着

+ [今天] 适(合|宜)穿什么
- {@__wf_guide_dresscode}

+ (*) 今天适(合|宜)穿什么
- {keep} ^getDresscodeByCity(<cap1>)

+ [今天] (*) 适(合|宜)穿什么
- {keep} ^getDresscodeByCity(<cap1>)


+ __wf_guide_dresscode
- {keep} 添加城市名哦,比如“今天北京适合穿什么”或者“北京今天适合穿什么”
- 我需要知道城市名称,比如“今天北京适合穿什么”或者“北京今天适合穿什么”
- 要告诉我城市名,比如“今天北京适合穿什么”或者“北京今天适合穿什么”

这也就是我们在天气查询机器人Demo中看到的机器人的脚本,在设计过程中,我们通过对话区域来测试机器人的回复是否符合预期,我们通过逻辑窗口来查看当前机器人的思维逻辑导图,当前机器人对话的状态会被高量,被命中的规则呈现为路径。

Screen-Shot-2018-07-10-at-7.15.22-PM

另外,在设计过程中,每次保存自动为脚本和函数生成快照,使用快照下拉列表,我们能方便的回退。

Screen-Shot-2018-07-10-at-7.19.08-PM

这就是我们追求的体验,业务人员可以更专注于对话机器人的对话逻辑满足需求

发布机器人

现在,有了可以工作的脚本,我们想发布一个版本,这时回到主面板,点击“发布”,填入如下信息,点击“确认”。

Screen-Shot-2018-07-10-at-7.32.23-PM

导出机器人

最终,我们需要得到一个“服务”,它能时刻被访问,以及和AI音箱集成。我们需要将天气机器人导出为对话应用,然后部署到智能问答引擎。在主面板,点击版本管理,我们看到操作中有三项。

Screen-Shot-2018-07-10-at-7.45.19-PM

  • 对比差异:在多个版本中比较差异,包括脚本和函数。
  • 导出:将机器人导出为对话应用文件。
  • 覆盖:使用这个版本覆盖当前机器人,包括脚本和函数等。

什么是智能问答引擎

智能问答引擎是多轮对话的运行时,同时也包含知识库统计监控等功能。智能问答引擎多轮对话设计器会同时发布。

Screen-Shot-2018-07-10-at-7.53.12-PM

如果您想参加这两个产品的发布会,请报名Chatopera产品发布会:追求高度智能化和自动化的企业服务

多轮对话设计器: 实现天气查询机器人的过程