-
Notifications
You must be signed in to change notification settings - Fork 9
/
AIMLBot.py
275 lines (234 loc) · 8.29 KB
/
AIMLBot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
#!/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)