Build A Simple Chatbot

Author: Theodore Odeluga

Introduction

The source files for this project can be downloaded from here

When it comes down to it, the working principle behind chatbots is pretty simple.

This is an application type whose sole purpose is to simulate human conversation.

A computer program smart enough to join a verbal exchange would have seemed like science fiction not so long ago but even with the richness and diversity of human language, these fascinating tools stopped being objects of speculation as far back as the 1960s; ELIZA - the first, was built 60 years ago.

Nevertheless, as we can imagine, the prospect of creating a conversational agent - even if not from scratch - is potentially a non-trivial task, not least because of advanced features dependent on techniques like natural language processing, machine learning and sentiment analysis to name just a few.

But what if we stripped back those specifications and prerequisites to their bare essentials?

What if we could just build a basic but functional system capable of providing accessible and effective utility on a smaller, simpler scale while still delivering a low-cost agile solution?

As long as the product fulfilled the technical definition above, the proposal could also include other advantages:

• A smaller memory footprint
• A smaller codebase
• Easy extension and customization
• Lower overhead from scaled down resource requirements

The somewhat unconventional idea described below, inspired by the above description, was too intriguing to resist when I first considered building a very minimal chatbot application.

The resulting codebase is indeed smaller (exceptionally so) and decidedly lightweight but effective as a proof-of-concept.

Essentially, this is a JavaScript implementation which works entirely on the client, does not require a large database (the “dataset” contained is nothing more than a collection of hard coded arrays) and naturally (as it runs in a local environment) doesn’t need anything particularly complicated by way of network or hardware infrastructure.

Developers reading this will also be pleased it doesn’t have any additional dependencies (no libraries or frameworks involved).

One might still argue that despite the above (as per the enterprise equivalent) if a trade-off in complexity results in robust and reliable functionality, the opposite approach for the sake of simplicity must then surely result in something less fit for purpose.

Maybe yes, maybe no. Let’s just say then that this is an experiment and put the simplified idea to the test.

To be sure, were obviously not recreating Chat GPT here but if we end up with an effective minimum viable product / prototype, this is as good a justification as any to proceed and a foundation for further development.

Concept

So who or what are we designing this chatbot for? Nothing and no one as yet. But if an experiment is worthwhile, we do need a test scenario.

I had the idea of building a bot that provides information for students on an imaginary Python course.

The Tony Stark School of Technology (hoping Marvel fans appreciate the reference) is running an introductory Python course.

After doing this course, you’ll be an all singing, all dancing, backflipping, high jumping, pole-vaulting, coffee making, uber-coding, football juggling software superhero. Or your money back.

Next, we need a plan to organize the program itself.

Decision Trees

A decision tree is a type of flowchart used to plan how a chatbot will communicate with its users.

Decision tree example

Decision trees can get quite elaborate and thankfully, this one isn’t.

These structures are very similar to the flowcharts used for planning other types of software and they work pretty well.

If communication can be imagined as a process, then we can map it out – at least in terms of what our bot will say in response to user input.

Note the circular stage in the graphic where the bot keeps asking for clarification. This is to simply ensure a user gets the right information if their request is perhaps a little too vague. We can all relate to how annoying a chatbot can be when it fails to understand and gives us the wrong information.

This bot at least tries to keep the user on topic by restricting communication to a specific theme.

A narrow subject domain makes sense. While attempting to match the global scale of a real-world system is of course beyond the scope of this project, it’s also unnecessary.

Unlike GPT, our bot doesn’t have to speak with everyone about everything, it simply needs to tell people about a Python course. So we don’t need to scrape the web for masses of information and constantly update it. We just need the right information in the right place (embedded in the source of the program).

Let’s code already

Now we have a project objective and a technical plan, let’s build.

Let’s consider the first module - load.js

function load(filename, filetype){

if(filetype=="js"){
     var fileref=document.createElement('script')
     fileref.setAttribute("type","text/javascript")
     fileref.setAttribute("src", filename)
}

if (typeof fileref!="undefined") {
      document.getElementsByTagName("head")[0].appendChild(fileref)
    }
}

//Capture user input + context
load("js/conversation/acknowledgement.js", "js")
load("js/conversation/aftercare.js", "js")
load("js/conversation/clarify.js", "js")
load("js/conversation/farewell.js", "js")
load("js/data/schedule.js", "js")
load("js/data/flexibility.js", "js")
load("js/data/accreditation.js", "js")
load("js/data/dates.js", "js")
load("js/data/cost.js", "js")
load("js/data/subjects.js", "js")
load("js/data/locations.js", "js")
load("js/search/token-search.js", "js")
load("js/input/input.js", "js")

Load.js conveniently loads all the other modules from a single location.

The alternative would've been to separately list all of the required tags in the HTML and keep adding to it.

This would have become pretty unwieldy pretty quickly.

If we decided to later add more features to our bot, it would’ve gotten messy managing an ever-expanding list while also trying to restructure the apps interface.

Load.js does all this easily – and separately - in an isolated file.

Next, we move on to the main JS folder, our repository for other components.

Two of the subfolders in 'js' contain a collection of scripts grouped and categorized by their collective purpose.

'data' provides details of the course (i.e. campus locations, dates, costs, schedules and accreditation etc.)

Likewise, 'conversation' and 'input' each house key aspects of the bot’s functionality.

The conversation folder is the most interesting part of the folder structure as it contains all the code for communication.

The bot is after all an automated customer service adviser.

In acknowledgement.js for example, it chooses from a set of initial responses to user queries.

let random_acknowledgement = Number();
let acknowledgement_array;

acknowledgement_array = [

     "Thats a great question. ",
     "Thank you for your interest in the Python course. ",
     "I hope I can help. ",
     "No problem. Let me explain. ",
     "I can help with that. ",
     "I can help you with this. ",
     "Thanks for your question. ",
     "Thanks for your interest. ",
     "Let me explain. ",
     "I'll explain. ",
     "Okay, here's how it works. ",
     "Thank you for asking. ",
     "Thank you for your enquiry. ",
     "Let me help here. ",
     "I'll give you an overview. "

]

function acknowledgement_response(random_acknowledgement){
  acknowledgement_array[random_acknowledgement];
}

Aftercare enables it to ask if anything else is required. This keeps the conversation going, allowing the bot to provide additional value.

aftercare_array = [

     "Is there anything else I can help with? ",
     "What else can I help you with? ",
     "I hope I can help. ",
     "Can I help you with anything else? ",
     "Can I give you any further assistance with anything? ",
     "Can I offer further assistance? ",
     "Anything else I can help with? ",
     "Anything else I can help with today? ",
     "How can I help you further today? ",
     "Anything else you need today? ",
     "How can I further assist your needs? ",
     "Would you like to know any more about the course? ",
     "Is there something else I can help you with today? ",
     "Please let me know if I can further assist. ",
     "What else can I tell you about the course? "

]

function aftercare_response(random_aftercare){
  aftercare_array[random_aftercare];
}

Clarify.js gets more information if the query is unclear.

clarify_array = [

     "I'm sorry, could you clarify your query? ",
     "I'm sorry, could you clarify that? ",
     "Sorry, could you be more specific? ",
     "I'm sorry, is there something specific you wanted to know? ",
     "I'm sorry, you'll need to clarify that. ",
     "Can I ask you to clarify that? ",
     "Could you be more specific (sorry)? ",
     "Is there anything specific I can help you with? ",
     "Sorry, what would you like to know exactly? ",
     "Can I assist with something specific? ",
     "Is there anything on the course you need to know about particularly? ",
     "What specific information can I provide? ",
     "What specifically can I assist with of interest to you? ",
     "Sorry, I need a more specific query. ",
     "Sorry, could you be more specific with your enquiry? "

]

function clarify_response(random_clarify){
  clarify_array[random_clarify];
}

And finally, farewell.js gives the user a courteous sign-off.

farewell_array = [

     "Have a great day! ",
     "No problem, have a great day! ",
     "Thanks again for your interest. All the best ",
     "Thanks, take care. ",
     "Thanks, have a good day today ",
     "Thank you. Have a good day ",
     "All the best and have a good day ",
     "Have a terrific day! ",
     "Thanks, enjoy the rest of the day ",
     "Many thanks, have a great day! ",
     "All the best and have a brilliant day! ",
     "You have a great day now. All the best. ",
     "Thanks for your interest. Have a brilliant day! ",
     "All the best to you. Have a great day! ",
     "Thanks for your time. Have a beautiful day! "

]

function farewell_response(random_farewell){
  farewell_array[random_farewell];
}

The rationale for choices of different phrases is simply to add the human effect - creating an impression of a more natural conversational output – as opposed to the stilted (and repetitive) tones of a robotic entity.

The randomization between the different phrases (achieved in token-search.js – more about this later) removes the sense of talking to a machine by adding variance, the same way a person would do even if they were having similar conversations – but with different people.

In token-search.js within the search folder, the application uses regular expressions to identify key words for establishing context from a user’s query.

Here’s an example, the regular expression for the schedule context:

let schedule_context = (/(length|long|duration|short|extent|temp)/g);

This enables the bot to respond to a query appropriately. Each keyword in the regular expression increases the opportunity for the bot to understand the user. As the user is likely to use at least one of these keywords, the bot will get its cue to respond with correct information.

As the regex can pick up a keyword from any position in the query string, we only have to make sure there are enough associated keywords to choose from.

Each context in turn is associated to a variable which is defined by an execution of the JavaScript regex function ‘match’. This uses the regex as its parameter value.

let schedule = search_term.match(schedule_context);

If a test returns true, information() is executed.

Any lack of detail prompts a request for clarity.

Testing

The input for this bot just needs to avoid use of special characters and punctuation (basic sanitization for security).

Accepted text isn’t case-sensitive, so it can be in caps or lower-case.

To test the bot, you can simply make a list of keywords from the regular expressions and type in different queries using any of those keywords.

Try these queries (don’t forget to remove the bracketed text (as well as the brackets themselves) and any special characters):

How long is the course? (schedule context)

How flexible is the course? (flexibility in terms of part-time, full-time etc.)

What is the start date of the course? (date context)

What subjects or topics in Python come up on the course? (subject’s context)

Where is my nearest campus? (location data context)

How much does the course cost? (cost context)

What qualification do I get at the end of the course? (accreditation context)

Thank you for your information (farewell context)

You’ll get a response – most of the time (more about the bot’s limitations below).

Limitations and caveats

Unfortunately, one way chatbots fail to provide desired output is through out-of-context responses. This bot is no exception. It just isn’t possible to account for every situation involving complex data and communication and is one of the reasons why a project like this would need to go through several iterations.

For example, asking something like this:

Query: If the course dates and times are subject to cancellation, will I get a refund in the event of a cancellation?

Could get this (as I did):

Response: I can help you with this. You'll certainly get a good grounding in Python. During the 5 day course you will be shown everything from the basics of variables and datatypes right through to libraries, frameworks and API's. How can I help you further today?

Lots of useful information as you can see. Except, the user wasn’t asking about (subject)s, they were asking about costs and refunds.

Humans can identify where the same word is used in phrases with different meanings (i.e. – plane (horizontal surface or aircraft), car (train carriage or automobile)) but by default, computers aren’t very good at this.

A large language model would likely make all the right connections – but that’s only because an LLM would have enough data and training - unlike a smaller program.

And so we must turn our attention to how we can improve the product.

Further development

No software is ever perfect and no software is ever “finished”.

As implied at the beginning, this project is a prototype. It’s not even a version 1.0 and certainly not a final product.

This also means on the plus side that it represents a technical opportunity and I hope all the coders among you take it and adapt it for your own versions.

I favored an “early release” to share knowledge and ideas which I hope has proved useful.

Here are a few ideas for further development:

Data

As this is a JS app, using client-side tools such as local storage or Indexed DB could allow the bot to “remember” inputs and learn from them.

This could make the bot more responsive (improving out-of-context responses).

Contextualization on a per keyword basis could follow a set of criteria (to define the context), keep track of the frequency of usage for each keyword and then from analysis of the frequency / context, predict where best to use those keywords in response.

This is something I’ll be working on myself.

Tokenization

By the same token (excuse the pun) following a similar methodology to the way natural language processing uses tokenization, instead of using keywords for triggering pre-written phrases, by contextualizing the users input based on those keywords, the bot could respond with other relevant and related keywords and dynamically construct completely new phrases from those words in real-time – which of course would mean not having to store replies beforehand (though nothing would stop you from keeping certain “stock phrases” for reuse).

Interface

The bot’s UI is fairly responsive and during testing looked acceptable in my browser (Mozilla Firefox). This included views both on the desktop and various smartphone sizes. But it didn’t fare so well with laptops and tablets. This could be fixed with an extension of the list of CSS media queries (I didn’t get round to doing this myself as I was focused on the bot’s behavior).

Conclusion

I hope you’ve found this a useful and enjoyable article. It’ll probably be a few months before I post again (the usual demands of work, life and other projects etc.) but I’ll be back again with more material in due course. In the meantime, stay safe, stay well, have fun and happy coding.