Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
275 lines (234 sloc) 8.29 KB
#!/usr/bin/env python
from AIMLBayes import AIMLBayes
import re
import aiml
import time
import string
from string import upper
class AIMLBot:
"""
An AIML chat bot that attempts to use a Bayesian guesser to
reduce the amount of AIML that needs to be written, allowing
us to just write very generic AIML and train the filter accordingly.
Ideally the bot should ask for help whenever it cannot find a reponse.
AIMLBot also intends to be highly extensible by allowing for callbacks
to be inserted in the AIML using 'handler' predicates. You can extend
this class and supply your own AIML and callbacks which will execute
once the bot is fully trained.
Duncan Gough 13/03/04
- Updated to switch from AIM to IRC, switching TocTalk for Twisted IRC.
- Renamed on_IM_IN to on_MSG_IN since we don't handle IMs anymore
- Removed all the AIM Buddy code
- Also updated to fix a bug with the training mode whereby topics with
an underscore were not being learnt. Since we used TRAINING_NICKNAME
to teach the bot, learning was effectively disabled. AIMLBot now creates
a training file with the topic of TRAININGNICKNAME which means that all
the other pieces join up.
Duncan Gough 11/01/09
"""
def __init__(self,name):
self.blist = {}
self.response = ''
self.typerate = 0.05
self._dir = dir(self)
# Initialize the AIML interpreter
self.kernel = aiml.Kernel()
self.kernel.verbose(1)
self.kernel.setPredicate("secure", "yes") # secure the global session
self.kernel.bootstrap(learnFiles="data/aiml/startup.xml", commands="bootstrap")
self.kernel.setPredicate("secure", "no") # and unsecure it.
self.kernel.setBotPredicate("name",name)
# Initialise the Bayes parser
self.bayes = AIMLBayes(name)
def do_RESPONSE(self,sn,reply):
"""
Sends the AIML response back to the other user.
Typing timeout code written by Michael Wakerly
"""
for sentence in reply.split("\n"):
# Pretend we are typing with an artificial timeout
slept = 0
for char in sentence:
time.sleep(self.typerate)
slept += self.typerate
if slept >= 5.0:
break
self.response = sentence
def fetch_aiml_response(self,line,sn):
"""
Returns the response from the AIML parser
"""
try:
return self.kernel.respond(line,sn)
except:
return None
def guess(self,line):
"""
Guess the topic of conversation using a Bayesian filter
"""
try:
topic = self.bayes.guess(line)[0][0]
print "[Guess] %s" % topic
return topic
except:
return None
def on_BAYES(self,sn,line,topic=""):
"""
Guess the topic and then try to find a reponse.
If that fails, ask for help.
"""
if topic:
try:
print "[Topic] %s" % topic
self.kernel.setPredicate("topic",topic,sn)
reply = self.fetch_aiml_response(line,sn)
if reply:
self.do_RESPONSE(sn,reply)
else:
self.on_UNKNOWN(sn,line)
except:
self.on_UNKNOWN(sn,line)
else:
try:
topic = self.bayes.guess(line)[0][0]
print "[Topic] %s" % topic
self.kernel.setPredicate("topic",topic,sn)
reply = self.fetch_aiml_response(line,sn)
if reply:
self.do_RESPONSE(sn,reply)
else:
self.on_UNKNOWN(sn,line)
except:
self.on_UNKNOWN(sn,line)
def on_FORGET(self,sn,line):
"""
Purposefully forget information stored in the Bayesian filter
"""
tmp = self.kernel.getPredicate("meaning",sn)
meaning,topic = tmp.split("means")
self.bayes.untrain(topic.strip(),meaning.strip())
# We don't want this topic to persist
self.kernel.setPredicate("topic","",sn)
def on_MSG_IN(self,sn,data):
"""
Main method called in reponse to an incoming message
"""
line,sn = self.parse_msg(data,sn)
guess = self.guess(line)
topic = self.kernel.getPredicate("topic",sn)
handler = self.kernel.getPredicate("handler",sn)
# Order of relevance:
# 1. Handler set by AIML (mainly used for training)
# 2. Bayesian guess (whether the topic agrees or not)
# 3. Persistent topic
# 4. Ask for help
if ("on_%s" % handler ) in self._dir:
# Handle further implementations (borrowed from PyTOC in the old GrokItBot code)
print "[Handler] %s" % handler
exec ( "self.on_%s(sn,line)" % handler )
elif guess == topic:
# Persistant topic, which Bayes agrees with
self.on_TOPIC(sn,line)
elif guess:
# Lets try a guess
self.on_BAYES(sn,line,guess)
# We can support persistant topics here but that requires much more
# training of the bot in the long run, as the topic of conversation
# will only change once the bot is able to guess at a change of topic
# from keywords in your input. Since this can take some time and doesn't
# show off the Bayes guessing ability so well, I've disabled that.
# If you want to try this out, just uncomment the following else statement.
# You'll find the bot will stay on topic and that you'll need to make use of
# the 'learn x' statement to train the bot accordingly.
#
#elif topic:
# # Stay on topic
# self.on_TOPIC(sn,line)
else:
# Last resort.. another guess
self.on_BAYES(sn,line)
# Finally, check for and carry out any callbacks set in
# the AIML (barring training, which requires one more input)
handler = self.kernel.getPredicate("handler",sn)
if ("on_%s" % handler ) in self._dir:
# Handle further implementations (just as PyTOC does)
if handler != "TRAINING":
print "[Callback] %s" % handler
exec ( "self.on_%s(sn,line)" % handler )
self.kernel.setPredicate("handler","",sn)
return self.response
def on_NEVERMIND(self,sn,line):
"""
Just a get out clause that resets topics and handlers
"""
self.kernel.setPredicate("topic","",sn)
self.kernel.setPredicate("handler","",sn)
self.do_RESPONSE(sn,"OK, forget it")
def on_RELOAD(self,sn,line):
"""
Causes the bot to reload its' AIML files
"""
reply = self.fetch_aiml_response(line,sn)
self.do_RESPONSE(sn,reply)
def on_SAVEBRAIN(self,sn,topic):
"""
Save the bayes file to disk
"""
self.bayes.save()
self.do_RESPONSE(sn,"OK, saved it")
def on_TOPIC(self,sn,line):
"""
A topic is already set, try and find a reponse for it.
If that fails, let the bayes filter try its' luck
"""
reply = self.fetch_aiml_response(line,sn)
if reply:
self.do_RESPONSE(sn,reply)
else:
self.on_BAYES(sn,line)
def on_TRAINING(self,sn,line):
"""
Use the AIML response as a keyword, the users response as
an explanation and train the bayesian filter accordingly
"""
self.kernel.setPredicate("topic","training" + sn,sn)
topic = self.fetch_aiml_response(line,sn)
meaning = self.kernel.getPredicate("meaning",sn)
if topic == "NEVERMIND":
self.on_NEVERMIND(sn,line)
else:
try:
self.bayes.train(topic,meaning)
self.do_RESPONSE(sn,"OK, I grok that")
except:
self.do_RESPONSE(sn,"Sorry, that didn't work")
# Reset the various training flags
self.kernel.setPredicate("topic","",sn)
self.kernel.setPredicate("handler","",sn)
def on_UNKNOWN(self,sn,line):
"""
Ask the user for help. The AIML parser will generate a dynamic AIML
file which it can learn, in anticipation of a meaningful reply
"""
self.kernel.setPredicate("topic","unknown",sn)
reply = self.fetch_aiml_response(line,sn)
self.do_RESPONSE(sn,reply)
self.kernel.setPredicate("topic","",sn)
def parse_msg(self,msg,sn):
"""
Munge the input and hack any URLs so that they aren't split into
multiple sentences by the AIML parser
"""
self.kernel.setPredicate("player1",sn,sn)
line = self.strip_tags(msg)
line = line.strip()
m = re.match('^(.*?)(http://)?((?:[a-z0-9-]+\.)?[a-z0-9-]+\.[a-z0-9]+(?:\.[a-z0-9\.]*)?.*$)',line,re.IGNORECASE)
try:
url = m.group(3).replace(".","::").replace("?","++")
line = m.group(1) + url
except:
pass
return line,sn
def strip_tags(self,value):
"Return the given HTML with all tags stripped. From http://xrl.us/beb7n7"
return re.sub(r'<[^>]*?>', '', value)