Skip to main content

Tools for creating chatbots

Project description

travis_badge docs_badge

Chatbot utils provides easy-to-use tools for building a chatbot capable of returning flexible, contextual responses when provided with text input.

Supports Python 2.x and 3.x.

By Contextual responses, I mean something like this;

human >> hey, what time is it?
  bot >> it's 10.32pm
human >> is that past my bedtime?
  bot >> no, you're good

The second phrase typed by the human, "is that past my bedtime?", is ambiguous, and required the bot to understand that this was an incomplete question related to the previous question, i.e. the context.

Installation

From PyPi

pip install chatbot_utils

From Github

  1. git clone github.com/eriknyquist/chatbot_utils

  2. cd chatbot_utils

  3. python setup.py build

  4. python setup.py install

API documentation

Read the API documentation here

Example bot with chatbot_utils, showing how to use contexts

The following example shows how to create a bot that can provide contexual responses to specific questions:

import random
import time

from chatbot_utils.responder import Responder
from chatbot_utils.utils import ContextCreator, get_input

random.seed(time.time())


responder = Responder()


# Add a context for talking about cats
with ContextCreator(responder) as ctx:
    # Phrase to trigger entry into cat context
    ctx.add_entry_phrases((["(.* )?(talk about|tell( me)? about) cats?.*"], ["Sure, I love cats"]))

    # These phrases will only be recognized after the entry phrase has been seen
    ctx.add_responses(
        (["(.* )?favou?rite thing about (them|cats?).*"], ["They are fuzzy"]),
        (["(.* )?(do )?you have (one|(a )?cat).*"], ["No, computer programs can't have cats."])
    )

    # Add a nested context inside the cat context (you can do this as deep as you like)
    with ContextCreator(ctx) as subctx:
        # Phrase to trigger entry into cat food context, will only be recognized when we're already in the cat context
        subctx.add_entry_phrases((["(.* )?(talk about|tell( me)? about) food?.*"], ["Sure, let's talk about cat food"]))

        # These phrases will only be recognized after BOTH entry phrases have been seen
        subctx.add_responses(
            (["(.* )?(favou?rite|best) type( of food)?.*"], ["Computer programs do not eat cat food."]),
        )

        # Add explicit exit phrase for cat food subcontext (if no exit phrase is added,
        # then he only way to exit the context is using a phrase that was added to the top-level
        # responder object with Responder.add_response())
        subctx.add_exit_phrases((["(.* )?(stop talking about ((dog )?food|this)|talk about something else).*"], ["OK, no more dog food talk."]))


# Add a context for talking about dogs
with ContextCreator(responder) as ctx:
    # Phrase to trigger entry into dog context
    ctx.add_entry_phrases((["(.* )?(talk about|tell( me)? about) dogs?.*"], ["Sure, I think dogs are great"]))

    # These phrases will only be recognized after the entry phrase has been seen
    ctx.add_responses(
        (["(.* )?favou?rite thing about (them|dogs?).*"], ["They are loyal"]),
        (["(.* )?(do )?you have (one|(a )?dog).*"], ["No, computer programs can't have dogs."])
    )

    # Add a nested context inside the dog context (you can do this as deep as you like)
    with ContextCreator(ctx) as subctx:
        # Phrase to trigger entry into dog food context, will only be recognized when we're already in the dog context
        subctx.add_entry_phrases((["(.* )?(talk about|tell( me)? about) food?.*"], ["Sure, let's talk about dog food"]))

        # These phrases will only be recognized after BOTH entry phrases have been seen
        subctx.add_responses(
            (["(.* )?(favou?rite|best) type( of food)?.*"], ["Computer programs do not eat dog food."]),
        )


# One of these responses will be randomly chosen whenever an unrecognized phrase is seen
responder.add_default_response(["Oh, really?", "Mmhmm.", "Indeed.", "How fascinating."])

# These phrases will only be recognized when no context is active
responder.add_responses(
    (["(.* )?hello.*"], ["How do you do?", "Hello!", "Oh, hi."]),
    (["(. *)?(good)?bye.*"], ["Alright then, goodbye.", "See ya.", "Bye."])
)

# Simple prompt to get input from command line and pass to responder
while True:
    text = get_input(" > ")
    resp, groups = responder.get_response(text)
    print("\n\"%s\"\n" % (random.choice(resp)))

Save this file as example_bot.py and run it with python example_bot.py. Example output:

#~$ python example_bot.py

> hello!

"Hello!"

> hey, can we talk about dogs for a bit?

"Sure, I think dogs are great"

> what's your favourite thing about them?

"They are loyal"

> do you have one?

"No, computer programs can't have dogs."

> OK, let's talk about cats now

"Sure, I love cats"

> do you have one?

"No, computer programs can't have cats."

> and what's your favourite thing about them?

"They are fuzzy"

Example bot with chatbot_utils, showing how to use format tokens

The following example shows how to create a bot that can remember what you said your favourite movie was, ad report it back later when asked:

from chatbot_utils.responder import Responder
from chatbot_utils.utils import ContextCreator, get_input

responder = Responder()

responder.add_default_response("Please tell me what your favourite movie is")

responder.add_responses(
    # When the bot is told what my favourite film is, it will save whatever film I said (4th
    # parenthesis group, or p3) in a new variable named "faveMovie"
    (["(.* )?(favou?rite|fave) (movie|film) is (.*)$"],
     "Cool, I will remember that your favourite film is {p3}!;;faveMovie={p3}"),

    # When the bot is asked to recall what my favourite film is, it will report the value of 'faveMovie'
    (["(.*)?(what is|what'?s|(can you )?tell me )?(what('?s)? )?my (fave|favou?rite) (movie|film).*"],
     "Your favourite film is {faveMovie}!")
)

# Simple prompt to get input from command line and pass to responder
while True:
    text = get_input(" > ")
    resp, groups = responder.get_response(text)
    print("\n\"%s\"\n" % resp)

Save this file as example_bot.py and run it with python example_bot.py. Example output:

#~$ python example_bot.py

> howdy!

"Please tell me what your favourite movie is"

> hmm, OK, I guess my favourite film is Gone With The Wind

"Cool, I will remember that your favourite film is Gone With The Wind!"

> hey, can you tell me what my fave movie is?

"Your favourite film is Gone With The Wind!"

> alright, now my favorite movie is spiderman 2

"Cool, I will remember that your favourite film is spiderman 2!"

> what's my favourite film?

"Your favourite film is spiderman 2!"

>

Performance characterizations

A core component of chatbot_utils is a custom dictionary called a ReDict, which expects values to be set with regular expressions as keys. Values can then be retrieved from the dict by providing input text as the key, and any values with a matching associated regular expression will be returned.

ReDicts with a large number of regular expressions (for example, a Responder with several thousand pattern/response pairs added using the add_response method) may take a significant amount of time when compiling the regular expression(s) initially. By default, this is done automatically on first attempt to access a ReDict, but you can also call Responder.compile() explicitly to control when the regular expressions associated with a responder are compiled.

One additional quirk to note is that having more parenthesis groups in your regular expressions results in a significant increase in compile time for ReDicts with a large number of items.

Analysis: compile time & fetch time with 100k items, no parenthesis groups

Each regular expression in the 100k items of test data used for this analysis was 14-19 characters in length, used several common special characters and was of the following form:

foo? 10|bar* 10

The Time to compile was calculated simply by timing the ReDict.compile() method. The Time to fetch is an average calculated by randomly fetching 10% of the total number of items in the dict (e.g. for a dict with 1000 pattern/value pairs added, 100 randomly-selected items would be fetched).

images/100000_items_no_extra_groups.png

Analysis: compile time & fetch time with 100k items, extra parenthesis groups

Each regular expression in the 100k items of test data used for this analysis was at least 25-30 characters in length, used several common special characters and was of the following form (note the addition parenthesis groups):

(f)(o)o? 10|b((a)(r)*) 10

Same as the previous test, the Time to compile was calculated by timing the ReDict.compile() method, and the Time to fetch is an average calculated by randomly fetching 10% of the total number of items in the dict.

images/100000_items_extra_groups.png

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

chatbot_utils-1.1.0-py3-none-any.whl (21.7 kB view hashes)

Uploaded Python 3

chatbot_utils-1.1.0-py2-none-any.whl (21.8 kB view hashes)

Uploaded Python 2

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page