Implementing the famous ELIZA chatbot in Python

ELIZA is a conversational agent, or “chatbot”, first implemented in 1966 by Joseph Weizenbaum. It was meant to emulate a Rogerian psychologist. Since then there have been various implementations, more or less similar to the original one. Emacs ships with an ELIZA-type program built in. The CIA even experimented with computer-aided interrogation of officers using a very similar, but rather more combative, version of the program.

My implementation is based on one originally written by Joe Strout. I have updated it significantly to use a more modern and idiomatic form of Python, but the text patterns in the reflections  and psychobabble  data structures are copied essentially verbatim.

Implementation

Let’s walk through the source code. Copy this into a file called eliza.py .

Run it with python eliza.py  and see if you can trip it up. Try not to spill your guts to your new computer therapist!

You will notice that most of the source code is taken up by a dictionary called reflections  and a list of lists called psychobabble . ELIZA is fundamentally a pattern matching program. There is not much more to it than that.

reflections  maps first-person pronouns to second-person pronouns and vice-versa. It is used to “reflect” a statement back against the user.

psychobabble  is made up of a list of lists where the first element is a regular expression that matches the user’s statements and the second element is a list of potential responses. Many of the potential responses contain placeholders that can be filled in with fragments to echo the user’s statements.

main  is the entry point of the program. Let’s take a closer look at it.

First, we print the initial prompt, then we enter a loop of asking the user for input and passing what the user says to the analyze  function to get the therapist’s response. If at any point the user types “quit”, we break out of the loop and the program exits.

Let’s see what’s going on in analyze .

We iterate through the regular expressions in the psychobabble  array, trying to match each one with the user’s statement, from which we have stripped the final punctuation. If we find a match, we choose a response template randomly from the list of possible responses associated with the matching pattern. Then we interpolate the match groups from the regular expression into the response string, calling the reflect  function on each match group first.

There is one syntactic oddity to note here. When we use the list comprehension to generate a list of reflected match groups, we explode the list with the asterisk (*) character before passing it to the string’s format  method. Format expects a series of positional arguments corresponding to the number of format placeholders – {0}, {1}, etc. – in the string. A list or a tuple can be exploded into positional arguments using a single asterisk. Double asterisks (**) can be used to explode dictionaries into keyword arguments.

Now let’s examine the reflect  function.

There is nothing too complicated going on in it. First, we make the statement lowercase, then we tokenize it by splitting on whitespace characters. We iterate through the list of tokens and, if the token exists in our reflections  dictionary, we replace it with the value from the dictionary. So “I” becomes “you”, “your” becomes “my”, etc.

As you can see, ELIZA is an extremely simple program. The only real intelligence in it is involved in the creation of suitably vague response templates. Try fiddling with the psychobabble  list to extend ELIZA’s conversational range and give her a different tone.

Connecting Eliza to IRC

The command line version of ELIZA is pretty fun, but wouldn’t it be cool to let her loose on the internet? I’m going to show you how to hook up the program we have already written to an IRC bot that connects to a public server, creates its own channel and carries on conversations with real human beings.

We’re going to use the SingleServerIRCBot  in the irc  package. You can install it with pip.

Copy this code into a file called elizabot.py .

Let’s go through it. The SingleServerIRCBot  class gives us some hooks we can use to respond to server events. We can make the bot join the given channel automatically by overriding the on_welcome  method.

Now, we have to listen to messages on the channel we joined and check if they are addressed to the bot. If they are, we pass the message to analyze  from the eliza  module and write the response back to the channel, prefixed with the nick of the user who sent the message.

We do that by overriding the on_pubmsg  method.

The IF statement in this method checks that the received message is prefixed with the bot’s nickname. Only then do we generate and send a response.

There is a little subtlety involved in sending messages. To send a message to a channel, we have to use the privmsg  method on the connection  object passed into the on_pubmsg  method, giving the name of the channel as the first argument. Fairly unintuitive, but easy once you know.

The rest of the script is straightforward. It just consists of a main  function that reads the command line arguments and starts the bot.

To run the script and and connect the bot to Freenode, type this command:

The bot will connect to the server, grab the nickame “Elizabot”, and join the #ElizaBot channel.

Here’s a demo of the bot in action:

talking_to_eliza

Download Mastering Decorators

Mastering_decorators_cover

Enjoyed this article? Join the newsletter and get Mastering Decorators - a gentle 22-page introduction to one of the trickiest parts of Python.

Weekly-ish. No spam. Unsubscribe any time. Powered by ConvertKit