万年不变的前言
我手上有一个生日时候公司送的天猫精灵 IN 糖
智能音箱大致长这样:
家里还有一个小米的智能音箱,导致天猫精灵一直闲置。于是我就去天猫精灵的开放平台研究了一下,发现可以自定义自己的语音指令,可以将语音指令转发到自己的服务器去处理消息,然后返回结果再由天猫精灵进行输出。
根据这种方式,所以想把天猫精灵利用起来,打造成一个语音自动化的智能音箱,负责对服务器进行巡检,检查K8S的健康状态等等,并通过语音方式从天猫精灵反馈出来。
大致流程如上,你们看不懂没关系,我能看懂就好😏。
上手操作
注册账户
首先我们需要去语音开发者平台,使用自己的淘宝账号进行登录。
https://www.aligenie.com/account/login
登录成功后,可能需要认证账户,根据提示选择个人认证即可。 选择技能应用平台
:
直到我们进入到这个技能平台的控制台中:
https://iap.aligenie.com/home
创建技能
这个技能就好比一个功能分类,例如上图中的运维助手
技能,它包含了多个指令例如:K8S资源使用情况
、构建{x xx项目}
、我的工单
...等等相关的指令都放在一个技能下面。
我们点击创建新技能,在打开的界面中选择自定义技能
,然后可以参考下图中进行填写。
创建完成后就会进入到新的界面:
了解意图
意图
故名思议,是用户进行交互对话的根本目的与需求,意图也类似于指令的分类,比如:查询今天的天气
、查询明天的天气
、天气查询
这三个指令虽然不同,但是他们的意图都是一样的,是为了查询天气情况。
我们可以在语音交互模型
中创建自己的意图:
可以参考我的意图填写方式:
刚才说了意图是类似指令的分类,所以在这个配置的下方,还有单轮对话表达
的配置,我配置了三个指令
:
然后我们提交意图即可:
创建带参数的意图
如上述的创建意图所示,我们的指令虽然可以去查询服务器的状态。但是如果我们有N台服务器怎么办,总不能写N个意图和指令吧。所以这里可以创建带参数的指令,例如:查询[127]的服务器
、[上海]的服务器运行状态
等等这种类似的指令。
这里就引入了一个概念,叫做实体
。我们在实体中创建一个新的实体:
可以参考我的写法:
点击保存
后,在下方的实体值
中可以添加我们的服务器名称:
然后我们就可以修改或者创建意图,并使用我们自定义的实体,这里我们有两个指令,然后新增参数,参数的名称可以随意填写:
然后对我门创建的例句
中,用鼠标选中某个指令中的关键词,例如server
,它会弹出一个对话框让你绑定一个参数:
然后我们绑定刚才创建的参数即可:
技能平台也预制了一些常用的实体,例如匹配数字
、日期
等等,它们都在公共实体里面:
到此我们在技能平台的操作已经完成了一大半,现在我们要着手实现自己的指令响应的部分了。
接口搭建
天猫精灵官方提供了Java的示例以及SDK服务,所以我这里使用的Java去实现接收指令并响应的接口服务。
接口交互的逻辑我这里简单带过一下:
下发语音指令
-> 识别意图
-> 携带意图信息和参数
-> 自定义的WebServer
-> 返回结果
-> 响应指令结果
创建项目
我们需要创建一个SpringBoot项目,然后在pom.xml
中添加额外的依赖:
<dependency>
<groupId>com.alibaba.da.coin</groupId>
<artifactId>semantic-execute-meta</artifactId>
<version>1.1.18-REALEASE</version>
</dependency>
其他的依赖看各位的需求,比如连接ssh
服务的依赖,访问K8S
和普罗米修斯
等。
定义入口方法
我们需要定一个可接收POST
请求的接口,用于读取天猫精灵的意图回调,这里简单写了一个入口以及读取当前的意图信息:
@PostMapping("/")
public String handle(@RequestBody String taskQuery) {
log.info("TaskQuery:{}", taskQuery);
TaskQuery query = MetaFormat.parseToQuery(taskQuery);
log.info("意图标识:{}", query.getIntentName());
return query.getIntentName();
}
我们可以将这个接口映射出来,然后在技能平台
配置我们的接口信息:
根据提示填写后端地址以及下载认证文件
,这个认证文件存放的路径是有要求的:
http://接口地址/aligenie/认证文件的名称.txt
只有认证通过才能使用这个接口。
配置好后端地址后,就可以在天猫精灵中尝试使用我们自定义的指令,然后通过后端服务的日志就可以看到能够输出对应指令的意图标识。
不方便使用天猫精灵的时候,也可以在创建的技能中使用测试功能;
使用工厂设计模式
这里使用工厂模式主要用于根据意图名称创建对应的事件处理类的实例。这样可以将对象的创建与使用解耦。并且约束每个意图事件处理类必须实现一个接口,这样就可以根据意图标识来找到对应的意图处理实例来处理对应的任务。
创建接口类
我们定一个处理事件的方法:
/**
* 用于约束每一个事件处理器必须拥有一个execute方法
* */
public interface EventHandle {
TaskResult execute(TaskQuery query);
}
创建注解
我们要创建一个标注在类上的注解,并且注解包含一个String类型的参数,用于配置意图的标识,这样就可以通过接口拿到的意图标识找到对应的事件处理实例:
/**
* 意图标识
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface IntentName {
String value();
}
创建一个意图处理类
我们假设要处理接收到的server_status
这个意图的逻辑实现,那么我们可以这样去编写这个类的具体实现:
/**
* 服务器运行状态查询
*/
@Component
@IntentName(value = "server_status")
public class ServerStatueHandle implements EventHandle {
@Override
public TaskResult execute(TaskQuery query) {
// 具体的逻辑这里实现
return null;
}
}
创建语音消息工具类
我们定一个工具类,用于构建TaskResult
这个对象,来实现返回语音消息让天猫精灵进行播报:
/**
* 消息构建工具类
*/
public class TaskResultUtil {
/**
* 语音消息构建
*
* @param message 消息文本
* @return TaskResult
*/
public static TaskResult generateResult(String message) {
// 回复语音消息
TaskResult result = new TaskResult();
result.setReplyType(ReplyType.TEXT);
result.setResultType(ResultType.RESULT);
result.setExecuteCode(ExecuteCode.SUCCESS);
List<GwCommand> gwCommands = new ArrayList<>();
GwCommand speak = new GwCommand("AliGenie.Speaker", "Speak");
Map<String, Object> payload = new HashMap<>();
payload.put("type", "text");
payload.put("text", message);
payload.put("expectSpeech", false);
speak.setPayload(payload);
gwCommands.add(speak);
result.setGwCommands(gwCommands);
// 彻底结束当前对话
SkillDialogSession skillDialogSession = new SkillDialogSession();
skillDialogSession.setSkillEndNluSession(true);
result.setSkillDialogSession(skillDialogSession);
return result;
}
}
然后我们的ServerStatueHandle
这个事件处理类中的execute
可以进行简单的语言回复:
@Override
public TaskResult execute(TaskQuery query) {
// 具体的逻辑这里实现
return generateResult("hello world");
}
创建工厂类
上面的代码,我们完成了意图事件的处理类,通过注解的形式可以处理不同的意图,现在我们需要将这些实例收集起来,并通过同一个入口根据意图标识来获取这些实例:
第一步:扫描所有被IntentName
所注解的类实例:
/**
* 扫描所有被 {@link com.bystart.tmapi.annotation.IntentName @IntentName} 所注解的类
* <p> 将注解的value值作为key,所注解的对象作为value放入到Map中
* */
@Data
@Configuration
public class IntentHandlerConfig implements ApplicationContextAware {
private final Map<String, EventHandle> intentHandlers = new HashMap<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
applicationContext.getBeansWithAnnotation(IntentName.class).forEach((k, v) -> {
IntentName intentName = v.getClass().getAnnotation(IntentName.class);
intentHandlers.put(intentName.value(), (EventHandle) v);
});
}
}
第二步:创建工厂类,返回所需要的实例:
/**
* Event工厂
*/
@Component
public class EventFactory {
@Resource
private IntentHandlerConfig intentHandlerConfig;
/**
* 通过意图标识拿到对应意图的事件处理实例
*
* @param intentName 意图标识
*/
public EventHandle getEvent(@NonNull String intentName) {
return intentHandlerConfig.getIntentHandlers().get(intentName);
}
}
完善入口方法
我们完成了一系列的意图定义,意图处理器以及工厂模式。现在我们需要将其完整的连接起来。我们的入口方法需要根据接收到的意图标识来获取对应的JavaBean实例来进行处理和返回:
/**
* 接收并处理所有意图的事件
*
* @param taskQuery 事件详情
* @return ResultModel
*/
@PostMapping("/")
public ResultModel<TaskResult> handle(@RequestBody String taskQuery) {
TaskQuery query = MetaFormat.parseToQuery(taskQuery);
EventHandle event = eventFactory.getEvent(query.getIntentName());
log.info("意图标识:{}", query.getIntentName());
TaskResult taskResult = event.execute(query);
// 返回对话结果
ResultModel<TaskResult> resultModel = new ResultModel<>();
resultModel.setReturnCode("0");
resultModel.setReturnValue(taskResult);
return resultModel;
}
真机调试
将代码编写完成并部署后,我们需要关联到设备才能够正常的与天猫精灵进行对话,并响应指令内容:
写在最后
到此,我们从定义指令到接收指令处理指令的完整过程已经写完了,关于巡检那部分的脚本我是用python写的,提供的了一个http的api接口进行调用。所以具体的脚本内容,大家可以根据自己的需求进行编写即可。慢慢享受折腾带来的快乐吧!
欢迎大家关注我的公众号,将会为大家推荐更优质的内容!