创建一个服务意图,在云开发平台预发环境部署以后实现语音对话,在线测试将对话界面进行截图。信大家对天猫精灵的技能应用的基本概念、开发方式、部署流程有了一个简单的了解。今天对天猫精灵中意图的创建配置进行学习,让我们的智能应用具备真正的服务能力。实现天气查询功能,来学习如何让技能与用户进行单轮或多轮的对话,让技能更加有趣。
一、配置语音交互模型
点击创建意图按钮进入创建意图页面,并且设置默认意图。
意图是用户使用语音技能的目的,语音技能至少要有一个意图(至少向用户提供一个功能,也可以是多个)只说调用词进入会默认意图,所以有调用词的技能需要有默认意图。
意图是用户进行交互对话的目的。您可以创建新的意图,也可以引用平台提供的公共意图。当您创建意图时需要明确此意图提供什么样的功能。例如“天气小助手”技能里,我们创建一个意图提供天气的查询功能。
二、配置单轮对话语料
语料:是指当用户为了达到目的向音箱说出的语音指令。
单轮对话语料的含义是:当用户说这句话时,就可以确认用户就是希望使用这个意图的功能。
语料的泛化:用户不可能只按照一句语料来说,所以相似的表达也应该配置成语料,这叫做语料的泛化。不同的人表达方式存在差异,实际的开发过程中要尽可能的覆盖表达句式。不然有些用户的表达方式无法匹配命中我们的意图。
例如:“贵阳今天天气怎么样”,“贵阳今天天气如何”,“查一下贵阳今天天气”,“贵阳天气怎么样”、“今天天气怎么样”......。
在天气查询意图中,可以配置几条类似的语料:
创建意图预料后进行保存。提交保存。
三、配置实体和追问
1.实体是什么?
我们在配置意图语料时,天气查询意图涉及到 城市 和 时间 这两个参数(例如“杭州 今天 天气怎么样”)。我们无需在语料中对城市,时间进行穷举,可以通过创建实体来为这两个参数提供取值范围。
实体:是自然语言处理领域中的重要概念,是一个规范的自然语言短语集合,通常定义为应用所在领域的关键词、术语。如常见的时间、地点、POI、人名、数量等,都可以作为实体来处理。
实体创建有两种方式:
自定义实体(开发者自己创建实体、填充实体值):“城市”实体可以使用 自定义实体,
引用公共实体(平台提供的已填充好实体值的实体,直接引用即可):“时间”实体 使用公共实体
2. 创建“城市”实体(自定义实体)
第一步,填写实体中文名称 和 实体标识;
第二步,添加实体值:例如北京、杭州、上贵阳等城市名称;
添加时,支持同时输入多个实体值,中间以空格分隔,点击回车保存
Tips:通过模板。调整我们的语料库(更具有通用性)
3. 创建“时间”实体(公共实体)
可以减少维护成本,比如我们引入日期公共实体
第一步,“时间”实体推荐使用公共实体:sys.date。点击“引用公共实体”;
第二步,输入sys.date进行搜索,找到 sys.date 公共实体,打开后面的“引用”按钮;
第三步,返回实体列表,可以看到“时间”实体已经创建完成。
4. 将参数和实体进行关联
我们对我们预料进行实体的标记。鼠标选中我们需要标注的词语,页面上会自动弹出支持标注的实体。如 “杭州今天天气怎么样”这句语料,我们分别标注“杭州”为“city”,“今天”为“sys.date(公共实体)”。标注后会自动生成参数名称“city”和“sys.date(公共实体)”。
参数是什么:参数是用户使用意图语料时用来代指实体值的一个动态变化的变量。在上一步骤中,我们已经为“天气查询”意图语料中涉及到的 城市 和 时间 两个参数设置好了相应实体,接下来需要在意图语料中将参数与实体进行关联,请按如上步骤进行操作。
5. 参数追问配置
虽然我们在意图语料中设置好了参数并且关联了实体,但用户在日常语音交互过程中很有可能没有说出想要查询 的城市或者日期。在这种情况下,语音交互识别过程中就缺少了必要的参数,技能就无法为用户提供精确的服务,此时我们可以通过设置参数追问来补全一些必要的信息。
当语料中不包含必要的参数时,可以有两种办法获取到:1)给参数指定一个默认值:当用户说的语料中没有这个参数,就默认是某个取值。2)设置参数追问:再通过一轮对话向用户询问参数的取值。
1)给 date 参数设置默认值
这里填写的“今天”就代表当用户使用语料“贵阳天气”进入意图后,默认用户查询的是今天的天气。配置有默认值时,请求数据中一定会携带这个参数。
2)缺少 city 参数时向用户追问
使用“精灵追问”,语音交互模型中就需要增加精灵追问的以下配置:
勾选参数前的 必选,
在 精灵追问 中添加至少一条追问语句。
在平台配置好追问后,后端逻辑代码中就不需要判断 city 参数是否存在了。系统自动判断是否含有 city 参数,如果没有则会使用追问语句向用户询问,获取到必要参数后会统一发送给后端服务进行处理.
6.配置多轮对话语料
增加语料
在意图配置中增加“那明天呢”,“那北京呢”语料。在用户日常聊天对话中,很多情况下仅凭单条语句是无法确认用户真实意图的,此时就需要结合对话的上下文来明确意图。
例如,下图语音对话交互流程中,用户说的“那明天呢”和“那北京呢”单句来看是无法辨别意图的。但结合前几轮对话的结果,就可以确认“那明天呢”是想要查询杭州明天的天气。
关联语料和实体
在“天气查询”意图的多轮对话的语料如下,并把明天和北京分别进行标注,明天标注为时间实体,北京标注为城市实体:
全都配置好后提交意图配置。
为了更具有通用性,我们使用模板,如下图:
四、后端功能开发
意图需要后端的服务来提供服务,进行开发,以下为代码:
package com.alibaba.ailabs;
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.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 java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @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));
// ResultModel<TaskResult> res = new ResultModel<>();
TaskResult taskResult = new TaskResult();
// 从请求中获取意图参数以及参数值
Map<String, String> paramMap = taskQuery.getSlotEntities().stream().collect(Collectors.toMap(slotItem -> slotItem.getIntentParameterName(), slotItem -> slotItem.getOriginalValue()));
//处理名称为 welcome 的意图
if ("welcome".equals(taskQuery.getIntentName())) {
taskResult.setReply("欢迎使用天气小蜜,使用小蜜可以查询天气哟");
//处理名称为 weather 的意图
} else if ("weather".equals(taskQuery.getIntentName())) {
//weather 意图中 date 参数勾选了必选,请求数据中一定会携带 date 参数,只需要判断 city 参数有没有。
if (paramMap.get("city") == null) {
taskResult.setReply("您要查询哪个城市的天气?");
return askReply(taskResult, "city", taskQuery.getIntentId());
}
//TODO 根据参数获取天气信息,这里使用假数据替代
taskResult.setReply(paramMap.get("city") + paramMap.get("sys.date(公共实体)") + "天气 晴");
//处理名称为 ari_quality 的意图
}else {
taskResult.setReply("请检查意图名称是否正确,或者新增的意图没有在代码里添加对应的处理分支。");
}
return reply(taskResult);
}
/**
* 结束对话的回复,回复后音箱闭麦
*/
private ResultModel<TaskResult> reply(TaskResult taskResult) {
ResultModel<TaskResult> res = new ResultModel<>();
taskResult.setExecuteCode(ExecuteCode.SUCCESS);
taskResult.setResultType(ResultType.RESULT);
res.setReturnCode("0");
res.setReturnValue(taskResult);
return res;
}
/**
* 指定追问参数,音箱自动开麦,用户的回答优先匹配追问的参数
*/
private ResultModel<TaskResult> askReply(TaskResult taskResult, String parameterName, Long intentId) {
ResultModel<TaskResult> res = new ResultModel<>();
taskResult.setExecuteCode(ExecuteCode.SUCCESS);
taskResult.setResultType(ResultType.ASK_INF);
AskedInfoMsg askedInfoMsg = new AskedInfoMsg();
askedInfoMsg.setIntentId(intentId);
askedInfoMsg.setParameterName(parameterName);
List<AskedInfoMsg> askedInfos = new ArrayList<>();
askedInfos.add(askedInfoMsg);
taskResult.setAskedInfos(askedInfos);
res.setReturnValue(taskResult);
return res;
}
}
提交代码,进行部署