赞
踩
FAQ(FAQ,frequently-asked questions)问答系统表示常见问题问答系统,常用于一些特定领域的智能客服,将用户经常问到的高频问答对索引起来,当新的提问命中时可以快速回答,准确而高效。
本文介绍一个简单的FAQ问答系统实现。基于检索和排序的两阶段框架,检索阶段基于Elasticsearch检索引擎、排序阶段基于语义匹配深度学习模型。后端基于SpringBoot系列框架。
FAQ问答大概的对话流程示意图如下:
系统的大致框架如下图所示:
以对话为例说一下系统各个模块的协同:
用户问题
向后台发送HTTP请求;用户问题
进行ES检索,返回N(可配置)个相关的初始候选集;值得一提的是,Redis的作用是对话状态管理,即每一个用户于系统交互都会在Redis中创建一个与之对应的对话状态(dialogue status),这个对话状态可以用来区分不同用户,也可以用来进行多轮对话(保存上一步对话的节点数据)。
对话是核心功能,提供一问一答的交互式方式。
人机对话:用户提出问题,系统给出回答。
FAQ问答对持久化保存在MySQL中,管理员只需维护MySQL中的数据。但是在对话时,系统不会去访问MySQL,而是通过ES检索引擎进行检索。因此,保证MySQL和ES的数据一致非常重要。
全量同步:将MySQL中的问答对数据全部同步到ES索引中。
更新多轮问答树:多轮问答基于多轮JSON,逻辑上为树的组织结构,需要将JSON文件读取到Redis中存起来。
打开浏览器访问http://localhost:1234/faq/swagger-ui/
可以查看全部接口并进行测试。
打开ui/dialogue.html
进行界面交互。以下显示了单轮对话和多轮对话的简单示例。
(值得一提的,前端ui用的是Alibaba开源的对话框架,感觉非常实用,只需要懂点JS就可以调了。)
整个FAQ问答系统就用了一张表,faq问答对,名称为faq_pair,表结构如下:
字段名 | 字段类型 | 是否可为空 | 键 | 注释 |
---|---|---|---|---|
id | int(11) | NO | PRI | |
qa_id | int(11) | NO | UNI | 标准问-标准答的唯一标识id |
standard_question | text | NO | 标准问,表示高频问题 | |
standard_answer | text | NO | 标准答,表示高频问题对应的回答 |
该系统配置一些自定义的状态码和说明,用一个枚举类CodeMsg
表示。
这些状态码可以用于定位问题所在,也可以让前端区分不同的返回值代表的含义等等。
public enum CodeMsg { //通用状态码10000系列,模块异常 ELASTICSEARCH_EXCEPTION(10001, "elasticsearch异常"), MYSQL_EXCEPTION(10002, "mysql异常"), SIMILARITY_NULL_EXCEPTION(10003, "相似度计算模型异常"), //通用状态码20000系列,有返回值,无异常 SUCCESS(20000, "success"), SUCCESS_SINGLE(20001, "success-->单轮"), SUCCESS_MULTI(20002, "success-->多轮"), //通用状态码30000系列,中间状态 OPTIONS_NOT_HIT(30001, "处于多轮问答中,但未命中多轮问答的选项,此时将重新检索用户问题"), //通用状态码40000系列,无返回值 FAILED(40000, "failed"), UNRECOGNIZED_QUESTION(40001, "failed-->无法识别的问题"), MULTI_ROUND_QA_NOT_FOUND(40002, "failed-->没有找到对应的多轮问答树"), MULTI_ROUND_QA_NULL(40003, "failed-->redis中多轮问答树为空"), MULTI_ROUND_QA_CHILD_NODE_NULL(40004, "failed-->多轮问答树子节点为空"); }
项目定义了用户配置文件application-user.yml
,通过在SpringBoot默认配置文件application.yml
中配置以下参数引入该配置文件
spring:
#引入自定义配置,application-user.yml
profiles:
include:
- user
application-user.yml
中自定义了一些参数,可以根据需要随时修改而不用改源码,如对话相关的参数:
#对话配置 dialogue: #置信度排序 confidence-rank: #返回的置信度最高的doc的个数 size: 5 #置信度计算权重 weights: #相关度权重 relevance-weight: 0.3 #相似度权重 similarity-weight: 0.7 #用户对话状态 status: #过期时间(单位: minute) expire-time: 2 #多轮问答树 multi-turn-qa: path: data/multi_turn_qa #redis热点数据缓存 hot-data: #是否开启 open: true #过期时间(单位: minute) expire-time: 5
对话相关参数的配置类如下:
@Configuration @ConfigurationProperties(prefix = "dialogue") @Data public class DialogueConfig { private ConfidenceRank confidenceRank; private Status status; private MultiRoundQa multiTurnQa; private HotData hotData; //redis中多轮问答树的key前缀 private final String MQATreeKeyPrefix = "MQATreeNode_"; //redis中question映射id的key private final String MQAQuestion2idKey = "MQA_question2id"; //redis中用户对话状态的key前缀 private final String DialogueStatusKeyPrefix = "dialogue_status_userId_"; //redis中热点数据的question映射id的key private final String HotDataQuestion2idKey = "hot_data_question2id"; //redis中热点数据的key前缀 private final String HotDataKeyPrefix = "hot_data_"; @Data public static class ConfidenceRank { private Integer size; private Weights weights; private Float threshold; @Data public static class Weights { private Float relevanceWeight; private Float similarityWeight; } } @Data public static class Status { private Integer expireTime; } @Data public static class MultiRoundQa { private String path; } @Data public static class HotData { private Boolean open; private Integer expireTime; } }
用的也是推荐的依赖包,引入的pom依赖如下:
<!-- 更推荐的读取配置文件的处理器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
对话流程表示从用户问题输入,到找到答案输出的流程,流程图如下。
多轮对话按规则执行,逻辑上组织为一棵树,示意图如下:
一颗多轮对话树在物理上为一个json文件,在更新多轮对话树时json文件将被转换成数据对象MultiQaTreeNode
,然后添加到redis中。
{ "qaId": 3, "question": "推荐一个景点", "answer": "好的,请问对景点评分有要求吗", "childNodes": [ { "question": "没要求", "answer": "好的,请问景点票价可接受范围?", "childNodes": [ { "question": "免费", "answer": "附近好多公园呢,比如xxx,今天天气不错,可以去转转。", "childNodes": [] }, { "question": "50元以内", "answer": "这个xx不错,自然风光秀丽,离您也不远。", "childNodes": [] }, { "question": "无所谓,不差钱", "answer": "推荐xxx景点给您呢,该景点绝对符合您的气质", "childNodes": [] } ] }, { "question": "3分以上", "answer": "可玩的就比较多了,有xx...", "childNodes": [] }, { "question": "5分", "answer": "在xx那有一处5A景区,评分有5分呢,推荐您去玩哈。", "childNodes": [] } ] }
MultiQaTreeNode
类如下:
public class MultiQaTreeNode implements Serializable {
//对应的qaId,一棵多轮问答树不同层节点的qaId是相同的,都为根节点question所对应的qaId
private Integer qaId;
//当前节点的问题
private String question;
//当前节点的回答
private String answer;
//当前节点的子节点
private List<MultiQaTreeNode> childNodes;
}
系统是简化版的,基本只保留了人机对话功能,问答对也只用了一张表,实际上对于一个高频问题,可以多生成一些与之相似的问题用于扩大搜索范围。另外,如果需要增加问答对,需要对MySQL数据表增加行数据,然后使用同步功能在ES建对应索引即可。
该项目源码如下:
GitHub
Gitee
另外,为了学习微服务相关技术栈,对单体架构进行了升级,而且增加了问答对管理的一些功能。
微服务版地址为:
Gitee
GitHub
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。