# 动态实体

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

# 动态实体的定义和维度

一个动态实体的定义可以包含多个资源Resource的定义,资源定义中包含了资源名称,以及从客户端上传数据抽取说法的字段名以及生效的维度。 生效的维度目前有用户级(uid)、应用级(appid)、自定义级,在客户端上传对应的资源数据时,维度信息需要和定义时保持一致。

# 应用级

举例

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

我想听李逵的故事

介绍一下孙悟空

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

我想听{name}的故事

介绍一下{name}

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

水浒传 name 实体 西游记 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 文档。

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

# 动态实体的上传

动态实体的上传有两种方式:通过动态实体上传的 WebAPI 接口,和通过 AIUI SDK 对应接口。

# WebAPI上传

本接口为你提供了通过 HTTP 的方式上传动态实体资源,你可以通过本接口上传资源。

动态实体的使用分两步,上传资源,资源生效。本接口只提供上传资源,Android、iOS、Windows、Linux 请通过CMD_SET_PARAMS设置pers_param使之生效,具体方法请参阅SDK示例,WebSocket API 应用请传pers_param参数。

说明

  • 授权认证,调用接口需要将nameSpace,nonce,curtime和checkSum信息放在HTTP请求头中。
  • 所有接口统一为UTF-8编码。
  • 所有接口支持http和https。

# 调用示例

Github (opens new window)

# 授权认证

说明

AccountKey 为账户级Key,每个AIUI 账户具有一个AccountKey,在「技能工作室-我的实体-动态实体密钥」中即可查看。

在调用所有业务接口时,都需要在Http Request Header中加入以下参数作为授权验证

参数名 说明 是否必须
X-NameSpace aiui开放平台的命名空间,在「技能工作室-我的实体-动态实体密钥」中查看
X-Nonce 随机数(最大长度128个字符)
X-CurTime 当前UTC时间戳,从1970年1月1日0点0 分0 秒开始到现在的秒数(String)
X-CheckSum MD5(accountKey + Nonce + CurTime),三个参数拼接的字符串,进行MD5哈希计算

注意

  • CheckSum有效期:出于安全性考虑,每个CheckSum的有效期为5分钟(用curTime计算),同时CurTime要与标准时间同步,否则,时间相差太大,服务端会直接认为CurTime无效。
  • checkSum生成示例,例如:
accountKey是abcd1234, Nonce是12, CurTime是1502607694。
那么CheckSum为MD5(abcd1234121502607694)
最终MD5为32位小写 bf5aa1f53bd173cf7413bf370ad4bddc

# IP 白名单

IP 白名单具备打开和关闭两种状态。

  • 当 IP 白名单打开时,用户在调用所有业务接口时,在授权认证通过后,检查调用方ip是否在aiui开放平台配置的ip白名单中。若在,则向用户提供服务,否则拒绝提供服务。

  • 当 IP 白名单关闭时,任意终端均可访问 AIUI 服务器,开发者需要自行保证 nameSpace 和 key 值安全。

注意

拒绝提供服务返回值:

{
  "code":"20004",
  "desc":"ip非法",
  "data":null,
  "sid":"rwabb52e660@dx6c9b0e56f81d3ef000"
}

# 上传资源接口

本接口提供动态实体上传资源功能,用于动态更新实体资源。

接口地址

	POST http[s]://openapi.xfyun.cn/v2/aiui/entity/upload-resource HTTP/1.1
	Content-Type:application/x-www-form-urlencoded; charset=utf-8

# 参数说明

参数 类型 必须 说明
示例
appid string 应用id 5adde3cf
res_name String 资源名,XXX为用户的命名空间 XXX.music
pers_param String 个性化参数(json) {"appid":"xxxxxx"}
data String Base64编码的资源 示例1

其中,pers_param为个性化参数。示例如下:

维度 示例 说明
应用级 {"appid":"xxxxxx"}
用户级 {"auth_id":
"d3b6d50a9f8194b623b5e2d4e298c9d6"}
auth_id为用户唯一ID(32位字符串,包括英文小写字母与数字,开发者需保证该值与终端用户一一对应)
自定义级 {"xxxxxx":"xxxxxx"}

data为web页面定义的主子段、从字段给的json格式对应的base64。例如,主字段为song、从字段singer,上传资源的格式为:

{"song":"给我一首歌的时间","singer":"周杰伦"}
{"song":"忘情水","singer":"刘德华"}
{"song":"暗香","singer":"刘德华"}
{"song":"逆光","singer":"梁静茹"}

注:每条数据之间用换行符隔开。

Base64编码为

eyJzb25nIjoi57uZ5oiR5LiA6aaW5q2M55qE5pe26Ze0Iiwic2luZ2VyIjoi5ZGo5p2w5LymIn0NCnsic29uZyI6IuW/mOaDheawtCIsInNpbmdlciI6IuWImOW+t+WNjiJ9DQp7InNvbmciOiLmmpfpppkiLCJzaW5nZXIiOiLliJjlvrfljY4ifQ0KeyJzb25nIjoi6YCG5YWJIiwic2luZ2VyIjoi5qKB6Z2Z6Iy5In0=

# 返回说明

参数名 说明 是否必须
code 结果码
data 返回结果,见data字段说明
desc 描述
sid 本次webapi服务唯一标识

data字段说明

参数 类型 必须 说明 示例
sid String 本次上传sid,可用于查看上传资源是否成功 psn003478f3@ch00070e3a78e06f2601
csid String 本次服务唯一标识 rwa84b7a73b@ch372d0e3a78e0116200

# 查看上传资源是否成功接口

本接口提供检查动态实体上传资源是否成功。

注意

上传资源数据后至少间隔10秒后再进行查看上传资源是否成功

接口地址

	POST http[s]://openapi.xfyun.cn/v2/aiui/entity/check-resource HTTP/1.1
	Content-Type:application/x-www-form-urlencoded; charset=utf-8

# 参数说明

参数 类型 必须 说明 示例
sid string sid psn开头的sid

# 返回说明

参数名 说明 是否必须
code 结果码
data 返回结果,见data字段说明
desc 描述
sid 本次webapi服务唯一标识

data字段说明

参数 类型 必须 说明
sid String 上传sid
csid String 上传sid
reply String 查看上传资源是否成功描述
error int 查看上传资源是否成功错误码

# 资源生效

资源上传成功后5min生效。你可以通过webapi 请求,传pers_param参数验证是否已生效。查看详情 (opens new window)

# AIUI SDK上传

除WebAPI接口外,动态实体的上传也可以配合客户端 SDK 进行,完整文档建议参阅 AIUI SDK 接入文档 (opens new window)

# 动态上传资源数据

通过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字段), 示例如下:

{"appName": "微信", "alias": "wechat", "extra": "xxx"}
{"appName": "新浪微博", "alias": "微博", "extra": "yyy"}
{"appName": "Telegram", "alias": "电报", "extra": "zzz"}
{"appName": "讯飞输入法", "alias": "", "extra": "uuu"}

注:每条数据之间用换行符隔开。

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);

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查询上传的资源数据是否处理成功。

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);

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:请上传资源数据后至少间隔10秒后再进行查询打包状态操作。

注2:具体代码示例工程可参考AIUIChatDemo (opens new window)中AIUIRepository类的queryDynamicSyncStatus方法实现。

注3:动态实体上传后5min生效。。

# 生效使用

动态实体上传后5min生效,届时可以通过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 (opens new window)中AIUIRepository类的queryDynamicSyncStatus方法实现。动态实体需要5min才能生效。

# 动态实体QA

Q:上传资源数据返回的状态与查询打包状态有什么区别?

**A:**上传资源数据时返回的状态代表数据是否上传成功至服务端,主要是用来反馈上传的数据格式是否正确;查询打包状态代表数据上传至后台后是否将这些资源数据处理成功,主要是用来反馈数据能否被正常生效使用。

Q:动态实体上传失败的原因一般有哪些?

**A:**①res_name未加上命名空间;②上传资源字段与后台不对应

Q:动态实体不能正常生效使用的原因一般有哪些?

**A:**①未设置pers_param参数;②上传资源数据时如果是在文本文件里读取的内容,请注意不要将文件的BOM头带入数据中或者直接将其转为无BOM头的文件;③aiui.cfg文件里未指定appid。