Building chatbots with Hedwig.
Chatbots are so 2018. I think they were definitely a fad which has seemed to die down. That said, they’re still pretty useful.
I found myself needing to write one. These days, I mostly work in Elixir. So after looking around for a chatbot framework, I came across Hedwig. It’s an Elixir (check) chatbot framework (check) with a cool name and logo (check check). I assume it’s named after an owl from some book I didn’t read as a kid.
Taking a look at the documentation, I could see it was a little out-of-date with regards to Elixir and how supervision trees are used. It took me a bit of time to parse through their docs to get this running, so I figured I’d do a quick writeup to save myself the confusion again in the future.
Getting Started
Creating a new project with Hedwig is pretty easy. First, you’ll want to create
a new Elixir project with a supervision tree. mix
makes this simple if you’re
unfamiliar with how to set this up yourself.
mix new chatbot --sup
Next, we’ll need to add the Hedwig dependency to our mix.exs
file.
# mix.exs
defmodule Chatbot.MixProject do
use Mix.Project
def project do
[
app: :chatbot,
version: "0.1.0",
elixir: "~> 1.7",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
def application do
[
extra_applications: [:logger],
mod: {Chatbot.Application, []}
]
end
defp deps do
[
{:hedwig, "~> 1.0"}
]
end
end
Run mix deps.get
to install Hedwig. Now we can create our chatbot. The
skeleton of it, at least.
> mix hedwig.gen.robot
Welcome to the Hedwig Robot Generator!
Let's get started.
What would you like to name your bot?: testrobot
Available adapters
1. Hedwig.Adapters.Console
2. Hedwig.Adapters.Test
Please select an adapter: 1
* creating lib/chatbot
* creating lib/chatbot/robot.ex
* updating config/config.exs
Don't forget to add your new robot to your supervision tree
(typically in lib/chatbot.ex):
worker(Chatbot.Robot, [])
This last part is a bit outdated. Instead of using worker/2
, we’re just going
to add our chatbot as a child of our supervision process.
Let’s update lib/chatbot/application.ex
to add our new chatbot to the
supervision tree.
# lib/chatbot/application.ex
defmodule Chatbot.Application do
@moduledoc false
use Application
def start(_type, _args) do
children = [
# Add your chatbot here.
{Chatbot.Robot, []}
]
opts = [strategy: :one_for_one, name: Chatbot.Supervisor]
Supervisor.start_link(children, opts)
end
end
That’s all we need to do to get this going. We set up our chatbot to use
Hedwig.Adapters.Console
, so we can test this without hooking it up to Slack or
some other service.
> mix run --no-halt
Hedwig Console - press Ctrl+C to exit.
The console adapter is useful for quickly verifying how your
bot will respond based on the current installed responders
>
In this prompt, we can test that this thing works. By default, the chatbot
generator will have included Hedwig.Responders.Help
and
Hedwig.Responders.Ping
. Both of these responders use respond/2
, which
requires that you identify the chatbot by name. In this case, our chatbot is
called testrobot
.
> testrobot help
testrobot> testrobot help - Displays all of the help commands that testrobot knows about.
testrobot help <query> - Displays all help commands that match <query>.
testrobot: ping - Responds with 'pong'
testrobot test - Echos "Wow" when the user enters "test"
> testrobot ping
testrobot> user: pong
Great, it works. Now let’s write our own responder.
Responders
Responders are the part of the Hedwig framework that specify how chatbots interpret and respond to messages. Let’s write a quick “Hello, World!” example.
# lib/chatbot/responders/hello_world.ex
defmodule Chatbot.Responders.HelloWorld do
use Hedwig.Responder
@usage"""
hedwig hello - Echos "world" when the user says "hello".
"""
hear ~r/hello$/i, msg do
reply msg, "world"
end
end
We can add this to our chatbot in config/config.exs
.
# config/config.exs
use Mix.Config
config :chatbot, Chatbot.Robot,
adapter: Hedwig.Adapters.Console,
name: "testrobot",
aka: "/",
responders: [
{Hedwig.Responders.Help, []},
{Hedwig.Responders.Ping, []},
{Chatbot.Responders.HelloWorld, []}
]
The other two responders in the configuration are the defaults Hedwig adds on creation. You can remove them if you want.
Now, if we run the chatbot again with mix run --no-halt
, we can test our new
commmand.
> hello
testrobot> world
> Hello
testrobot> world
> HeLlO
testrobot> world
Great! Notice that this command isn’t namespaced. What I mean that is we aren’t
required to specify what chatbot we want to respond. This is how the hear/4
macro works. Our chatbots will listen to any message coming over the wire, and
respond when it matches, even if it wasn’t addressed to them.
In most cases, we’ll want our commands to be addressed. We can fix this with a quick change.
# lib/chatbot/responders/hello_world.ex
- hear ~r/hello$/i, msg do
+ respond ~r/hello$/i, msg do
reply msg, "world"
end
Now, if we run our chatbot, it will only respond to the hello
command if we
say its name first (testrobot
, if you forgot).
> hello
> testrobot hello
testrobot> world
That’s the basics of creating a chatbot with Hedwig. Next, we’ll look at how to manage conversational state, which is useful if you want users to feel like they’re having a conversation and not sending one-off messages.