前言

@张俊同学有天晚上邀我写一期文章,介绍论坛机器人的技术实现。登时诚惶诚恐,群里大神云集,我此前写的这个粗陋的机器人实在难登大雅之堂。考虑到机器人还在群里头用,就献丑来简单介绍下技术层面的实现,方便大家日后改进它 :)

本文侧重谈论整体的设计和实现,至于细节,大家可以看源码和文档(文档在doc目录下) :)

功能

论坛机器人目前实现了三个功能:

  1. 微信群与paperweekly论坛的双向通信(发帖/回复)
  2. paperweekly1群和2群的消息转发(当前只支持文本和表情)
    • 更多功能(图片/链接/跨群@)在todo list中
  3. 问题检索
    • 支持中文提问

以上三个功能按实现的先后排序。下边分别简述这三个功能的实现

微信群与paperweekly论坛的双向通信

先贴上实际使用的截图:

这个功能是最初的需求,@张俊同学觉得群聊消息归档不便,不利于深入讨论问题。为了更加高效的分类和管理大家讨论的精华(QA对),想做个bot,与外部论坛打通,便于归档优质的讨论内容。大家群聊的时候只要按照特定格式发言,机器人就能将信息转发到论坛中,用论坛来保存这些讨论信息

关于最初我们试图解决的问题,@张俊同学在PaperWeekly十期总结的Issue 2里写的很详细,对项目背景有兴趣的同学可以参考

最初的需求描述如下(来自@张俊):

我觉得这个想法很有趣,决定一试。用了一个晚上实现了微信与论坛的通信机制(大半时间在熟悉Misago)。次日早起,调试完成,后来也没空去重构代码,这部分代码很丑陋(quick and dirty),欢迎有兴趣的小伙伴帮忙重构

思路与实现

微信群与paperweekly论坛双向通信问题,实际上是个消息转发问题。消息无非两种流向:

  • 来自群聊的消息发往论坛
  • 来自论坛的讨论发往微信群

消息的流向可以从这张图里看出:(这张图也描述了项目的结构和数据流)

我们先来看看从微信群发往论坛的消息。

当微信群的讨论(成员的发言)匹配预先设定的模式时,将触发机器人特定行为,诸如往论坛发帖或回答问题。(这实际是CLI风格,如果你熟悉*nix,会觉得很亲切),所以机器人并没有处理自然语言,更多的是正则匹配,哈哈这有点小尴尬,如@张俊所言

我们整天都在用机器学习,也都想通过人工智能来改变这个世界,来改变我们的生活,很多时候模型和工具都有,但缺少数据和需求,这次有了数据和需求,我们却无能为力了,感觉有一点点小讽刺

机器人士如何把消息发往论坛的呢。我hack了Misago(论坛所用的框架),使其对外提供RESTful API,允许使用http请求来做常见操作(发帖/回复之类)

以发帖为例,我们跟着消息走,看到的是这样一个流程:微信群里某些消息匹配固定模式,机器人被激活,进而携带着token往论坛发送特定http请求,消息作为请求参数。于是消息从微信群流到了论坛

接着我们来看看论坛发往微信群的消息。在论坛这块,我hack了源码里的发帖函数,使其具有webhook output功能,你很可能已经用过github的webhook output。当论坛产生帖子时,将对外发起一个web请求(output),外部系统收到请求便知道事件的发生,同时也能获取事件的细节(请求参数),如此一来这个钩子(hook)就把两个异构系统勾连起来。

webhook是我偏好的异构系统间通信的解决方案,灵活性高,耦合度低。

我将论坛的web请求发往一个消息服务(我选择kinto,至于原因我在开发文档里也有描述),而在机器人这边,会不断向消息服务轮询。这实际上是经典的发布/订阅模型(至于为何不采用redis和rabbitmq的原因我在开发文档里也有说明)。

使用发布订阅模型考虑的主要是扩展性,我手头有nlp相关的 QQ群,当时觉得paperweekly的讨论内容很棒,也许其他nlp小组也会感兴趣,使用发布订阅模型,我只要在任何group里(QQ/wechat/telegram/whatapp)放上机器人,就可以订阅paperweekly的动态,我这边做了两个具体实现,其一是在微信群中订阅消息,源码见paperweeklybot.py,其二是QQ群中订阅消息:源码见SmartQQBot

至于论坛的选型(最终选择了Misago),大家在群里讨论的结果倾向选择基于python的论坛框架。因为大家对python更为熟悉,方便维护

群消息转发

关于这个需求的背景,引述@张俊的说明(PaperWeekly十期总结):

一个群很快就到了500人,出现了一个棘手的问题,第二个群的人如果太少,几乎没有讨论意义,所以就想用什么办法可以打通两个群,让两个群的童鞋在同一时空内进行交流

@碱馒头同学用了一个中午时间写了群消息转发机器人,我觉得很妙,功能干净利落、十分实用。后来我们觉得两个机器人可以合并成一个,本来计划由馒头兄来实现这块,后来因为我的微信机器人框架和馒头兄不同(我偏好itchat),我就自己用itchat实现了这块

这部分的实现比较简单,机器人同时在两个群里,当机器人接收到A群消息时,立马转发到B群。至于机器人如何得到群消息,就是微信机器人框架(itchat)做的事了,大家可以自行读文档

目前只转发文本和表情,下一步准备支持图片/链接分享和跨群@

问题检索

国庆去白洋淀玩,路上,@张俊同学提到一个有趣的想法:

有人提问之后,能否直接从quora或者知乎检索相应的答案

我想到之前玩过的一个叫做howdoi的项目,于是以此为原型给bot添加了问题检索的功能,源码见qa_bot.py

目前用户可以在微信群向机器人提问,它会返回stackoverflow里最佳的答案

原理很简单,howdoi的源码很短,大体思路是利用google的site特性,在指定的站点搜索问题(默认是stackoverflow),得到相关连接,然后打开连接,用pyquery提取内容,返回干净的答案给用户

从howdoi的原理我们可以看出:实际上问题搜索是利用了google强大的搜索能力,同时我们看到,这是一个易于扩展的框架,只要改变site对应的网址,理论上可以检索任何网址。最初的需求里提到的知乎和quora当然没有问题,剩下的工作只剩对答案做html清洗。惭愧的是我近来挖的坑太多,业余时间被分散在各个坑里,这个坑暂时还没填,等待小伙伴加入

因为howdoi不支持中文(用中文从stackoverflow里基本搜不出有价值的东西),后来我们添加了一个翻译层,先做翻译,然后再检索,这样一来,就可以直接在微信群里用中文向机器人提问

于是你就看到了上边一张图片里的问答

下一步

接下来除了填完上文提到的坑(todolist)

还准备先把群消息存下,诸如(groupname,username,content,time),然后pandas做些简单统计,可做的事包括:

  • 添加一些小彩蛋,诸如@机器人可以返回该用户的历史参与情况
  • 统计得出年度热心群友(举个栗子:),莫当真)

进一步,可以用bokeh作可视化展示

当然也可以把语料开放给群成员,供大家使用nlp来做一步的分析和挖掘,诸如挖出

  • 近期焦点问题
  • 冷场话题排行版

或者用来训练一只paperweekly风格的机器人 :)