上一篇已经说明了自然语言处理的简单实现方法,在此基础上,如何实现一个机器人与我们交流,是这篇文章需要讨论的问题。希望这个机器人不算太笨。^_^
微信的交互方式是触发式的,由用户发起会话,一问一答。所有的设计都需要基于这个模式。
会话管理机制
想想平时交流时的行为,我们会根据对方的每句话来逐渐建立起一个语境,即对话上下文。根据不同的语境,即使是同一个问题,也会有不同的答案。
基于这个想法,设计一个简单的会话管理机制,即每个用户有一个独立的对话上下文context,每个context会有一个状态来标志当前的语境。
1、会话状态管理
- 为使每个会话拥有独立的语境,每个上下文拥有自己的状态。
- 根据用户不同的请求,将会话切换为不同的状态。
- 根据当前的状态,机器人选择不同的回答方式或答案。
- 当前的实现比较简单,只有如下五种状态
public enumContextStateEnum {
WAIT_QUERY, //等待询问
WAIT_LEARN, //等待学习
WAIT_CONFIRM, //等待确认
WAIT_WEATHER_CITY, //等待指定需要查询天气的城市
WAIT_MUSIC_INFO; //等待指定需要搜索的歌曲信息
}
2、创建与删除会话
- 当用户发起会话时,创建一个属于该用户的上下文。
- 考虑到系统负载,每个会话拥有超时时间,并且设定一个最大会话数。
- 当会话数达到上限时,删除超时的会话。如果没有超时的会话,则不接受新的会话请求。
3、会话处理流程
有了这个基本思路,再来看看每个会话的处理流程。
我将用户的问题分为两类,非功能性问题和功能性问题:
- 非功能性:这类问题的答案由用户调教获得,存储在知识库中,查询本地数据库即可回答,用户可通过调教修改问题答案。
- 功能性:这类问题的答案通过访问外部API获得,比如查询天气,搜索音乐。Context状态中,WAIT_WEATHER_CITY, WAIT_MUSIC_INFO两个状态属于功能性问题的状态。
Context的状态切换如下图:
- 会话创建时,状态初始化为WAIT_QUERY。
- 用户发起请求后,如果处于WAIT_QUERY状态则首先要判断是否是功能性问题(通过匹配关键字来判断)。
- FUNC_WAIT代表功能性问题的状态,如WAIT_WEATHER_CITY, WAIT_MUSIC_INFO。
- 对于非功能性问题,用户通过调教可以修改答案,其流程如下图。
Context模块与其他模块的关系
- Context模块提供会话接口给微信适配层调用,用于处理用户请求。
- 处理非功能性问题时就需要调用自然语言处理模块的接口。
- 处理功能性问题时就调用外部API适配层提供的接口。
- 还有一些会话信息需要保存到数据库,调用DAO模块的接口。
一些扩展的设想
当前的实现比较简单,如果继续扩展,可以加入一个解析模块。解析模块负责分析会话中的聊天记录,context模块根据分析结果切换会话状态,比如机器人可以有开心、愤怒、悲伤等状态,在不同的状态下,即使是同一个问题机器人也可以选择不同的回复。当然,这需要把知识库中的答案进行归类,机器人才能根据状态选择对应的答案。
实现过程中碰到的问题
问题:分布式服务器导致机器人状态机混乱
最初设计context保存在内存中,定义一个map容器,以用户ID作为key,context结构体作为value。
本地运行没有问题,但是发布到BAE服务器后,问题来了,通过微信与奇迹蛋robot对话,发现奇迹蛋的context状态机混乱,行为非预期。并且问题发生具有一定的随机性。
开始一直以为是多线程操作map引起的问题,在程序中又加了单例,满怀期待的发布了,结果还是不行。
折腾了挺久,最后在网上找到了一个比较靠谱的解释,参考:http://blog.csdn.net/ostrichmyself/article/details/8098119,“3.1 静态变量无法常驻内存”。
简单来说,由于BAE是分布式服务器,我们部署在上面的web应用会被放在多个容器里执行,而这些容器的内存是非共享的,如下图:
- 用户发起会话A,机器人程序在web容器A中被执行,此时判断为一个新用户访问,申请内存创建MapA,保存本次会话信息。
- 同一个用户,继续发起会话B,此时分布式服务器把机器人程序放在web容器B中执行,该容器中并没有MapA,该用户再次被作为一个新用户处理,创建MapB。
- 这样用户会话的状态不能有效的保持,导致状态机混乱。
弄清楚了原因就好办了,有两种解决方法:
1. 使用BAE的cache服务,创建共享内存
研究了一下,要调用BAE的API,本地调试不方便,加上BAE的文档看的蛋疼,放弃了。
2. 使用数据库来保存
放弃使用内存的方式,用mysql来保存context map。尽管增加了数据库操作,不过用户也没几个,无所谓了。
奇迹蛋的主要设计思路基本就介绍完了,整个系统比较简单,有些功能设计了但是没有去实现。主要目的还是学习一下java web的开发方法,也顺便了解了解时下最火的微信APP。
Have fun~
下篇文章会聊一些代码实现中碰到的问题,并符上源码。
谢谢关注奇迹蛋~扫一下&调戏之