动态实体创建

转自aiui文档
静态实体的适用范围很大,但当开发者遇到以下几种用例时可能不能满足,为此我们推出了动态实体,满足开发者各终端的实体需要差异化设置时的需求。

#应用级

image

举例

以开发两款百科 App 为例,分别是《水浒传百科》和《西游记百科》,开发者期望用户的交互方式为:

我想听李逵的故事

介绍一下孙悟空

我们可以把以上两句话抽象为一个自定义技能,其中语料如下:

我想听{name}的故事

介绍一下{name}

《水浒传百科》和《西游记百科》两个 App 均添加了这个技能,但是这两个 App 的实体并不相同。{name}内容分别如下:

image.png

为了实现这个目标,我们提供了动态实体(应用级)动态实体(应用级)可以在实体名相同的情况下,为每个 appid 设置不同的实体副本。

动态实体(应用级)作用域为 appid,生效时间为永久。

#用户级

image

举例

以开发一款电话 App 为例, 开发者期望用户的提问方式为:

打电话给张三

呼叫李四

我们可以把以上两句话抽象为一个自定义技能,其中语料如下:

打电话给{contacts}

呼叫{contacts}

由于每个用户的联系人名称都不一样,因此无法使用静态实体。为此 AIUI 的 SDK 中,为开发者提供了相应的 API 接口,开发者可以针对每个用户维护一个实体副本。动态实体(用户级)只对特定用户生效,生效时间为永久。

#自定义级

image

举例

以开发一个全国连锁餐厅点餐 App 为例, 开发者期待用户的交互方式为:

我想吃杭椒牛柳

点一份宫保鸡丁

我们将其抽象为一个自定义技能,其中语料如下:

我想吃{dish}

点一份{dish}

但是由于该连锁餐厅全国不同省份的菜单不一样,每个省份有若干台点餐终端,但是实体不能通用。

为了解决这个问题,我们引入了动态实体(自定义级)

我们为这款点餐 App 新建了一个动态实体命名为" dish",同时在这个实体下创建两个资源,分别是"dish_general" 和"dish_specail",其中 "dish_general" 的维度为应用级," dish_specail" 的维度为自定义,我们把这个自定义的维度命名为"province"。在控制台的设置如下:

image

如图所示,开发者可以为一个动态实体,设置两个维度的资源。

其中 dish_general 中,开发者可以通过 API上传全国通用的菜单。

针对 dish_special, 开发者可以通过维度名 province 上传各个省份的菜单,例如 Anhui,Beijing,Fujian 等,具体操作请参考 SDK 文档。

使用动态实体(自定义级)开发者可以自定义分组,实体设置成功后,只对属于特定分组的终端生效。

#所见即可说

举例

假设开发者要在汽车中控上开发一款导航技能,开发者期望用户的交互方式为:

导航到中科大

此时屏幕上的数据如下:

image

用户可能说的话术包含:

中国科学技术大学东校区``我要去西校区``第三个

我们将这些话术抽象为语料:

{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就对应uidid_value是维度具体值,如id_nameuidid_value就需要是该资源针对生效的用户的具体UID,AIUI会使用 当前用户UID进行补全,appid同理。自定义维度因为是由开发者自定义,所以id_nameid_value都需要设置具体值。

通过指定id_nameid_value,上传的动态实体资源数据到服务端就有了唯一的从属,在生效使用时就可以指定此处的id_nameid_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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容