背景分析
企业可能已经在使用第三方产品,如企业微信,来管理客户关系和内部沟通。智能质检在支持电话语音坐席、工单服务、IM服务的行业等主流行业的质检需求外,同样支持对第三方产品的会话内容进行智能分析和质检。
基本业务流程
图1所示为企业微信进行实时文本质检或离线文本质检的对接流程。通过集成企业微信消息传递SDK,拉取企微会话文本,支持实时或离线拉取,形成待检数据集,为企业微信会话提供全面的对话内容分析。
在该流程中,待检数据首先与业务人员设定的质检规则相结合,利用智能算法进行匹配分析。分析结果将实时通知至质检结果处理人员,确保每一环节的准确性和及时性。同时,服务支持处理人员在线进行人工校验,以进一步提升分析的精确度,并允许保存复核结果,确保信息的可追溯性。
图一 企业微信支持智能质检的业务流程图
获取第三方会话记录
下文将以企业微信为例,介绍获取聊天记录的基本业务流程:
开启接收消息模式:
在企业微信管理后台,进入自建应用的管理页面。
开启接收消息,设置接收消息的参数。
配置接收消息参数:
设置接收消息的URL、Token、EncodingAESKey参数。
企业微信会向设置的URL发送一条验证消息(GET请求)验证URL请求。
配置方案参考接收消息与事件。
使用接收消息:
一旦URL验证成功,接收消息模式将被开启。
企业微信将把用户发送的消息以POST请求的形式推送到设置的URL。
消息传输:
企业微信在推送消息给企业时,会对消息内容做AES加密,以XML格式POST到企业应用的URL上。企业在被动响应时,也需要对数据加密,以XML格式返回给企业微信。下文的消息格式以解密后的明文举例,加解密方案参考企业微信加解密方案。
企业微信不同语言的加解密库可参考加解密库下载与返回码。
以Java为例:
Maven依赖:引入所需依赖。
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20231013</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
完整调用demo(入口为main方法):
import org.json.JSONObject;
import com.qq.weixin.mp.aes.WXBizJsonMsgCrypt;
public class Sample {
public static void main(String[] args) throws Exception {
String sToken = "QDG6eK";
String sCorpID = "wx5823bf96d3bd56c7";
String sEncodingAESKey = "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C";
WXBizJsonMsgCrypt wxcpt = new WXBizJsonMsgCrypt(sToken, sEncodingAESKey, sCorpID);
/*
------------使用示例一:验证回调URL---------------
*企业开启回调模式时,企业微信会向验证url发送一个get请求
假设点击验证时,企业收到类似请求:
* GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3×tamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D
* HTTP/1.1 Host: qy.weixin.qq.com
接收到该请求时,企业应
1.解析出Get请求的参数,包括消息体签名(msg_signature),时间戳(timestamp),随机数字串(nonce)以及企业微信推送过来的随机加密字符串(echostr),这一步注意作URL解码。
2.验证消息体签名的正确性
3. 解密出echostr原文,将原文当作Get请求的response,返回给企业微信
第2,3步可以用企业微信提供的库函数VerifyURL来实现。
*/
// 解析出url上的参数值如下:
// String sVerifyMsgSig = HttpUtils.ParseUrl("msg_signature");
String sVerifyMsgSig = "5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3";
// String sVerifyTimeStamp = HttpUtils.ParseUrl("timestamp");
String sVerifyTimeStamp = "1409659589";
// String sVerifyNonce = HttpUtils.ParseUrl("nonce");
String sVerifyNonce = "263014780";
// String sVerifyEchoStr = HttpUtils.ParseUrl("echostr");
String sVerifyEchoStr = "P9nAzCzyDtyTWESHep1vC5X9xho/qYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp+4RPcs8TgAE7OaBO+FZXvnaqQ==";
String sEchoStr; //需要返回的明文
try {
sEchoStr = wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp,
sVerifyNonce, sVerifyEchoStr);
System.out.println("verifyurl echostr: " + sEchoStr);
// 验证URL成功,将sEchoStr返回
// HttpUtils.SetResponse(sEchoStr);
} catch (Exception e) {
//验证URL失败,错误原因请查看异常
e.printStackTrace();
}
/*
------------使用示例二:对用户回复的消息解密---------------
用户回复消息或者点击事件响应时,企业会收到回调消息,此消息是经过企业微信加密之后的密文以post形式发送给企业,密文格式请参考官方文档
假设企业收到企业微信的回调消息如下:
POST /cgi-bin/wxpush? msg_signature=477715d11cdb4164915debcba66cb864d751f3e6×tamp=1409659813&nonce=1372623149 HTTP/1.1
Host: qy.weixin.qq.com
Content-Length:
Content-Type:text/json
{
"tousername":"wx5823bf96d3bd56c7",
"encrypt":"RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==",
"agentid":"218"
}
企业收到post请求之后应该 1.解析出url上的参数,包括消息体签名(msg_signature),时间戳(timestamp)以及随机数字串(nonce)
2.验证消息体签名的正确性。
3.将post请求的数据进行json解析,并将"encrypt"标签的内容进行解密,解密出来的明文即是用户回复消息的明文,明文格式请参考官方文档
第2,3步可以用企业微信提供的库函数DecryptMsg来实现。
*/
// String sReqMsgSig = HttpUtils.ParseUrl("msg_signature");
String sReqMsgSig = "0623cbc5a8cbee5bcc137c70de99575366fc2af3";
// String sReqTimeStamp = HttpUtils.ParseUrl("timestamp");
String sReqTimeStamp = "1409659813";
// String sReqNonce = HttpUtils.ParseUrl("nonce");
String sReqNonce = "1372623149";
// post请求的密文数据
// sReqData = HttpUtils.PostData();
String sReqData = "{\"tousername\":\"wx5823bf96d3bd56c7\",\"encrypt\":\"CZWs4CWRpI4VolQlvn4dlEC1alN2MUEY2VklGehgBVLBrlVF7SyT+SV+Toj43l4ayJ9UMGKphktKKmP7B2j/P1ey67XB8PBgS7Wr5/8+w/yWriZv3Vmoo/MH3/1HsIWZrPQ3N2mJrelStIfI2Y8kLKXA7EhfZgZX4o+ffdkZDM76SEl79Ib9mw7TGjZ9Aw/x/A2VjNbV1E8BtEbRxYYcQippYNw7hr8sFfa3nW1xLdxokt8QkRX83vK3DFP2F6TQFPL2Tu98UwhcUpPvdJBuu1/yiOQIScppV3eOuLWEsko=\",\"agentid\":\"218\"}";
try {
String sMsg = wxcpt.DecryptMsg(sReqMsgSig, sReqTimeStamp, sReqNonce, sReqData);
System.out.println("after decrypt msg: " + sMsg);
// TODO: 解析出明文json标签的内容进行处理
// For example:
JSONObject json = new JSONObject(sMsg);
String Content = json.getString("Content");
String CreateTime = json.getString("CreateTime");
String customerOrAgent = json.getString("FromUserName");
System.out.println("Content:" + Content);
System.out.println("CreateTime:" + CreateTime);
System.out.println("Character:" + customerOrAgent);
//System.out.println("");
} catch (Exception e) {
// TODO
// 解密失败,失败原因请查看异常
e.printStackTrace();
}
/*
------------使用示例三:企业回复用户消息的加密---------------
企业被动回复用户的消息也需要进行加密,并且拼接成密文格式的json串。
假设企业需要回复用户的明文如下:
{
"ToUserName": "mycreate",
"FromUserName":"wx5823bf96d3bd56c7",
"CreateTime": 1348831860,
"MsgType": "text",
"Content": "this is a test",
"MsgId": 1234567890123456,
"AgentID": 128
}
为了将此段明文回复给用户,企业应: 1.自己生成时间戳(timestamp),随机数字串(nonce)以便生成消息体签名,也可以直接用从企业微信的post url上解析出的对应值。
2.将明文加密得到密文。 3.用密文,步骤1生成的timestamp,nonce和企业在企业微信设定的token生成消息体签名。 4.将密文,消息体签名,时间戳,随机数字串拼接成json格式的字符串,发送给企业。
以上2,3,4步可以用企业微信提供的库函数EncryptMsg来实现。
*/
String sRespData = "{\"ToUserName\":\"wx5823bf96d3bd56c7\",\"FromUserName\":\"mycreate\",\"CreateTime\": 1409659813,\"MsgType\":\"text\",\"Content\":\"hello\",\"MsgId\":4561255354251345929,\"AgentID\": 218}";
try{
String sEncryptMsg = wxcpt.EncryptMsg(sRespData, sReqTimeStamp, sReqNonce);
System.out.println("after encrypt sEncrytMsg: " + sEncryptMsg);
// 加密成功
// TODO:
// HttpUtils.SetResponse(sEncryptMsg);
}
catch(Exception e)
{
e.printStackTrace();
// 加密失败
}
}
}
消息格式:
文本消息明文示例及参数说明参考消息格式。
控制台发起质检分析
将多句文本整理为数据集后,可上传至智能对话分析控制台创建数据集任务发起质检分析。
文本数据集中要求提供的角色、开始时间、内容与文本消息XML中的FromUserName、CreateTime、Content一一对应。
创建质检规则、质检任务配置、执行质检任务和质检结果复核相关内容请参考创建质检规则。
接入SDK发起质检分析
离线文本对话具体说明参考UploadDataV4 - 上传文本质检V4。
调用智能对话分析的UploadDataV4接口,发起离线文本质检分析。
实时文本检测具体说明参考UploadDataSync - 文本实时质检。
调用智能对话分析的UploadDataSync接口,发起实时文本质检分析。
以Java代码为例:
maven依赖,引入质检SDK。
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-qualitycheck</artifactId>
<version>3.0.7</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.0.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83_noneautotype</version>
</dependency>
完整调用demo(入口为main方法):
说明
调用接口前,需配置环境变量,通过环境变量读取访问凭证。关于配置环境变量的操作,请参见使用说明。
智能对话分析的AccessKey ID和AccessKey Secret的环境变量名:SCA_AK_ENV
、SCA_SK_ENV
。
package com.aliyun.sample;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.aliyuncs.qualitycheck.model.v20190115.UploadDataRequest;
import com.aliyuncs.qualitycheck.model.v20190115.UploadDataResponse;
import com.aliyuncs.qualitycheck.model.v20190115.UploadDataSyncRequest;
import com.aliyuncs.qualitycheck.model.v20190115.UploadDataSyncResponse;
/**
* 发起离线/实时文本质检Demo
*/
public class Sample {
/**
* 此处通过从环境变量中读取AccessKey,初始化智能对话分析Client
* @return Client
* @throws Exception
*/
public static IAcsClient createSCAClient() throws Exception {
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户
// 此处以把AccessKey 和 AccessKeySecret 保存在环境变量为例说明。您也可以根据业务需要,保存到配置文件里
// 强烈建议不要把 AccessKey 和 AccessKeySecret 保存到代码里,会存在密钥泄漏风险
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", System.getenv("SCA_AK_ENV"), System.getenv("SCA_SK_ENV"));
//指定服务接入地址,以下值为固定
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", "Qualitycheck", "qualitycheck.cn-hangzhou.aliyuncs.com");
return new DefaultAcsClient(profile);
}
/**
* 组装入参JsonStr,调用文本上传CDK,上传质检数据
* @param args_
* @throws Exception
*/
public static void main(String[] args_) throws Exception {
/**
* 1. 组装调用参数
*/
String jsonStr = null;
/**
* uploadData 上传音频质检, 入参jsonStr
*
* jsonStr示例:其中dialogue为必填项,其余为选填项,详细文档见https://help.aliyun.com/zh/sca/developer-reference/api-qualitycheck-2019-01-15-uploaddata
* {
* "isSchemeData": 1,
* "ruleIds": [
*
* ],
* "tickets": [
* {
* "skillGroupName": "技能组******",
* "callType": "3",
* "callee": "176********",
* "caller": "057**********",
* "business": "北京",
* "callStartTime": "17************",
* "customerServiceId": "270***********",
* "customerServiceName": "ServiceName",
* "remark1": "****",
*
* "remark25": "*****",
* "dialogue": [
* {
* "beginTime": "2000-12-31 23:59:58",
* "role": "客户",
* "identity": "某客户",
* "emotionValue": 6,
* "speechRate": 111,
* "words": "你好。",
* "end": "2580",
* "begin": "1800",
* "channelId": "1"
* },
* {
* "beginTime": "2000-12-31 23:59:59",
* "role": "客服",
* "identity": "某客服",
* "emotionValue": 6,
* "speechRate": 222,
* "words": "请问有什么可以帮您。",
* "end": 8540,
* "begin": 6770,
* "channelId": 0
* }
* ],
* "fileName": "yourfilename"
* }
* ]
* }
*
* uploadDataSync 实时上传音频质检, 入参jsonStr,详细文档见https://help.aliyun.com/zh/sca/developer-reference/api-qualitycheck-2019-01-15-uploaddatasync
* {
* "ruleIds": [
* 1
* ],
* "tickets": [
* {
* "tid": "本段文本的ID,可以使用对应的电话或工单数据 ID,注意不要重复",
* "dialogue": [
* {
* "role": "客服",
* "words": "你好",
* "end": 5000,
* "begin": 1000,
* "beginTime":"2000-12-31 23:59:59"
* }
* ]
* }
* ]
* }
*/
uploadData(jsonStr);
uploadDataSync(jsonStr);
}
// 上传离线文本质检数据
public static void uploadData(String jsonStr) {
UploadDataRequest req = new UploadDataRequest();
req.setJsonStr(jsonStr);
try {
UploadDataResponse response = createSCAClient().getAcsResponse(req);
System.out.println(response.getCode());
System.out.println(response.getMessage());
System.out.println(response.getData());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 上传实时文本质检数据
public static void uploadDataSync(String jsonStr) {
UploadDataSyncRequest req = new UploadDataSyncRequest();
req.setJsonStr(jsonStr);
try {
UploadDataSyncResponse response = createSCAClient().getAcsResponse(req);
System.out.println(response.getCode());
System.out.println(response.getMessage());
System.out.println(response.getData());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}