A rough idea for a conversation simulator

by Gregory Brown

These days, it seems I hardly have the time for doing fun random hacks. So here I've started one, and if anyone finds it interesting, please take it from here and let me know how it turns out.

Loosely based off of AIML, kind of, but not really:


class Conversation

def initialize(person)
@person = person
@response_id = 0
end

attr_reader :response_id

def say(msg)
print "#{@person}: "
@response_id = Response[@response_id].respond_to(msg)
end

class Response

def self.responses
@responses ||= {}
end

def self.[](id)
responses[id]
end

def initialize(id)
@id = id
self.class.responses[id] = self
@matchers = []
@messages = []
end

attr_reader :id,:matchers

def when(pattern,id)
@matchers << [pattern,id]
end

def inherits(arr)
arr.each do |id|
@matchers += Response[id].matchers
end
end

def might_say(msg,weight=1)
weight.times do
@messages << msg
end
end

def talk
puts @messages.sort_by { rand }.pop
end

def respond_to(msg)
@matchers.each do |pattern,id|
if msg =~ pattern
Response[id].talk
return id
end
end
return @id
end

end

end

def response(id)
r = Conversation::Response[id] || Conversation::Response.new(id)
yield(r)
end


UPDATE: Check out this annotated code submitted by a reader.

13 Comments

Stefan Edlich
2007-09-01 04:37:50
How about giving a running example so that we can see what it does? Thanks!
Gregory Brown
2007-09-01 05:45:02
If you can't see what it does or might do, you probably won't have much fun or luck extending it :)
hgs
2007-09-03 07:08:40
Oh, come on :-), it wouldn't hurt to put some intentional comments in there if you aren't providing example use!


This is an interesting approach, but it might be difficult to control since, from my reading of the code, the selected response depends on the order of insertion of the matching regexps. In AIML it depends on the type of wildcard, _ or *, and how specific the match is, as well as the topic.


I'm quite impressed with AIML, but it has an annoying lack of DRYness in that for each pattern, even within one topic, one can only have one THAT or response clause, so one must repeat the pattern again and again for different contexts instead of grouping them. Also, there is not much to hold and access state implicitly, so conversations degenerate when the Alicebot looses track of what the conversation was about. It would be interesting to attempt to tackle that, perhaps by allowing searching backwards through the conversation for patterns that help give context, without having to number the THAT clauses explicitly.
There are some natural language ruby libraries now, and some necessary AI topics have been covered in Ruby Quizzes to a certain depth, so this sort of thing might be more feasible than it was. I think the main difficulty is managing the amount of text and how it is structured, so a DSL of some kind seems useful.

Gregory Brown
2007-09-03 07:38:13
@hgs, fair enough:



c = Conversation.new("Ralph")


response(0) { |r|
r.when /Hello/i, 1
}


response(1) { |r|
r.might_say "Hello"
r.might_say "Hi"
r.might_say "Howdy"
r.when /Hello/i, 4
r.when /./, 3
}


response(3) { |r|
r.might_say "Finally, something interesting"
r.inherits [1]
}


response(4) { |r|
r.might_say "You already said that, didn't you?"
r.inherits [1]
}


loop do
print "Greg: "
r = gets
c.say(r)
end



Greg: Hello
Ralph: Howdy
Greg: Hello
Ralph: You already said that, didn't you?
Greg: Yes, I guess I did.
Ralph: Finally, something interesting
Greg: Hello
Ralph: You already said that, didn't you?


You're right that it depends on the ordering, which I use as a feature of sorts to allow patterns to be stacked on top of others, thus overriding some of the inherited patterns.


Of course, we do need things like partial matching, and doing things with state could get quite interesting. Perhaps if we could store action blocks that get fed the matches, it would become *very* interesting.


The idea of wrapping some of the Ruby natural language libraries is interesting, but as I mentioned, there's no way I'd find time to dig into this deeply. Of course, I'm very interested in anyone's progress down this road, and if you or anyone else come up with anything neat, let me know and I'll be sure to blog it here.


-greg

laki
2007-12-17 01:15:51
Hi,


I am new to Ruby, but as I am interested in AI, I was curious to see how your code works. I had to comment the code in order to be able to understand it. I hope you don't mind.

Gregory Brown
2007-12-17 01:33:01
Hi Laki,


Of course I don't mind, it's neat to see you add commentary to this somewhat intentionally barren code.


This may not be the clearest example if you're new to Ruby, but some comments:


a ||= b is just shorthand for a = a || b


@foo is an instance variable, @@foo a class variable, and $foo A global variable. Try to stay away from those last two :)


def self.[](id) just lets me do Conversation::Response[id]


It seems like you add a few might_say calls to my response(0) branch, and yeah, those won't get called unless something points back to the 0 response block.


Finally, this stuff isn't quite AI. You should check out some of the RubyQuiz stuff if that's where your interests lie. Hopefully you had fun playing with hack though. :)


-greg


Laki
2007-12-17 02:11:37
Thanks for clarifications.


Regarding AI I know this small code is not AI, but since you mentioned AIML, I was curious to understand your approach.


However, with little expansion, and decent database, it may achieve success of 'Chomsky smiley bot' or 20Q game! You can never know. ;)

laki
2008-07-21 13:19:01
huh... where did the link to the commentary go?
Gregory Brown
2008-07-21 13:41:02
@Laki,


Not sure. I didn't edit the comment stream, so it's a bit of a mystery. There are some major overhauls going on behind the scenes at the O'Reilly blogs.... maybe it got lost that way.

laki
2008-07-21 13:53:09
yeah, databases can get overly complex, and synchronizing them can be a real mess...


so here it is again: comments by a novice (me)


or you can just place the link in my first comment above, so that it would make sense again!

laki
2008-07-21 13:55:15
hmm, yeah, its oreilly system error. it erases link during posting.


another test: link to google

Gregory Brown
2008-07-21 14:22:04
@Laki, I added a link to the bottom of the post.
laki
2008-07-21 15:45:47
thanks.


i guess 'overhauls going on behind the scenes' affect the scenes ;-)


i will let webmasters know about the problem...