@[toc]
1. 绪论
1.1 天猫精灵是啥
天猫精灵是阿里发布的AI智能音箱,支持智能家居控制、语音购物、手机充值、叫外卖、音频音乐播放、订日程、备忘提醒、定闹钟、查天气、找手机、倒计时、卡路里查询、听新闻、查星座运势、查价格、查股票等功能。
1.2 什么是天猫精灵开放平台
- 天猫精灵技能应用开发平台是阿里巴巴天猫精灵面向不同类型的广大开发者推出的技能应用开发、接入平台。
- 该平台提供了从语音交互模型定义、语义解析理解能力,到技能开发、测试、部署的一整套开发工具和便捷的可视化操作工作台,帮助开发者高效地将各类技能应用快速接入到天猫精灵音箱以及精灵生态硬件终端。
- 平台旨在将人工智能中ASR(语音识别)、NLP(自然语言处理)、TTS(语音合成)等自然语言处理技术进行整合,并配合 AI 能力和设备控制能力对外共享开放。同时,依托于强大的底层技术、智能的算法引擎、完善的云端服务和成熟的软硬件标准系统,持续不断的将全面、易用的核心技术能力进行输出,为开发者带去更多可能。最终方便各种类型的开发者都可以通过平台便捷享用,从而高效率地创建音箱端的智能应用,不断丰富应用生态,提升用户价值和满意度的同时,实现自身的利益诉求。
2. 成为开发者
2.1 登录平台
登陆后点击控制台,选择技能应用平台并点击进入。
2.1 注册平台
根据实际情况选择认证的类型。并根据页面提示填写开发者真实信息,完成账号认证。下面以认证企业账号为例,介绍认证的操作步骤。
3. 领取一只精灵
3.1 创建语音技能
认证通过后进入技能应用平台,我们要创建一个语音技能,选中语音技能分类:点击创建技能。
3.2 填写技能基本信息
选中语音技能 中的 自定义技能 填写技能创建信息:技能名称和调用词
技能名称:展示在技能市场的技能名称,在发布时将进行唯一性检测。
技能属性选择 公有技能 / 私有技能。技能属性选择,建议您先参考【公有&私有】
,由于我们要开发一个面向所有天猫精灵用户的应用,所以技能属性这里选择公有技能。
技能调用词也称为 唤醒词,是用户使用这个技能所需要说的关键字。如“天猫精灵,天气小蜜”
,则使用了调用词为“天气小蜜”的技能。
填写技能调用词时系统会进行唯一性检测,截图中填写的“天气小蜜”仅仅作为范例,请您在创建技能时注意不要和范例相同或填重复过的调用词。
3.3 配置语音交互模型
创建技能成功后,需配置语音交互模型,才可与用户进行语音对话,语音交互模型包含意图、实体、问答三块,本节课先配置意图。
首先了解下什么是意图:意图是用户使用语音技能的目的,语音技能至少要有一个意图(至少向用户提供一个功能,也可以是多个)只说调用词会进入默认意图所以有调用词的技能需要有默认意图。
了解什么是意图后,接下来开始创建语音交互模型。
第一步,点击创建意图按钮进入创建意图页面;
第二步,设置意图名称、意图标识,并将这条意图设置为默认意图,设置好后点击提交。
3.4 配置后端服务
跳转到阿里云开发平台后,会自动进入 CloudIDE
,平台会自动生成模板代码,查看代码路径:src/main/GenieEntry.java
,您可以直接在示例代码上进行开发;
代码编写后,要把代码提交到仓库中,不然再进来就没有了,提交步骤:点击源代码管理,然后点击提交按钮;
提交完成后要选择部署环境,打开CloudIDE
左侧的 部署调试插件,进入到部署面板,选择预发环境进行部署;
境选择好之后,我们要检查路由、函数映射表,第一个字段是应用访问的路径,格式为:/skill-78623/entry
,其中数字 67954
为智能应用平台上此应用的技能id,技能id显示在技能概览页中的基本信息表中,这个字段一般系统会默认自动填好。
第二个字段表示某个文件的函数入口,如果选择Java
语言, 此数据为:com.alibaba.ailabs.GenieEntry::handleRequest
,表示当应用发布之后,天猫精灵这边的请求会发送到 com.alibaba.ailabs.GenieEntry
类的handleRequest
方法,handleRequest
是从AbstractEntry
类中继承的方法, 请不要重写此方法。如果选择NodeJS
语言或Python
语言,此数据为:index.handler,
表示含义和前面类似;由于我们选的是java
语言, 所以第二个字段我们要填com.alibaba.ailabs.GenieEntry::handleRequest。
package com.alibaba.ailabs;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.alibaba.ailabs.common.AbstractEntry;
import com.alibaba.da.coin.ide.spi.meta.AskedInfoMsg;
import com.alibaba.da.coin.ide.spi.meta.ExecuteCode;
import com.alibaba.da.coin.ide.spi.meta.GwCommand;
import com.alibaba.da.coin.ide.spi.meta.ResultType;
import com.alibaba.da.coin.ide.spi.standard.ResultModel;
import com.alibaba.da.coin.ide.spi.standard.TaskQuery;
import com.alibaba.da.coin.ide.spi.standard.TaskResult;
import com.alibaba.fastjson.JSON;
import com.aliyun.fc.runtime.Context;
import com.google.common.collect.Lists;
/**
* @Description 天猫精灵技能函数入口,FC handler:com.alibaba.ailabs.GenieEntry::handleRequest
* @Version 1.0
**/
public class GenieEntry extends AbstractEntry {
@Override
public ResultModel<TaskResult> execute(TaskQuery taskQuery, Context context) {
context.getLogger().info("taskQuery: " + JSON.toJSONString(taskQuery));
//String screenStatus = taskQuery.getRequestData().get("screenStatus");
String screenStatus = "online";
// 从请求中获取意图参数以及参数值
Map<String, String> paramMap = taskQuery.getSlotEntities().stream().collect(
Collectors.toMap(slotItem -> slotItem.getIntentParameterName(), slotItem -> slotItem.getOriginalValue()));
String reply;
String city = paramMap.get("city");
String date = paramMap.get("date");
//处理名称为 weather 的意图
if ("weather".equals(taskQuery.getIntentName())) {
//weather 意图中 date 参数勾选了必选,请求数据中一定会携带 date 参数,只需要判断 city 参数有没有。
if (city == null) {
reply = "您要查询哪个城市的天气?";
return askReply(reply, "city", taskQuery.getIntentId());
}
//TODO 根据参数获取天气信息,这里使用假数据替代
reply = city + date + "天气 晴。";
if ("online".equals(screenStatus)) {
return weatherTplReply(reply, city);
} else {
return reply(reply);
}
//处理名称为 air_quality 的意图
} else if ("air_quality".equals(taskQuery.getIntentName())) {
//air_quality 意图中 date 参数勾选了必选,请求数据中一定会携带 date 参数,只需要判断 city 参数有没有。
if (city == null) {
reply = "您要查询哪个城市的空气质量?";
return askReply(reply, "city", taskQuery.getIntentId());
}
//TODO 根据参数获取空气质量信息,这里使用假数据替代
reply = city + date + "空气质量46 优。";
if ("online".equals(screenStatus)) {
return airTplReply(reply, city);
} else {
return reply(reply);
}
}
reply = "请检查意图名称是否正确,或者新增的意图没有在代码里添加对应的处理分支。";
return reply(reply);
}
/**
* 结束对话的回复,回复后音箱闭麦
*/
private ResultModel<TaskResult> reply(String reply) {
ResultModel<TaskResult> res = new ResultModel<>();
TaskResult taskResult = new TaskResult();
taskResult.setReply(reply);
taskResult.setExecuteCode(ExecuteCode.SUCCESS);
taskResult.setResultType(ResultType.RESULT);
res.setReturnCode("0");
res.setReturnValue(taskResult);
return res;
}
/**
* 指定追问参数,音箱自动开麦,用户的回答优先匹配追问的参数
*/
private ResultModel<TaskResult> askReply(String reply, String parameterName, Long intentId) {
ResultModel<TaskResult> res = new ResultModel<>();
TaskResult taskResult = new TaskResult();
taskResult.setReply(reply);
taskResult.setExecuteCode(ExecuteCode.SUCCESS);
taskResult.setResultType(ResultType.ASK_INF);
AskedInfoMsg askedInfoMsg = new AskedInfoMsg(parameterName, intentId);
taskResult.setAskedInfos(Lists.newArrayList(askedInfoMsg));
res.setReturnCode("0");
res.setReturnValue(taskResult);
return res;
}
/**
* 天气查询意图的TPL回复
*/
private ResultModel<TaskResult> weatherTplReply(String reply, String city) {
return commonReply(reply, city, reply);
}
/**
* 空气质量查询意图的TPL回复
*/
private ResultModel<TaskResult> airTplReply(String reply, String city) {
return commonReply(reply, city, reply);
}
private ResultModel<TaskResult> commonReply(String reply, String city, String detail) {
//屏显模板数据需要使用gwCommands字段携带,会导致reply失效。回复内容通过speak指令返回
GwCommand speakGwCommand = new GwCommand("AliGenie.Speaker", "Speak");
HashMap<String, Object> payload = new HashMap<>();
payload.put("type", "text");
payload.put("text", reply);
speakGwCommand.setPayload(payload);
//携带屏显模板数据的指令
GwCommand tplGwCommand = new GwCommand("AliGenie.Screen", "Render");
HashMap<String, Object> payload1 = new HashMap<>();
payload1.put("pageType", "TPL.RenderTemplate");
payload1.put("pageTitle", "天气小助手");
//模板标识数据
HashMap<String, Object> data = new HashMap<>();
data.put("template", "aligenie_weather_tpl_demo");
//模板中展示内容的字段 dataSource
HashMap<String, String> dataSource = new HashMap<>();
dataSource.put("imageUrl", "https://ailabs-iot.aligenie.com/iap/platform3.0/weather-banner.png");
dataSource.put("city", city);
dataSource.put("minTemperature", "36°");
dataSource.put("maxTemperature", "38°");
dataSource.put("detail", detail);
data.put("dataSource", dataSource);
payload1.put("data", data);
tplGwCommand.setPayload(payload1);
return tplReply(Lists.newArrayList(speakGwCommand, tplGwCommand));
}
/**
* 携带TPL数据的结束对话回复,回复后音箱闭麦
*/
private ResultModel<TaskResult> tplReply(List<GwCommand> gwCommands) {
ResultModel<TaskResult> res = new ResultModel<>();
TaskResult taskResult = new TaskResult();
taskResult.setExecuteCode(ExecuteCode.SUCCESS);
taskResult.setResultType(ResultType.RESULT);
taskResult.setGwCommands(gwCommands);
res.setReturnCode("0");
res.setReturnValue(taskResult);
return res;
}
}
3.4 测试天猫精灵
输入创建技能时设置的调用词“天气小蜜”并发送,测试欢迎意图是否配置成功,系统回复默认欢迎语则技能测试成功;
输入创建技能时设置的调用词并发送(调用词以技能创建时的为准),本教程中以“天气小蜜”调用词为例,系统回复则表示技能部署成功。