在线客服系统是一种网页版即时通讯软件的统称。主要能实现对其他通信软件无缝衔接(小编这里主要是指飞书和其他各大内部系统),为网站以及其他的应用提供和访客对话的平台。
准备工作
”so easy 嘛!需求文档, 拿来吧你~“
“需求图给你,接好~”
这个需求文档好像长得不太对劲???
没办法了,那只能靠个人想象发挥了~
正所谓“人靠衣装马靠鞍”,好看的UI框架是必不可少的。身为忠实阿里巴巴粉丝,始终坚信阿里巴巴出品必属精品!
最终我选择由阿里巴巴出品的 (能满足项目中基本需求)作为我的前端UI组件,而后端则选择Go语言中Web框架gin。
需求探讨
显然根据上文展示的需求文档进行一系列开发是不太现实的,这时就需要我们站起来和需求方进行深入交流(交流过程省略1万字。。。。自行脑补)
最终得出的需求有以下几点
机器人对话FQA(一问一答的方式)用户问题联想功能在线人工服务知识库浏览功能业务工单系统会话历史查询
1. 机器人对话FQA(一问一答的方式)
机器人对话FQA是什么呢? asked 的缩写,通俗的叫法“常见问题解答”。可以列出一些用户常见的问题,是一种在线帮助形式。
功能分析
表明机器人可以做什么
猜你想了解的
输入文字则返回相应的数据
功能1(表明机器人可以做什么?)
实现起来超简单的,只要后端返回当前登录用户,以及登录时间,简述机器人的作用即可。这里不过多描述
功能2(猜你想了解)
根据需求分析,得到以下效果。
用户所在场景分为两大类别:
飞书客户端(工作台)以及谷歌浏览器
公司内部系统嵌套
大致流程如下:
首先判断用户登录的地方开源智能客服系统,若是飞书登录。查询用户是否当天咨询过,如果咨询过则优先返回咨询过的数据,返回的数据条数不足10条,查询热门问题;否则 直接查询热门问题。若热门问题+咨询过的问题不足10条,则随机取;取到10条为止。
用户咨询过的行为用户表引擎进行记录(用户行为不能修改,只用于添加和查询,因此为了降低MySQL的压力,使用是最合适的选择)
猜你想了解流程图
掀起袖子,开始撸代码
// 1. 查询用户是否当天咨询过
ymd := fmt.Sprintf("%s-%s-%s 00:00:00", time.Now().Year(), time.Now().Month(), time.Now().Day())
stamp, _ := time.ParseInLocation("2006-01-02 15:04:05", ymd, time.Local)
rows, _ := config.G_clickhouse.Query(fmt.Sprintf("select content, count(content) As content_num from support_message where user_id = %d and create_time > toDateTime(%d) group by content order by content_num desc;", util.Int(checkExist["id"]), util.Int(stamp.Unix())))
recommendList := make([]map[string]interface{}, 0)
existsList := make([]string, 0)
for rows.Next(){
// 如果recommendList的长度等于10就退出
if len(recommendList) == 10{
return
}
var (
content string
content_num int
)
if err := rows.Scan(&content, &content_num);err !=nil{
Self.Write_resp(c,1, "clickhouse查询失败", "")
return
}
recommendList = append(recommendList, map[string]interface{}{
"title": content,
})
existsList = append(existsList, content)
}
// 1.1 如果当前的recommendList已经等于10,则不需要做任何操作
if len(recommendList) < 10{
// 1.2 如果当前的recommendList小于10,则有两个逻辑(1.追加当天热门问题且不重复的 2.随机不重复的问题)
// 1.2.1 追加当天热门不重复问题
hots,_ := config.G_clickhouse.Query(fmt.Sprintf("select content, count(content) As content_num from support_message where create_time > toDateTime(%d) group by content order by content_num desc;", util.Int(stamp.Unix())))
for hots.Next(){
... 执行追加逻辑
}
// 1.2.2 如果追加了当天热门不重复问题长度依然没有到长度10,则随机不重复问题了
if len(recommendList) < 10{
// 随机获取推荐的10条信息
... 在mysql中获取10条随机不重复的数据
}
}
通过一系列操作之后,实现的效果如图所示:
效果图
数据优先级:用户咨询过的 —– 热门类型 —- 随机
功能3(输入文本返回相应的数据)
用户输入某个大概词语或者关键字,输出对应的关键词。
由于小编这里快捷短语,联想输入和用户输入都是同一由前端send函数调用,因此没有对动作行为进行区分开。若要达到较好的用户体验,则需要对思路进行梳理。
具体实现思路:
具体的代码就不放出来,代码量较多。。
2. 用户问题联想功能(关键词自动补全 ,自动纠正)
作为资深搜索的用户,常常感叹的问题联想是怎么实现的?怎么这么牛逼的呢
经过多次查询资料后得出,实现类似功能。可以通过使用中的建议器( )进行实现。
于是掀起袖子,开始撸代码啦~~~~
首先创建索引名为ndex
PUT http://xxx.xxx.xxx.xxx/support_konwledege_index/
{
"settings": {
"number_of_shards": "1",
"index.refresh_interval": "15s",
"index": {
"analysis": {
"analyzer": {
"default": {
"tokenizer": "ik_max_word"
},
"pinyin_analyzer": {
"tokenizer": "shopmall_pinyin"
},
"first_py_letter_analyzer": {
"tokenizer": "first_py_letter"
},
"full_pinyin_letter_analyzer": {
"tokenizer": "full_pinyin_letter"
},
"ik_pinyin_analyzer":{
"tokenizer":"ik_smart"
}
},
"tokenizer": {
"shopmall_pinyin": ... 语法分析器根据索引进行配置即可,
"first_py_letter": ... 同上,
"full_pinyin_letter": {
"type": "pinyin",
"keep_separate_first_letter": false,
"keep_full_pinyin": false,
"keep_original": false,
"limit_first_letter_length": 16,
"lowercase": true,
"keep_first_letter": false,
"keep_none_chinese_in_first_letter": false,
"none_chinese_pinyin_tokenize": false,
"keep_none_chinese": true,
"keep_joined_full_pinyin": true,
"keep_none_chinese_in_joined_full_pinyin": true
}
}
}
}
}
}
index. 执行刷新操作的频率,这使得索引的最近更改对搜索可见。默认为1s. 可以设置-1为禁用刷新。如果未明确设置此设置,则至少index..idle.after几秒钟内未看到搜索流量的分片将不会收到后台刷新,直到它们收到搜索请求。命中空闲分片的搜索将等待下一次后台刷新(在 内1s)。此行为旨在在不执行搜索的默认情况下自动优化批量索引。为了选择退出此行为,1s应将的显式值设置为刷新间隔。(由于数据不需要那么实时更新和缓解后台压力,因此设置15s是完全够的)
定义Ik分词器以及分词器
完成以上操作,索引已经创建完成了开源智能客服系统,剩下需要创建map映射了
PUT http://xxx.xxx.xxx.xxx/support_konwledege_index/_mapping/konwledege_type?include_type_name=true
{
"properties": {
"title": {
"type": "completion",
"fields": {
"pinyin": {
"type": "completion",
"analyzer": "pinyin_analyzer",
"preserve_separators": false
},
"keyword_pinyin": ...(配置可以到网上进行查询)
"keyword_first_py": ...(配置可以到网上进行查询)
"my_pinyin":{
"type":"completion",
"analyzer": "ik_pinyin_analyzer",
"preserve_separators": false
}
}
}
}
}
保留分隔符,默认为true. 如果禁用,您可以找到一个以 开头的字段Foo 。可以通过foof 查找,一般这里设置成false即可。
完成上面的操作后,索引,map都创建好了,只需导入数据即可。
接下来对进行搜索,看代码:
GET http://xxx.xxx.xxx.xxx/support_konwledege_index/konwledege_type/_search?pretty
{
"_source": "title",
"suggest": {
"match": {
"text": "什么事",
"completion": {
"field": "title",
"size": 5,
"fuzzy": {
"fuzziness": "AUTO",
"min_length": 3,
"prefix_length": 2,
"unicode_aware": true
}
// "skip_duplicates":true
}
}
}
}
结果:
{
"took": 6,
"timed_out": false,
"_shards":...,
"hits": ...,
"suggest": {
"match": [
{
"text": "什么事",
"offset": 0,
"length": 3,
"options": [
{
"text": "什么是回源HOST",
"_index": "support_konwledege_index",
"_type": "konwledege_type",
"_id": "31",
"_score": 2.0,
"_source": {
"title": {
"input": "什么是回源HOST",
"weight": 2
}
}
},
...
]
}
]
}
}
为什么我输入“什么事”,会搜索到”什么是XXX“这种标题呢?原因是fuzzy这个字段,官网中的解析是The also fuzzy — this means you can have a typo in your and still get back. 简单的描述就是支持模糊查询,在搜索中输入拼写错误,但仍然可以返回结果。
具体参数:
, . .
if set , are as one of two,
of the input fuzzy are ,
of the input, which is not for fuzzy , to1
, all (like fuzzy edit , , and ) are in code of in bytes. This is than raw bytes, so it is set .
图上代码主要设置了模糊因子为自动,: Auto;: 3,:2,: : 这个对字符进行 转换,由于我们大多数是中文,需要 编码的,否则 和 的长度计算都不是以编码计算的。
可能你也发现了评分参数和 是一样的~,是的。我对内容进行权重的控制,实现热门问题置顶的效果。(默认搜索结果排序是按照数字大小、字母ASCII码、input索引到ES的顺序排序)
根据上面的代码来进行操作已经完成了核心的功能,我们就往细节进行优化了~
当用户输入关键词后,没有找到文章内容,相关问题应该返回热门问题,文章内容需要返回没有找到内容的话语。
当用户输入关键词后,找到文章内容,但没有找到类似的问题(类似问题的寻找规律是关键词的长度 除以 2 形成新的关键词), 相关问题需要返回热门问题
当用户输入关键词后,找到文章内容,权重需要发生变化
用户多次搜索同一个标题,标题权重只会加1次
3. 在线人工服务
当机器人没有返回用户满意的答案时,人工接入成为解决用户问题的最终手段。
想要实现较好的人工服务,必须有一个较好的架构服务。经过多方考量,得出人工服务架构如下:
人工服务使用web端即时通讯技术,实现即时通讯方法在市面上普遍有4种方法
轮询长轮询(comet)长连接
具体的优缺点,我在此就不一一分析。项目中采用的是协议进行通信的。
既然采用协议,所以需要对原有的http协议进行升级改造(这里使用//)
// 升级get请求为websocket协议
ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
return
}
client := &Client{Conn: ws, Send: make(chan []byte, 256), UserInfo: nil}
本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,请添加站长微信举报,一经查实,本站将立刻删除。
如若转载,请注明出处:http://www.ibjoo.com/10829.html