AIML ,全名为Artificial Intelligence Markup Language(人工智能标记语言),是一种创建自然语言软件代理的XML语言,是由Richard Wallace和世界各地的自由软件社区在1995年至2002年发明的。
它的雏形是一个名为”A.L.I.C.E.” (“Artificial Linguistic Internet Computer Entity”)的高度扩展的Eliza机器人。ALICE总共赢得3次每年度的Loebner奖,并且在2004年获得了Chatterbox Challenge的冠军。由于A.L.I.C.E. 的AIML设置是在GNU GPL协议下发布的,所以已经有许多基于该程序和AIML库的“克隆ALICE”出现。目前AIML已经有了Java,Ruby,Python, C ,C#,Pascal等语言的版本。
Tornado 是 FriendFeed 使用的可扩展的非阻塞式 web 服务器及其相关工具的开源版本。这个 Web 框架看起来有些像 web.py 或者 Google 的 webapp,不过为了能有效利用非阻塞式服务器环境,这个 Web 框架还包含了一些相关的有用工具 和优化。
Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。得利于其 非阻塞的方式和对 epoll 的运用,Tornado 每秒可以处理数以千计的连接,这意味着对于实时 Web 服务来说,Tornado 是一个理想的 Web 框架。我们开发这个 Web 服务器的主要目的就是为了处理 FriendFeed 的实时功能 ——在 FriendFeed 的应用里每一个活动用户都会保持着一个服务器连接。
微信公众平台 是运营者通过公众号为微信用户提供资讯和服务的平台,而公众平台开发接口则是提供服务的基础,开发者在公众平台网站中创建公众号、获取接口权限后,可以通过阅读本接口文档来帮助开发。
Python可以很方便地利用Tornado框架以及AIML搭建一个聊天机器人微信公众号,本文简单介绍下如何用Python编写简单的聊天机器人。不过由于目前AIML上缺少高质量的中文语料库而不支持中文聊天。因此本文搭建的为英文聊天机器人。
此搭建方式需要一台具有固定公网ip地址的主机一台。也可以利用现有的云主机或者云平台,例如 sina app engine 。
此微信订阅号的完整源代码可以通过此链接下载:聊天机器人订阅号源代码。
搭建的微信订阅号可以直接回复用户发送的英文消息与用户聊天。
我自己搭建了一个订阅号CuriousGuys,可以添加此订阅号后直接发送英文消息。
要添加CuriousGuys可以直接扫码:
需要用到Tornado以及aiml库。
Linux下安装Tornado可以直接用以下脚本:
pip install tornado
其它操作系统下安装Tornado可以参考Tornado官方网站。
Linux下安装aiml也可以直接pip安装:
pip install aiml
其它操作系统下的安装请参考AIML官方网站。
Python aiml安装完成后在Python安装目录下的 Lib/site-packages/aiml下会有alice子目录,将此目录复制到工作区。
或者在Google code上下载alice brain: aiml-en-us-foundation-alice.v1-9.zip
要搭建订阅号,需要在微信官网进行注册,注册网址:微信公众平台。
目前个人用户可以免费申请微信订阅号,虽然很多权限申请不到,但是基本的消息回复是没有问题的。
具体的接入步骤可以参考官网上的接入指南。
配置里的URL为服务器提供订阅号后台的url路径,本文用到的源代码配置的是 http://server_ip/wx 其中 server_ip 是运行源代码的主机的公网ip地址。这个可以通过修改源代码里的config.py来配置。
Token 可以设置为任意字符串,不过要将源代码config.py里的settings[‘wx_token’]改为设置的字符串。
EncodingAESKey 可以选择随机生成。
消息加密方式可以设置为比较简单的明文模式。
接受并处理微信服务器发送的接入请求的关键代码为Tornado的一个Handle(在源代码里的handle/wx.py中):
class WX(tornado.web.RequestHandler):
def get(self):
signature = self.get_argument('signature', 'default')
timestamp = self.get_argument('timestamp', 'default')
nonce = self.get_argument('nonce', 'default')
echostr = self.get_argument('echostr', 'default')
if config.settings['wx_test'] or (signature != 'default' and timestamp != 'default' and nonce != 'default' and echostr != 'default' and check_wx_request(signature, timestamp, nonce)):
self.write(echostr)
else:
self.write('Not Open')
此代码的作用就是验证消息是来自微信官方服务器后直接返回echostr。
配置好程序源代码后运行,确认运行无误后再点击 提交 ,如果程序运行没问题,会显示接入成功。
关键部分为利用aiml回复用户发送的消息。
aiml载入语料库的代码为(在config.py中):
import aiml
cur_dir = os.getcwd()
print 'cur_dir:', cur_dir
os.chdir('./res/alice')
alice = aiml.Kernel()
alice.learn("startup.xml")
alice.respond('LOAD ALICE')
os.chdir(cur_dir)
print 'cur_dir:', os.getcwd()
服务器回复用户消息的关键代码为(在handle/wx.py中):
# -*- coding: utf-8 -*-
import tornado.escape
import tornado.web
from BaseHTTPServer import HTTPServer
import base64
import sys
import json
import time
import config
import time
import hashlib
import urllib
import os
import random
from util.xml2json import *
# 处理xml格式的用户消息
def wx_proc_msg(msg_body):
try:
jmsg = xml2json(msg_body)
msgjson = json.loads(jmsg)
msg = msgjson['xml']
MsgType = msg['MsgType']
main_content = {}
main_content['MsgType'] = msg['MsgType']
main_content['CreateTime'] = msg['CreateTime']
main_content['ToUserName'] = msg['FromUserName']
main_content['FromUserName'] = msg['ToUserName']
if MsgType == 'text':
req = msg['Content']
respond = config.alice.respond(req)
if respond == None or len(respond) < 1:
respond = '''Sorry I can't understand you'''
main_content['Content'] = respond
result = {}
result['xml'] = main_content.copy()
result = json2xml(result)
return result
elif MsgType == 'image':
main_content['MsgType'] = 'text'
main_content['Content'] = '''Sorry I can't read picture.'''
result = {}
result['xml'] = main_content
return json2xml(result)
elif MsgType == 'voice':
pass
elif MsgType == 'video':
pass
elif MsgType == 'shortvideo':
pass
elif MsgType == 'location':
pass
elif MsgType == 'link':
pass
else:
pass
except Exception, e:
print 'Error when process this message:', msg_body
print e
return ''
# 验证消息是否来自微信官方服务器
def check_wx_request(signature, timestamp, nonce):
token = config.settings['wx_token']
arr = [token, timestamp, nonce]
arr.sort()
sh = hashlib.sha1(arr[0] + arr[1] + arr[2]).hexdigest()
if sh == signature:
return True
else:
return False
# Tornado的Handle,用于接收并处理用户消息
class WX(tornado.web.RequestHandler):
def post(self):
signature = self.get_argument('signature', 'default')
timestamp = self.get_argument('timestamp', 'default')
nonce = self.get_argument('nonce', 'default')
if config.settings['wx_test'] or (signature != 'default' and timestamp != 'default' and nonce != 'default' and check_wx_request(signature, timestamp, nonce)):
body = self.request.body
try:
self.write(wx_proc_msg(body))
except IOError, e:
return