转自aiui文档
静态实体
的适用范围很大,但当开发者遇到以下几种用例时可能不能满足,为此我们推出了动态实体
,满足开发者各终端的实体需要差异化设置时的需求。
#应用级
举例
以开发两款百科 App 为例,分别是《水浒传百科》和《西游记百科》,开发者期望用户的交互方式为:
我想听李逵的故事
介绍一下孙悟空
我们可以把以上两句话抽象为一个自定义技能
,其中语料如下:
我想听{name}的故事
介绍一下{name}
《水浒传百科》和《西游记百科》两个 App 均添加了这个技能,但是这两个 App 的实体并不相同。{name}
内容分别如下:
为了实现这个目标,我们提供了动态实体(应用级)
,动态实体(应用级)
可以在实体名相同的情况下,为每个 appid 设置不同的实体副本。
动态实体(应用级)
作用域为 appid,生效时间为永久。
#用户级
举例
以开发一款电话 App 为例, 开发者期望用户的提问方式为:
打电话给张三
呼叫李四
我们可以把以上两句话抽象为一个自定义技能
,其中语料如下:
打电话给{contacts}
呼叫{contacts}
由于每个用户的联系人名称都不一样,因此无法使用静态实体。为此 AIUI 的 SDK 中,为开发者提供了相应的 API 接口,开发者可以针对每个用户维护一个实体副本。动态实体(用户级)
只对特定用户生效,生效时间为永久。
#自定义级
举例
以开发一个全国连锁餐厅点餐 App 为例, 开发者期待用户的交互方式为:
我想吃杭椒牛柳
点一份宫保鸡丁
我们将其抽象为一个自定义技能
,其中语料如下:
我想吃{dish}
点一份{dish}
但是由于该连锁餐厅全国不同省份的菜单不一样,每个省份有若干台点餐终端,但是实体不能通用。
为了解决这个问题,我们引入了动态实体(自定义级)
。
我们为这款点餐 App 新建了一个动态实体
命名为" dish",同时在这个实体下创建两个资源,分别是"dish_general" 和"dish_specail",其中 "dish_general" 的维度为应用级
," dish_specail" 的维度为自定义
,我们把这个自定义的维度命名为"province"。在控制台的设置如下:
如图所示,开发者可以为一个动态实体,设置两个维度的资源。
其中 dish_general 中,开发者可以通过 API上传全国通用的菜单。
针对 dish_special, 开发者可以通过维度名 province 上传各个省份的菜单,例如 Anhui,Beijing,Fujian 等,具体操作请参考 SDK 文档。
使用动态实体(自定义级)
开发者可以自定义分组,实体设置成功后,只对属于特定分组的终端生效。
#所见即可说
举例
假设开发者要在汽车中控上开发一款导航技能,开发者期望用户的交互方式为:
导航到中科大
此时屏幕上的数据如下:
用户可能说的话术包含:
中国科学技术大学东校区``我要去西校区``第三个
我们将这些话术抽象为语料:
{destination}``我要去{destination}
其中实体 destination 根据用户前一句话导航的目的地的不同,每次展现的结果都是调用地图引擎动态搜索出来的地名,并且用户可能使用一些精简的表达方式(如:第三个,西校区),因此destination无法抽象为传统的实体。为了满足类似的场景需求,技能工作室提出了动态实体(所见即可说)
的概念。静态实体或者动态实体(应用级、自定义级、用户级)更新的频率可能是几天或者几个月,但所见即可说
要求开发者每次使用前,必须实时上传实体资源,系统会使得开发者上传的资源秒级生效(生效成为语义层面的实体,以及识别层面的热词),在一次交互结束后,开发者上传的资源也随之失效。
客户端获取信源信息 -> 整理信源中的关键数据 -> 调用所见即可说API接口上传数据 -> 用户语音交互 -> 一次交互结束立刻失效
#接入文档
动态实体、所见即可说的上传需要配合客户端 SDK 进行,完整文档建议参阅 AIUI SDK 接入文档。
#动态实体定义
一个动态实体的定义可以包含多个资源Resource的定义,资源定义中包含了资源名称,以及从客户端上传数据抽取说法的字段名以及生效的维度。 生效的维度目前有用户级(uid)、应用级(appid)、自定义级,在客户端上传对应的资源数据时,维度信息需要和定义时保持一致。
#动态上传资源数据
通过CMD_SYNC
上传同步资源数据,arg1表示同步的数据类型,动态实体对应SYNC_DATA_SCHEMA
(常量对应值为3)。 data为同步JSON内容的utf-8二进制数据。 JSON内容示例如下:
{
"param": {
"id_name": "uid", // 维度
"id_value": "", // 维度具体值,当维度取uid或appid时,该值可取空,AIUI会自动补全
"res_name": "XXX.user_applist" // 资源名称
},
"data": "xxxxxx"// 与schema名称对应的数据内容base64编码
}
注意:同步数据前需确保设备已经连接到AIUI服务器,可通过接收到EVENT_CONNECTED_TO_SERVER
事件来判断,当收到EVENT_SERVER_DISCONNECTED
事件后,将不能进行同步个性化数据等操作。
XXX为用户的命名空间(namespace),在技能工作室任意一个技能的基本信息中可以查看。
id_name
与动态实体定义资源时指定的维度对应,如果定义时是用户级,那此处id_name
就对应uid
。id_value
是维度具体值,如id_name
为uid
,id_value
就需要是该资源针对生效的用户的具体UID,AIUI会使用 当前用户UID进行补全,appid同理。自定义维度因为是由开发者自定义,所以id_name
、id_value
都需要设置具体值。
通过指定id_name
、id_value
,上传的动态实体资源数据到服务端就有了唯一的从属,在生效使用时就可以指定此处的id_name
、id_value
的值就能生效使用当前上传的动态实体资源。
res_name
对应的是平台自定义动态实体资源名,格式:“命名空间.资源名”。
data中是原始资源数据的base64编码内容。原始资源数据是包含多条json记录的文本,通过动态实体定义时的抽取字段名可以从每条 json记录中抽取出定义支持的说法。
如定义资源时数据抽取的主列名为appName,别名为alias,那就要确保每条json记录中都要包含如上的字段(可以包含冗余字段,如下面的extra字段), 示例如下:
注:每条数据之间用换行符隔开。
data的实际内容是将如上数据进行base64编码后的结果。
同步上传的代码示例如下:
JSONObject syncSchemaJson = new JSONObject();
JSONObject paramJson = new JSONObject();
paramJson.put("id_name", "uid");
paramJson.put("res_name", "XXX.user_applist");
syncSchemaJson.put("param", paramJson);
syncSchemaJson.put("data", Base64.encodeToString(FileUtil.readAssetsFile(context, "file_path"), Base64.DEFAULT | Base64.NO_WRAP));
// 传入的数据一定要为utf-8编码
byte[] syncData = syncSchemaJson.toString().getBytes("utf-8");
AIUIMessage syncAthenaMessage = new AIUIMessage(AIUIConstant.CMD_SYNC,AIUIConstant.SYNC_DATA_SCHEMA, 0, "", syncData);
mAIUIAgent.sendMessage(syncAthenaMessage);
iOS的代码如下
NSString *str = @"";
for (EbikeDTO *model in [SessionHelper sharedInstance].carList) {
if (str.isEmpty) {
str = [NSString stringWithFormat:@"{\"name\":\"%@\", \"recordStatus\":\"%ld\"}",model.plateNo, (long)model.statusForRecord];
} else {
str = [NSString stringWithFormat:@"%@\n{\"name\":\"%@\", \"recordStatus\":\"%ld\"}",str, model.plateNo, (long)model.statusForRecord];
}
}
NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
NSString *base64Str = [data base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithCarriageReturn];
//组装自定义实体数据格式
NSMutableDictionary *dataDic = [NSMutableDictionary dictionary];
NSMutableDictionary *paramDic = [NSMutableDictionary dictionary];
[paramDic setObject:@"uid" forKey:@"id_name"];
[paramDic setObject:@"OS1382679815.vehicle_info" forKey:@"res_name"];
[dataDic setObject:paramDic forKey:@"param"];
[dataDic setObject:base64Data forKey:@"data"];
NSError *err;
NSData *data = [NSJSONSerialization dataWithJSONObject:dataDic options:NSJSONWritingPrettyPrinted error:&err];
if(!data){
SLog(@"parse error! dataDic=%@", dataDic);
return;
}
NSString *paramStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
//同步个性化数据
IFlyAIUIMessage *msg = [[IFlyAIUIMessage alloc] init];
msg.msgType = CMD_SYNC;
msg.arg1 = SYNC_DATA_SCHEMA;
msg.params = paramStr;
msg.data = data;
[self.aiuiAgent sendMessage:msg];
CMD_SYNC
完成后会有EVENT_CMD_RETURN
事件回调,可以获取该操作对应的sid,便于后面查询使用:
private void processCmdReturnEvent(AIUIEvent event) {
switch (event.arg1) {
case AIUIConstant.CMD_SYNC: {
int dtype = event.data.getInt("sync_dtype");
//arg2表示结果
if (0 == event.arg2) { // 同步成功
if (AIUIConstant.SYNC_DATA_SCHEMA == dtype) {
mSyncSid = event.data.getString("sid");
showTip("schema数据同步成功,sid=" + mSyncSid);
}
} else {
if (AIUIConstant.SYNC_DATA_SCHEMA == dtype) {
mSyncSid = event.data.getString("sid");
showTip("schema数据同步出错:" + event.arg2 + ",sid=" + mSyncSid);
}
}
} break;
}
}
若用户需要同时上传多条数据,但在上传结果回调里无法将上传数据与结果一一对应时,可在上传资源数据时加上sync_tag标签,数据上传后,在结果回调里也会将此标签带出。用户可通过此标签,将结果与上传数据对应。
上传使用示例如下:
String dataTag = "data_tag_1";
JsonObject params = new JsonObject();
params.put("tag", dataTag);
AIUIMessage syncAthenaMessage = new AIUIMessage(AIUIConstant.CMD_SYNC,AIUIConstant.SYNC_DATA_SCHEMA, 0, params.toString(), syncData);
mAIUIAgent.sendMessage(syncAthenaMessage);
结果回调示例如下:
private void processCmdReturnEvent(AIUIEvent event) {
switch (event.arg1) {
case AIUIConstant.CMD_SYNC: {
int dtype = event.data.getInt("sync_dtype");
String sync_tag = event.data.getString("tag");
} break;
}
}
#查询打包状态
通过CMD_SYNC
上传同步动态实体的资源数据后,AIUI服务端会进行处理然后生效,处理的过程是异步的,可以通过CMD_QUERY_SYNC_STATUS
查询上传的资源数据是否处理成功。查询需要等上传成功后,间隔至少3秒再去查询
arg1表示状态查询的类型,动态实体对应SYNC_DATA_SCHEMA
(常量对应值为3),params为json,包含需要对应同步上传操作的sid,示例如下:
JSONObject paramsJson = new JSONObject();
paramsJson.put("sid", mSyncSid);
AIUIMessage querySyncMsg = new AIUIMessage(AIUIConstant.CMD_QUERY_SYNC_STATUS,AIUIConstant.SYNC_DATA_SCHEMA, 0,paramsJson.toString(), null);
mAIUIAgent.sendMessage(querySyncMsg);
iOS的代码如下
NSMutableDictionary *queryJson = [NSMutableDictionary dictionaryWithObjectsAndKeys:self.syncSid, @"sid", nil];
NSError *err;
NSData *data = [NSJSONSerialization dataWithJSONObject:queryJson options:NSJSONWritingPrettyPrinted error:&err];
if(!data){
SLog(@"parse error! queryJson=%@", queryJson);
return;
}
NSString *dataStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
IFlyAIUIMessage *msg = [[IFlyAIUIMessage alloc] init];
msg.msgType = CMD_QUERY_SYNC_STATUS;
msg.arg1 = SYNC_DATA_SCHEMA;
msg.params = dataStr;
[self.aiuiAgent sendMessage:msg];
CMD_QUERY_SYNC_STATUS
执行完成后会有EVENT_CMD_RETURN
事件回调,表示查询结果,解析示例如下:
private void processCmdReturnEvent(AIUIEvent event) {
switch (event.arg1) {
//schema数据打包结果查询结果
case AIUIConstant.CMD_QUERY_SYNC_STATUS: {
int syncType = event.data.getInt("sync_dtype");
if (AIUIConstant.SYNC_DATA_QUERY == syncType) {
String result = event.data.getString("result");
if (0 == event.arg2) {
showTip("查询结果:" + result);
} else {
showTip("schema数据状态查询出错:" + event.arg2 + ", result:" + result);
}
}
} break;
}
}
注1:请上传资源数据后至少间隔3秒后再进行查询打包状态操作。
注2:具体代码示例工程可参考AIUIChatDemo中AIUIRepository类的queryDynamicSyncStatus方法实现。
#生效使用
通过CMD_SET_PARAMS
设置pers_param
即可使用已设置的动态实体(CMD_SET_PARAMS
具体用法参见动态配置
生效应用级动态实体:
{
"audioparams": {
"pers_param":"{\"appid\":\"\"}"
}
}
生效用户级动态实体:
{
"audioparams": {
"pers_param":"{\"uid\":\"\"}"
}
}
生效自定义级动态实体:
{
"audioparams": {
"pers_param":"{\"custom_key\":\"custom_val\"}"
}
}
如果需要在本机器上生效当前应用对应的所有应用级的动态实体,在pers_param
加入\"appid\":\"\"
(值留空, AIUI中会自动补全appid和uid的值),同理用户级动态实体生效需要加入\"uid\":\"\"
。
对于自定义维度需要用后台定义实体时的自定义维度名作为key,使用动态上传指定的自定义维度作为值。如 后台定义的自定义维度名为vendor,那在动态上传时就需要构造如下数据进行上传:
{
"param": {
"appid": "xxxxxx",// appid
"id_name": "vendor",// 自定义维度名
"id_value": "spec_vendor", // 自定义维度value
"res_name": "user_applist" // 资源名称
},
"data": "xxxxxx"// 与schema名称对应的数据内容base64编码
}
那对应需要在交互时使用该自定义维度对应的动态实体就需要加入\"vendor\":\"spec_vendor\"
。
除了通过CMD_SET_PARAMS
设置pers_param
:
注意: set audioParams这种方式只会对语音交互生效,文本语义参考下面数据写入带pers_param的方式
JSONObject params = new JSONObject();
JSONObject audioParams = new JSONObject();
audioParams.put("pers_param", "{\"appid\":\"\"}");
params.put("audioparams", audioParams);
AIUIMessage setMsg = new AIUIMessage(CMD_SET_PARAMS, 0 , 0, params.toString(), "");
mAgent.sendMessage(setMsg);
也可以在音频写入时设置该参数:
//写入音频
byte[] audio = xxx; //初始化
String params = "data_type=audio,sample_rate=16000,pers_param={\"appid\":\"\"}";
AIUIMessage msg = new AIUIMessage(AIUIConstant.CMD_WRITE, 0, 0, params, audio);
mAIUIAgent.sendMessage(msg);
如需同时生效使用多种级别的动态实体,可直接在pers_param
里添加对应的级别的参数即可,示例如下:
{
"audioparams": {
"pers_param":"{\"appid\":\"\", \"uid\":\"\"}"
}
}
或
audioParams.put("pers_param", "{\"appid\":\"\", \"uid\":\"\"}");
注:具体代码示例工程可参考AIUIChatDemo中AIUIRepository类的queryDynamicSyncStatus方法实现。
#动态实体QA
Q:上传资源数据返回的状态与查询打包状态有什么区别?
A:上传资源数据时返回的状态代表数据是否上传成功至服务端,主要是用来反馈上传的数据格式是否正确;查询打包状态代表数据上传至后台后是否将这些资源数据处理成功,主要是用来反馈数据能否被正常生效使用。
Q:动态实体上传失败的原因一般有哪些?
A:①res_name未加上命名空间;②上传资源字段与后台不对应
Q:动态实体不能正常生效使用的原因一般有哪些?
A:①未设置pers_param参数;②上传资源数据时如果是在文本文件里读取的内容,请注意不要将文件的BOM头带入数据中或者直接将其转为无BOM头的文件;③aiui.cfg文件里未指定appid。