DEV Community

Alex Pliutau
Alex Pliutau

Posted on

Building a Slack Bot with Go and Wit.ai

wit

This is a text version of the video packagemain #9: Building Slack Bot with Go and Wit.ai.

We will build a simple Slack Bot with NLU functionality to get some useful information from Wolfram. No worries if you didn't use Wolfram before, it's a computational knowledge engine which can give you a short answer to your question.

There are different platforms for NLU, such as LUIS.ai, Wit.ai, RASA_NLU, we will use Wit.ai, an NLU platform acquired by Facebook, provides you functionality to parse text and extract useful information.

Our Bot will have minimal functionality, such as: "Reply to user on greeting" and then "Search in Wolfram and reply back". Why do we need NLU here? Because we don't know how user greets us, also we don't know how user will ask our Bot, will it be something like "Do you know who is the president of USA?" or "Can you tell me who is the president of USA.". Also it could be a typo from user. And NLU will help us to extract useful info from a custom message.

Let's start with creating our new Bot in Slack, for this I created new Slack team - https://packagemain.slack.com/apps

  • Search for Bots
  • Add Configuration
  • Username "packagemain"
  • Configure other settigns if you need and get token
  • Now we can check if bot exists: type Hi

Let's write some simple program which will use Slack real time messaging to receive user input. We need to use Slack token, which I already set locally.

Let's handle each incoming message in separate go routine.

go get github.com/nlopes/slack
Enter fullscreen mode Exit fullscreen mode
var (
    slackClient   *slack.Client
)

func main() {
    slackClient = slack.New(os.Getenv("SLACK_ACCESS_TOKEN"))

    rtm := slackClient.NewRTM()
    go rtm.ManageConnection()

    for msg := range rtm.IncomingEvents {
        switch ev := msg.Data.(type) {
        case *slack.MessageEvent:
            go handleMessage(ev)
        }
    }
}

func handleMessage(ev *slack.MessageEvent) {
    fmt.Printf("%v\n", ev)
}
Enter fullscreen mode Exit fullscreen mode

Setup Wit.ai

Before we're going to make Bot reply to user, we should configure Wit.ai. We're going to create an application there now.

Wit.ai has predefined entities and we will use 2 of them. We can also define our own and train Wit.ai to understand it, but I'll leave it to you as a homework.

  1. wit/greetings: Hi, Hello.
  2. wit/wolfram_search_query: Who is the president of Belarus, distance between Earth and Mars, formula of ethanol.

I searched for Wit.ai Go package, there are 5 packages on godoc.org and 4 of them are not compatible with new API. And one which is working has 2 stars on GitHub. We will use it, but I don't recommend to use it in Production environments. Wanna try to create Go package - create Wit.ai SDK please.

Get server access token in Settings.

go get github.com/christianrondeau/go-wit
Enter fullscreen mode Exit fullscreen mode
result, err := witClient.Message(ev.Msg.Text)
if err != nil {
    log.Printf("unable to get wit.ai result: %v", err)
    return
}

fmt.Printf("%v\n", result)
Enter fullscreen mode Exit fullscreen mode

Wit.ai will return us list of entities, each entity contains value and confidence, so let's filter entities with low confidence, for that we will set a confidence threshold as 0.9.

const confidenceThreshold = 0.9
Enter fullscreen mode Exit fullscreen mode

As you can see when we type "hi" Wit.ai returns us 2 successfull entities as "hi" is a valid wolfram search also. But greetings entity has a higher confidence, let's use one with highest confidence.

var (
    topEntity    wit.MessageEntity
    topEntityKey string
)

for key, entityList := range result.Entities {
    for _, entity := range entityList {
        if entity.Confidence > confidenceThreshold && entity.Confidence > topEntity.Confidence {
            topEntity = entity
            topEntityKey = key
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we can reply to user based on input:

func replyToUser(ev *slack.MessageEvent, topEntity wit.MessageEntity, topEntityKey string) {
    switch topEntityKey {
    case "greetings":
        slackClient.PostMessage(ev.User, "Hello user! How can I help you?", slack.PostMessageParameters{
            AsUser: true,
        })
        return
    case "wolfram_search_query":
        // TODO
    }

    slackClient.PostMessage(ev.User, "¯\\_(o_o)_/¯", slack.PostMessageParameters{
        AsUser: true,
    })
}
Enter fullscreen mode Exit fullscreen mode

We can see that our program also handles messages send by itself, let's fix that.

if len(ev.BotID) == 0 {
    go handleMessage(ev)
}
Enter fullscreen mode Exit fullscreen mode

Setup Wolfram

We can say hello, let's now get answer to user's question from Wolfram:

go get github.com/Krognol/go-wolfram
Enter fullscreen mode Exit fullscreen mode
res, err := wolframClient.GetSpokentAnswerQuery(topEntity.Value.(string), wolfram.Metric, 1000)
if err == nil {
    slackClient.PostMessage(ev.User, res, slack.PostMessageParameters{
        AsUser: true,
    })
    return
}

log.Printf("unable to get data from wolfram: %v", err)
Enter fullscreen mode Exit fullscreen mode

Testing part

me: Hi
Bot: Hello user! How can I help you?
me: Hola
Bot: Hello user! How can I help you?
me: Who is the president of US?
Bot: Donald Trump (from 20/01/2017 to present)
me: What is the meaning of life?
Bot: 42
me: bye
Bot: ¯\(o_o)

Discussion (1)

Collapse
noncototient profile image
Bo

Glorious. Def gonna build one