ChatBot framework 开发实践

前言

通常而言,通用聊天机器人(比如小冰等)底层技术是采用类似Seq2Seq等“生成”技术的。但是这种机器人属于探索性质,无法
提供特定的服务。而Siri则是兼具闲聊以及垂直领域功能的,比如可以预约提醒,打电话,定餐厅等特定功能。相信Siri在实现特定预约提醒,打电话功能等,则是使用了“语言模板”匹配技术以及结合分类器来实现精度的控制和定向动作的执行。

对于聊天机器人我个人是相当感兴趣的,奈何现在的已经公开的文章都“相对初级和入门”,或者说过于专注里面的某个算法,比如问答匹配算法。所以萌生了写一篇文章的想法。本文基于自己开发相应系统的经验,理论上会给大家带来一些帮助。但是因为是内部系统,只能谈及一些较为公开的思想。

现在我们的目标是探讨是如何设计和实现一个,只要通过简单配置就完成一个特定主题对话的机器人。有需要的话,可以经过插件(组件)开发,为其增加新垂直领域对话功能。这些插件,就如我前面所言,可能需要集成大量的针对特定领域问题的算法。

核心技术要点

  1. 语言模板引擎
  2. 对话配置系统
  3. 机器学习相关技术

语言模板可以保证意图识别的准确性,机器学习除了能否增加覆盖度以外,同时也是对话配置系统的核心所在。在今天这篇文章里我们会重点探讨1,2两点,3有需要的时候会提及。

语言模板引擎

语言模板很好理解,就是一句话匹配上了某个模板,这个模板会指向一个动作,从而能够给出一个响应。一个大致的配置如下:

{
      "action": "how to deal with ",
      "template": [
       "<疾病>*怎么办",
       "<疾病>*怎么*治疗"
      ],
      "reg": "",
      "id": "24"
    }

比如 糖尿病怎么办,就会匹配上这个模板,并且执行action动作。如果怕模板定制的过于宽泛,可以再通过reg (正则)来进行限制。id 则是这个模板的唯一编号。 * 表示中间可以匹配任何字符。<疾病> 则表示特定实体,这需要有海量的实体词典支持。

通常语言匹配模板引擎会有比较明显的性能问题。当你有几千个上万个模板,每个模板里面又有几十个甚至上百个子模板,那么一次匹配的成本会相当高,对CPU压力也会非常大。所以通常我们会采用倒排索引技术,比如*怎么*治疗 会进行如下编码:

怎么 -> [24,100...... ]
治疗 -> [24,36...... ]

比如糖尿病应该怎么办治疗,我们会提取出”怎么“,”治疗“,两个词汇,然后获得他们的倒排列表,求交集,就能得到24,并且再做一次实体检查,就能实现快速查找了。当然具体如何做倒排列表,如何做抽词,包括做实体识别,实体积累我们在本文不做详解。

语言模板引擎是聊天机器人里较为核心的组件,通常算法在这种场景里是补充。

对话配置系统

对话配置系统,其实就是chatbot framework, 据说有一些开源实现,不过我没具体了解过。我这里说说我的设计。

通常对于一次性对话(一问一答)这个比较好处理,依托于上面的语言模板引擎基本就能实现了。对于有一个”对话引导流程“的会话,这种多伦对话则需要一个较为完善的对话配置系统。

对话配置系统会涉及到几个概念:

  1. 会话主题。每次对话应该都是围绕一个主题的,比如帮助用户完成转账流程,这期间要和用户发生多次交互,直到最后帮用户搞定。
  2. 跳转。 根据用户的反馈,又分为会话内跳转,和会话间跳转。因为一个会话会有多次交互,所以会有会话内跳转。会话间跳转,可以通过一个简单的例子来解释:比如用户问附近哪家餐厅比较好,你可能会询问用户是要西餐还是中餐,这个时候用户不搭理你了,说给我安排一个日程吧,这个时候时候就需要主题间的跳转。主题通常依托在特定会话中。
  3. 对话树。一个对话对话是一个树状结构。同时我们又会有多个对话,对话之间不一定是互通的,最终有个会话森林的概念。
  4. 对话树节点。前面我们提及,一个会话会有多伦交互,所以为了完成一个会话, 配置上至少有两种类型的节点:一个是条件节点,一个回答节点。

上面都是一些要点,我这里会举一个最简单的配置例子:

{
  "对话名称": {
    "id": 1,
    "intercept": [
      {
        "name": "......ConversationChangeInterceptor",
        "params": {
          "match": "template:6,8,9,10",
          "target_step": {
            "match_one": "3:1",
            "no_match": "2:0"
          }
        }
      }
    ],
    "conversation": [
      {
        "chain_type": "condition",
        "match": [
          "template:6,7,8"
        ],
        "desc": "",
        "msg": "",
        "step": 0,
        "target_step": {
          "question_finish": 11,
          "no_match": 10
        }
      },
     {
        "chain_type": "conversation",
        "match": [
        ],
        "desc": "",
        "format_class": {
          "name": ".....ClassifyFormatter",
          "params": {
            "url": "..../prediction"
          }
        },
        "msg": "您好 ${name}先生",
        "step": 6,
        "target_step": -1
      }

intercept表示会话拦截,在对话流程里任何一个环节,都需要检查下是不是发现了主题变更,如果符合,则会根据target_step实现对应的跳转。在interceptor的target_step 被表示为 A:B 这种形式,意思是跳转到A对话里的B节点上。

match 表示匹配了哪些模板,当然,也可以是一个算法模型,比如"model:com.org.QuestionClassify",比如我需要判定用户是不是在描述自己的身体状况,这个时候用模板显然是不行的,可能需要继承一个算法分类器。显然上面的配置是支持这种集成的。

如果匹配上了则跳转到对应的step 11,如果没有匹配则跳转到step 10。根据类型(chain_type),这是一个条件节点,所以他不会对用户做任何输出,而是默默的根据条件往其他节点条。

msg 表示应答的语句,如果你想动态调整这个输出,可以配置format_class。format_class主要实现复杂动态的应答逻辑。另外还有一个类似配置是query_class,会拦截进来用户的问题,并且改写用户的问题。

step 表示当前处于会话的那个节点,这个节点处理完后的下一个节点会是target_step。 通过适当形态,可以实现会话之间的跳转。我举的例子只是展示了内部跳转。

有了这套配置引擎后,比如做一个客服机器人,就变得很简单了,把用户的常见问题罗列下,之后执行特定的动作。当然,很多传统的客服机器人为了简单期间,主要是通过依托于QA算法做匹配,并不会采用这种我说的方案。

我们根据这套配置引擎,可以实现一个很复杂的对话。而且可以配置很多有趣的功能,比如 功能导航对话,比如吃饭请按1,睡觉轻按2 这种。只要配置一个新对话即可,然后这个对话作为作为起始对话,通过会话间跳转来完成导航功能。

因为千人千面,所以在实际的引擎实现过程中,我们需要记录每个用户当前所处的会话以及所在的节点,还包括会话期间的一些信息搜集,这也是一个较为复杂的话题了。我们底层采用redis做这个存储。

总结

一般而言,我们无法使用“某个”算法就实现一个复杂的系统,当然,“某个”算法可能很重要,甚至是系统能否成功的关键。一个复杂的系统通常都是根据每个环节的需求不同,综合利用了方方面面的算法,而每个算法使用的数据又是其他算法处理而来的。ChatBot framework 本身能够通过配置,复用一些已有的组件完成一些基础的对话功能,但是如果要实现更复杂的对话,则需要更多算法和组件的支持。

你可能感兴趣的:(ChatBot framework 开发实践)