C#开发微信门户及应用(31)--微信语义理解接口的实现和处理
微信语义理解接口提供从用户自然语言输入到结构化解析的技术实现,使用先进的自然语言处理技术给开发者提供一站式的语义解析方案。该平台覆盖多个垂直领域的语义场景,部分领域还可以支持取得最终的展示结果。开发者无需掌握语义理解及相关技术,只需根据自己的产品特点,选择相应的服务即可搭建一套智能语义服务。结合语音识别接口,通过微信语音识别得到用户的语音信息之后,经过语义分析理解,得到用户需求,及时回复用户。本文介绍如何实现对微信语义接口的封装处理,以及一些常用场景的调用。
1)微信语义理解接口
这个东西也就是把我们日常的话语(称之为自然语言)解析为对应的信息结构体,方便我们提取里面的相关信息进行搜索查询,并精确回应给对应的请求者的一个桥梁,其主要的功能就是解析我们所说的内容。
微信开放平台语义理解接口调用(http请求)简单方便,用户无需掌握语义理解及相关技术,只需根据自己的产品特点,选择相应的服务即可搭建一套智能语义服务。我们来看看微信语义理解接口的定义内容。
http请求方式: POST(请使用https协议)
https://api.weixin.qq.com/semantic/semproxy/search?access_token=YOUR_ACCESS_TOKEN
POST数据格式:JSON,POST数据例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
{ "query" : "查一下明天从北京到上海的南航机票" , "city" : "北京" , "category" : "flight,hotel" , "appid" : "wxaaaaaaaaaaaaaaaa" , "uid" : "123456" } |
参数说明
参数 | 是否必须 | 参数类型 | 说明 |
access_token | 是 | String | 根据appid和appsecret获取到的token |
query | 是 | String | 输入文本串 |
category | 是 | String | 需要使用的服务类型,多个用“,”隔开,不能为空 |
latitude | 见接口协议文档 | Float | 纬度坐标,与经度同时传入;与城市二选一传入 |
longitude | 见接口协议文档 | Float | 经度坐标,与纬度同时传入;与城市二选一传入 |
city | 见接口协议文档 | String | 城市名称,与经纬度二选一传入 |
region | 见接口协议文档 | String | 区域名称,在城市存在的情况下可省;与经纬度二选一传入 |
appid | 是 | String | 公众号唯一标识,用于区分公众号开发者 |
uid | 否 | String | 用户唯一id(非开发者id),用户区分公众号下的不同用户(建议填入用户openid),如果为空,则无法使用上下文理解功能。appid和uid同时存在的情况下,才可以使用上下文理解功能。 |
注:单类别意图比较明确,识别的覆盖率比较大,所以如果只要使用特定某个类别,建议将category只设置为该类别。
返回说明 正常情况下,微信会返回下述JSON数据包:
{ “errcode”:0, “query”:”查一下明天从北京到上海的南航机票”, “type”:”flight”, “semantic”:{ “details”:{ “start_loc”:{ “type”:”LOC_CITY”, “city”:”北京市”, “city_simple”:”北京”, “loc_ori”:”北京” }, “end_loc”: { “type”:”LOC_CITY”, “city”:”上海市”, “city_simple”:”上海”, “loc_ori”:”上海” }, “start_date”: { “type”:”DT_ORI”, “date”:”2014-03-05”, “date_ori”:”明天” }, “airline”:”中国南方航空公司” }, “intent”:”SEARCH” }
返回参数说明
参数 | 是否必须 | 参数类型 | 说明 |
errcode | 是 | Int | 表示请求后的状态 |
query | 是 | String | 用户的输入字符串 |
type | 是 | String | 服务的全局类型id,详见协议文档中垂直服务协议定义 |
semantic | 是 | Object | 语义理解后的结构化标识,各服务不同 |
result | 否 | Array | 部分类别的结果 |
answer | 否 | String | 部分类别的结果html5展示,目前不支持 |
text | 否 | String | 特殊回复说明 |
上面就是微信官方给出的代码案例,以及一个《语义理解接口协议文档》,里面介绍了各个场景的语义结构信息,虽然这个文档好像好久都没怎么更新,不过总体内容还是稳定的,我们可以通过这个文档进行相关的类库设计工作。
2、语义理解接口的C#实现
根据《语义理解接口协议文档》文档,我们可以定义各种所需的语义结构类库,这些是我们开展语义接口的基础类。
例如我们定义基础的时间协议类,如下所示。
/// <summary> /// 时间相关协议datetime /// </summary> public class Semantic_DateTime { /// <summary> /// 单时间的描述协议类型:“DT_SINGLE”。DT_SINGLE又细分为两个类别:DT_ORI和DT_INFER。DT_ORI是字面时间,比如:“上午九点”; /// DT_INFER是推理时间,比如:“提前5分钟”。 时间段的描述协议类型:“DT_INTERVAL” /// 重复时间的描述协议类型:“DT_REPEAT” DT_ REPEAT又细分为两个类别:DT_RORI和DT_RINFER。DT_RORI是字面时间,比如:“每天上午九点”;DT_RINFER是推理时间,比如:“工作日除外” /// </summary> public string type { get; set; } /// <summary> /// 24小时制,格式:HH:MM:SS,默认为00:00:00 /// </summary> public string time { get; set; } /// <summary> /// Time的原始字符串 /// </summary> public string time_ori { get; set; } } /// <summary> /// 单时间的描述协议datetime /// </summary> public class Semantic_SingleDateTime : Semantic_DateTime { /// <summary> /// 格式:YYYY-MM-DD,默认是当天时间 /// </summary> public string date { get; set; } /// <summary> /// 格式:YYYY-MM-DD 农历 /// </summary> public string date_lunar { get; set; } /// <summary> /// date的原始字符串 /// </summary> public string date_ori { get; set; } }
当然时间还有很多类型的定义,都基本上按照文档所列的字段进行处理,上面的代码只是定义了常用的单时间的描述协议类型:“DT_SINGLE”。
除了时间协议,还有数字,地点位置等相关协议,如数字协议如下所示。
public class Semantic_Number { /// <summary> /// 大类型:“NUMBER” NUMBER又细分为如下类别:NUM_PRICE、NUM_PADIUS、NUM_DISCOUNT、NUM_SEASON、NUM_EPI、NUM_CHAPTER。 /// </summary> public string type { get; set; } /// <summary> /// 开始 /// </summary> public string begin { get; set; } /// <summary> /// 结束 /// </summary> public string end { get; set; } }
地点位置协议如下所示
/// <summary> /// 地点相关协议 /// </summary> public class Semantic_Location { /// <summary> /// 大类型:“LOC” LOC又细分为如下类别:LOC_COUNTRY、LOC_PROVINCE、LOC_CITY、LOC_TOWN、LOC_POI、NORMAL_POI。 /// </summary> public string type { get; set; } /// <summary> /// 国家 /// </summary> [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public string country { get; set; } /// <summary> /// 省全称,例如:广东省 /// </summary> [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public string province { get; set; } /// <summary> /// 省简称,例如:广东|粤 /// </summary> [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public string province_simple { get; set; } /// <summary> /// 市全称,例如:北京市 /// </summary> [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public string city { get; set; } /// <summary> /// 市简称,例如:北京 /// </summary> [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public string city_simple { get; set; } ..............
前面我们看到了,语音立即的POST数据格式是一个较为固定的格式内容,我们可以把它定义为一个类,方便数据处理。
POST数据格式:JSON,POST数据例子如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
{ "query" : "查一下明天从北京到上海的南航机票" , "city" : "北京" , "category" : "flight,hotel" , "appid" : "wxaaaaaaaaaaaaaaaa" , "uid" : "123456" } |
那么我们可以定义它的类库如下所示。
/// <summary> /// 语义查询条件 /// </summary> public class SemanticQueryJson { /// <summary> /// 输入文本串 /// 必填 /// </summary> public string query { get; set; } /// <summary> /// 需要使用的服务类别,多个用,隔开,不能为空 /// 必填 /// </summary> public string category { get; set; } /// <summary> /// 城市名称,与经纬度二选一传入 /// 见说明,选填 /// </summary> [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public string city { get; set; } /// <summary> /// App id,开发者的唯一标识,用于区分开放者,如果为空,则没法使用上下文理解功能。 /// 非必填 /// </summary> public string appid { get; set; } /// <summary> /// 用户唯一id(并非开发者id),用于区分该开发者下不同用户,如果为空,则没法使用上下文理解功能。appid和uid同时存在的情况下,才可以使用上下文理解功能。 /// 非必填 /// </summary> public string uid { get; set; } ................ }
接着我们分析语义理解的接口返回值,它们基本上都是很有规律的内容,如下所示。
这样我们也就可以定义一个通用的类库对象,用来存储不同的返回内容了,如下代码所示。
/// <summary> /// 微信语义结果 /// </summary> public class SemanticResultJson<T> : ErrorJsonResult { /// <summary> /// 用户的输入字符串 /// </summary> public string query { get; set; } /// <summary> /// 服务的全局类别id /// </summary> public string type { get; set; } /// <summary> /// 语义理解后的结构化标识,各服务不同 /// </summary> public T semantic { get; set; } }
而其中的T semantic就是另外一个结构体里面的内容,这个结构体总体也是固定的内容,我们继续定义一个如下的类。
/// <summary> /// 详细信息里面的对象 /// </summary> /// <typeparam name="T"></typeparam> public class SemanticDetail<T> { /// <summary> /// 详细信息 /// </summary> public T details { get; set; } /// <summary> /// 查询类型 /// </summary> public string intent { get; set; } }
有了这些类库的支持,我们可以封装语义理解接口的返回值了,这样它的接口定义和封装处理代码如下所示。
/// <summary> /// 语意理解接口 /// 微信开放平台语义理解接口调用(http请求)简单方便,用户无需掌握语义理解及相关技术,只需根据自己的产品特点,选择相应的服务即可搭建一套智能语义服务。 /// </summary> public class SemanticApi : ISemanticApi { /// <summary> /// 发送语义理解请求 /// </summary> /// <param name="accessToken">调用接口凭证</param> /// <param name="data">查询条件</param> public SemanticResultJson<SemanticDetail<T>> SearchSemantic<T>(string accessToken, SemanticQueryJson data) { var url = string.Format("https://api.weixin.qq.com/semantic/semproxy/search?access_token={0}", accessToken); string postData = data.ToJson(); return JsonHelper<SemanticResultJson<SemanticDetail<T>>>.ConvertJson(url, postData); }
由于微信语义结果是针对不同的服务协议,我们需要根据这些不同的服务协议,来定义属于这些信息结构,如在文档里,我们可以看到有很多不同类型的服务协议。
根据文档的详细字段说明,我们可以定义不同服务的对应类库。
例如对于旅游服务的语义理解,它们的协议类如下所示。
/// <summary> /// 旅游服务(travel) /// </summary> public class Semantic_Details_Travel { /// <summary> /// 旅游目的地 /// </summary> public Semantic_Location location { get; set; } /// <summary> /// 景点名称 /// </summary> public string spot { get; set; } /// <summary> /// 旅游日期 /// </summary> public Semantic_SingleDateTime datetime { get; set; } /// <summary> /// 旅游类型词 /// </summary> public string tag { get; set; } /// <summary> /// 0默认,1自由行,2跟团游 /// </summary> public int category { get; set; } }
那么调用的旅游语义的案例代码如下所示
var api = new SemanticApi(); var json = new SemanticQueryJson { appid = appId, uid = openId, category = SemanticCategory.travel.ToString(), query = "故宫门票多少钱", city = "北京市" }; var travel = api.SearchSemantic<Semantic_Details_Travel>(token, json); Console.WriteLine(travel.ToJson());
如果我们测试,上面的代码跑起来会返回一个旅游的协议对象,包括了相关的数据信息。
{ "errcode" : 0, "query" : "故宫门票多少钱", "semantic" : { "details" : { "answer" : "", "context_info" : {}, "hit_str" : "故宫 门票 多少 钱 ", "spot" : "故宫" }, "intent" : "PRICE" }, "type" : "travel" }
我们再来看一个例子,例如对于航班服务,我们定义它的语义理解协议如下所示。
/// <summary> /// 航班服务(flight) /// </summary> public class Semantic_Details_Flight { /// <summary> /// 航班号 /// </summary> public string flight_no { get; set; } /// <summary> /// 出发地 /// </summary> public Semantic_Location start_loc { get; set; } /// <summary> /// 目的地 /// </summary> public Semantic_Location end_loc { get; set; } /// <summary> /// 出发日期 /// </summary> public Semantic_SingleDateTime start_date { get; set; } /// <summary> /// 返回日期 /// </summary> public Semantic_SingleDateTime end_date { get; set; } /// <summary> /// 航空公司 /// </summary> public string airline { get; set; } /// <summary> /// 座位级别(默认无限制):ECONOMY(经济舱)BIZ(商务舱)FIRST(头等舱) /// </summary> public string seat { get; set; } /// <summary> /// 排序类型:0排序无要求(默认),1价格升序,2价格降序,3时间升序,4时间降序 /// </summary> public int sort { get; set; } }
那么调用获取语义理解内容的代码如下所示。
json = new SemanticQueryJson { appid = appId, uid = openId, category = SemanticCategory.flight.ToString(), query = "查一下明天从广州到上海的南航机票", city = "广州" }; var flight = api.SearchSemantic<Semantic_Details_Flight>(token, json); Console.WriteLine(flight.ToJson());
我们可以获取到的JSON数据如下所示
{ "errcode" : 0, "query" : "查一下明天从广州到上海的南航机票", "semantic" : { "details" : { "airline" : "中国南方航空公司", "answer" : "已帮您预定2016-04-13,从广州市出发,前往上海市的航班。", "context_info" : { "isFinished" : "1", "null_times" : "0" }, "end_loc" : { "city" : "上海市", "city_simple" : "上海|沪|申", "loc_ori" : "上海", "modify_times" : "0", "slot_content_type" : "2", "type" : "LOC_CITY" }, "hit_str" : "查 一下 明天 从 广州 到 上海 南航 机票 ", "sort" : "1", "start_date" : { "date" : "2016-04-13", "date_lunar" : "2016-03-07", "date_ori" : "明天", "modify_times" : "0", "slot_content_type" : "2", "type" : "DT_ORI", "week" : "3" }, "start_loc" : { "city" : "广州市", "city_simple" : "广州", "loc_ori" : "广州", "modify_times" : "0", "province" : "广东省", "province_simple" : "广东|粤", "slot_content_type" : "2", "type" : "LOC_CITY" } }, "intent" : "SEARCH" }, "type" : "flight" }
这样就是我们把我们常规的语义,分析成了机器可以识别的准确的数据结构了,我们可以根据不同的语义场合对它进行分析,然后给用户进行不同的响应就可以了,结合微信语音识别为文本内容,我们可以把它做得很强大,有的类似机器智能的味道了。
微信语义理解是一个好东西,不过在微信官网上没有看到进一步的案例,如果能够有一些与实际结合的例子,估计更能帮助我们理解和应用了。