Building an offline chatbot - better brains
Mon, Aug 20, 2018
~ Read in 9 minutes ~
Last time, we got down the basics of making a chatbot that runs offline but can only greet us. This time around, we’ll get to making it a tad smarter and teaching it a couple of tricks. To make that happen, first we’ll take a step back and have a short look at the project structure, since I realized I completely omitted that last time. We won’t get into the finer points of
create-react-app of course, but some context is always helpful. Github link for reference, it helps to follow along :).
I’ll try to keep this as brief as possible, focusing on the really important bits.
Aside from the already mentioned compromise.js, there are a bunch of other dependencies that are used for the bots skills, namely (in no particular order):
- wikijs - The “Hail Mary”, last-ditch-effort to give the user some sort of a response. Wikijs is a browser-friendly wraper for any MediaWiki thingie out there.
- lodash - Needs no particular introduction, but I should note that I only use just stuff like
randomfor the simplicity. We can omit those and use homegrown functions instead, but that’s not the point here.
- moment - For all of our time-related needs. Used to determine time, format it in a friendly manner etc.
- weather-man - Another browser friendly library, that allows us to query OpenWeatherMap for local weather (althought I should note, you need an API key from OWM to make this work).
Besides these, you’ll find the usual suspects in any
create-react-app out there, plus some utilities that aren’t too important for the bot itself.
2. Bot files
Now, on to the fun part. I touched upon some of these in the previous article, but we’ll dig a bit further this time around.
Expanding on what we went over last time, first we get into the functionality this file exports -
The rest of that file is some code that determines context (the one we should start with at boot), plus some dependency imports, so I’ll skip those as they are pretty self-explanatory.
As mentioned in the previous installment, a skill is something that the bot can do / has a way of “understanding” and replying to. So, let’s take a closer look at the “greet” skill from last time, although expanded a bit:
Except for having more code in there, the one major difference from last time is the response mode. Having images in addition to text feels like a good choice to make things a bit more interesting, opposed to using just text.
Using the above pattern, we can build any skill we can think of. Of course, there’s a point at which the matching system will start being weird and inaccurate, due to the potential for overlaps in the matching syntax. It’s not too simple keeping track more than 10 skills and expecting reliable results (I imagine). We will address that later however, using something that start with Bayes, and ends with classifier :).
2.3. The rest of it
To clarify some of the GUI / state code, I’ll briefly go over how it all ties together:
/src/index.jssimply renders the
Appcomponent into the DOM.
/src/App.jsloads some css, and renders the basic layout into the DOM plus the
Chatcomponent, which is the “meat” here.
/src/Chat.jsis the big one - it manages the state of the application, renders the
Historycomponents, handles input submission, takes care of starting the bot and finally, is in charge of enabling / disabling the
Input(when the bot is producing a reply with that fancy typing animation ;)).
/src/Input.jsis a simple presentational component that loads some css and renders the input on screen. It’s state and behavior are dictated by
/src/History.jsis also a “dumb” presentational component that handles the chat history, but with a nasty hack - since I’m using react-typed in there, I have to somehow hide the blinking cursor when the bot finishes it’s message. Without getting into too much detail over the issue, this component is in charge of rendering text or images that come from the
Chatcomponent’s state above.
Building a new skill, step by step
Now, smarts are a relative thing when building an automated reply machine such as this one. It’s smarts are pretty much contained in the amount and diversity of replies it can provide. We have diversity of reply nailed down somewhat, by picking a random one on each input. But the real smarts are determined by the diversity of topics it can cover. The bot currently can do the following “tricks”:
/src/skills/general.jsuses wikipedia to try and find an answer to an input that doesn’t trigger any other response.
/src/skills/greet.jsgreets the user. It can also greet them by name, if it’s already saved by the intro skill.
/src/skills/name.jsreplies with the Bots name (tries to be a smartass in the process).
/src/skills/roll.jscan roll a dice for the user.
/src/skills/time.jstells you the current time, and can be a bit sassy if you keep on asking for the time.
/src/skills/util.jscontains the easter egg and system responses.
/src/skills/weather.jscan tell you the current weather. Currently hardcoded to my hometown, but can be easily changed by asking for geolocation from the browser or changing the LAT/LON values to your own location.
Now, let’s teach it something new.
Making the bot tell (dad) jokes
The internet is full of joke databases, but to preserve the bot’s sassy character, we’ll make it only tell dad jokes - the cringier, the better. Naturally, there already is a database for that sort of thing that also happens to have a very nice API. And that’s exactly what we’re going to use for the skill.
On to the code part itself (
While this is great, makes the bot smarter and perhaps funnier, it makes something painfully obvious - the objectively crappy matching algorithm :). One example that comes to mind is typing “what is a joke?” or something along those lines. That will trigger the newly built joke skill and that’s not the appropriate one for this scenario. The more appropriate one would be the general skill, which will ask wikipedia for an answer to the question and provide a meaningful response.
There are a ton of smaller things that can be improved, but the big one is, as mentioned above, the matching algorithm. Since we’re trying to keep this offline and reasonably fast a Naive Bayes Classifier comes to mind as a quick and relatively easy solution to the matching problem. There happens to be a very nice, browser-friendly library out there to address this, and we’ll use that in part three to make the bot behave a lot smarter.
Thanks for reading and join me in part 3, where we’ll get into some more interesting stuff that will (hopefully) make for a smarter chatbot.