Chatbot core making context possible

This forum is a place to ask questions about the python programming language.
Post Reply
michal
New User
Posts: 1
Joined: 22 Aug 2018, 15:14

Chatbot core making context possible

Post by michal » 04 Dec 2018, 23:03

Hello everyone,

I have finished writing the core for our chatbot. Now, it allows treating functions (lambdas or functions defined somewhere outside the list) as a proper response. The sample use of my extension is described below.

Simple shopping list chatbot
This is a simple example of a Shopping list chatbot. It allows you to add items to the list and get to know the items that are on the list. This example is able to understand three different queries.

1. Add jellies to the list!

Code: Select all

r"(.*)(add|put)( )(.*)( )(on|to)(.*)"
The first response will match any query that has somewhere on the beginning (there may be other words before it) a word "add" or "put" followed by a space character, the item we want to add, one more space character, and a word "on" or "to", and possibly a few more words.

Examples:
  • "Add coke to the list!" - In this case, we add "coke" to the list.
  • "Please add jellies on the list" - In this case, we add "jellies" to the list.
  • "Just add those jellies to the list" - Which jellies would you like to add? :lol: It will add "THOSE jellies". It does not really care what you mean by that.
Before (without this extension) you were able to respond with any string message. With the use of the NLTK library, we could even add some parts of the query to the response. Yet it did not allow us to change the state of our bot, making it stateless. So we could respond with something like that:

Code: Select all

"Sure, I will remember about your %3"
# If we "activate" this response with a query "Please add jellies on the list" the message printed to the screen will read "Sure, I will remember about your jellies". The "%3" gets replaced with a second match in the regex expression.
Now, as we have this extension we are able to use functions as our responses. Please get familiar with lambdas and ternary operator (Please see the first answer by Azhar Khan) so the code below will be trivial for you, trust me!

Code: Select all

lambda matches: "Noted!" if add_to_list(matches[3]) else "%3 is already on the list!"
This response will first run the function add_to_list. Based on the return value (If the item was already on the list, the return is False. Otherwise the return value is True.) the function will evaluate either to "Noted!" or "%3 is already on the list!". In the second case, "%3" will be replaced with the name of the item we are trying to add to the list.

There are two requirements for the response functions:
  • They MUST return a string value.
  • They MUST take ONE and ONLY argument named MATCHES.
The matches parameter will contain all of the parts of the regular expression that was matched. You can easily test it by writing a simple response with a lambda that prints the argument.
Putting it all together, this is the query-response pair we came up with:

Code: Select all

[
        r"(.*)(add|put)( )(.*)( )(on|to)(.*)", 
        [lambda matches: "Noted!" if add_to_list(matches[3]) else "%3 is already on the list!"]
]
2. What is on the list?

Code: Select all

r'What is on the list?'
The second response will match exactly one question "What is on the list?".
This time the response for this query is a bit simpler.

Code: Select all

lambda matches: ','.join(shopping_list)
This lambda function will return a string containing all of the items from the shopping list. Eventually, those items will be printed on the screen. If you do not remember the join function, please review the Treehouse video on those.

The final code for the second response:

Code: Select all

[
        r'What is on the list?',
        [lambda matches: ','.join(shopping_list)],
]
3. eciwoeifbofj3ofhwvw4?

Code: Select all

r'(.*)'
The last response will match any other question.
It will respond with one of two possible messages: 'I am afraid I dont understand.' or 'Please focus on the shopping.'.
The full code snippet for the third response:

Code: Select all

[
        r'(.*)',
        ['I am afraid I dont understand.', 'Please focus on the shopping.'],
]
Some helper functions and variables
The function add_to_list, as I said before, adds an item to the list. More specifically it checks whether the list contains given item. If it contains the item, it returns False. Otherwise, it adds the item to the list and returns True. This information is used in the first response by our chatbot.

Code: Select all

def add_to_list(item):
    '''
    This function adds an item to the shopping list.
    If given item is already in the list it returns
    False, otherwise it returns True
    '''

    if item in shopping_list:
        return False
    else:
        shopping_list.append(item)
        return True
The last thing we used was a list called shopping_list. Nothing special, it is just a simple list.

Code: Select all

shopping_list = []
The full code listing for the chatbot:

Code: Select all

from nltk.chat.util import Chat, reflections
import re
import random

# === This is the extension code for the NLTK library ===
#        === You dont have to understand it ===

class ContextChat(Chat):
    def respond(self, str):
        # check each pattern
        for (pattern, response) in self._pairs:
            match = pattern.match(str)

            # did the pattern match?
            if match:
                resp = random.choice(response)    # pick a random response

                if callable(resp):
                    resp = resp(match.groups())
                
                resp = self._wildcards(resp, match) # process wildcards

                # fix munged punctuation at the end
                if resp[-2:] == '?.': resp = resp[:-2] + '.'
                if resp[-2:] == '??': resp = resp[:-2] + '?'
                return resp

    def _wildcards(self, response, match):
        pos = response.find('%')
        while pos >= 0:
            num = int(response[pos+1:pos+2])
            response = response[:pos] + \
                self._substitute(match.group(num + 1)) + \
                response[pos+2:]
            pos = response.find('%')
        return response

    def converse(self, quit="quit"):
        user_input = ""
        while user_input != quit:
            user_input = quit
            try: user_input = input(">")
            except EOFError:
                print(user_input)
            if user_input:
                while user_input[-1] in "!.": user_input = user_input[:-1]    
                print(self.respond(user_input))

# === Your code should go here ===

shopping_list = []

def add_to_list(item):
    '''
    This function adds an item to the shopping list.
    If given item is already in the list it returns
    False, otherwise it returns True
    '''

    if item in shopping_list:
        return False
    else:
        shopping_list.append(item)
        return True

pairs = [
    [
        r'(.*)(add|put)( )(.*)( )(on|to)(.*)', 
        [lambda matches: 'Noted!' if add_to_list(matches[3]) else '%3 is already on the list!']
    ],
    [
        r'What is on the list?',
        [lambda matches: ','.join(shopping_list)],
    ],
    [
        r'(.*)',
        ['I am afraid I dont understand.', 'Please focus on the shopping.'],
    ],
]

if __name__ == "__main__":
    print("Hi, my name is Bob McBot and I will help you with your shopping.")
    print("What would you like to add to your shopping list?")
    chat = ContextChat(pairs, reflections)
    chat.converse()
    
1 x

Tags:

Post Reply