Algorithmia Blog - Deploying AI at scale

Build a Sentiment Analysis Slack Chatbot in Python

slack-chatbot-appThis is a guest post by Chris Hannam, a professional Python and Java developer. Want to contribute your own how-to post? Let us know here.

As companies adopt chat tools like Slack to manage internal communication, they’re learning that a lot can get lost when communicating over text.

With this in mind, I built a Slack chatbot to keep an eye on messages, and flag negative ones to give the commenter a little nudge to be nicer in the future.

To achieve this, we’re going to use Slack’s API to analyze messages in real time. We’ll also be able to ask our Slack chatbot for overall sentiment of the channel from the previous 24-hours of messages.

Please note that this post only covers the how to setup a Slack chatbot to check the sentiment of messages. It’s not about building conversational chatbots. 

Ready? Let’s go.

Build Your Slack Chatbot

Slack already has huge number of bots available, but I wanted something simple and lightweight to monitor a Slack channel.

We’ll be using the Slack Real-Time Messaging API (RTM) to communicate with our channel. It’ll send us messages as they happen, which allows us to spot negative comments in real-time. We can then nudge users that send negative messages to reconsider their word choice in the future. We’ll also use this to request the current mood of a Slack channel.

To analyze messages for sentiment, we’ll be using the Social Sentiment Analysis microservice from Algorithmia. This algorithm was trained specifically on social media texts, which is perfect for Slack since messages tend to be short. Learn more about sentiment analysis algorithms here.

The Slack rtmbot works using plugins. Their code will connect you to a Slack channel, and pass all messages to your bot. In the root of the code is a plugins directory. Any Python files added to this directory will be run when messages are sent in your Slack account.

tl;dr Don’t want to read the tutorial?
Clone the bassdread/marvin repo, rename rtmbot.conf.example to rtmbot.conf, and then add your Slack and Algorithmia credentials. Already have a Slack RTM bot running? Copy the plugin to your plugins directory.

Step 1: Setup Your Slack Bot

The first thing we need to do is visit the Custom Integrations dashboard for your Slack team to get your API keys. The URL will look like this:
https://YOURSLACKCHANNEL.slack.com/apps/build/custom-integration

Select Bots, and have a look under “Integration Settings” for your API key. More information can be found in Slack’s Bot Users guide.

Slack Chatbot Custom IntegrationClone Slack’s Python bot kit, which has everything you need to run a bot using the RTM API.

cd python-rtmbot

and then…

pip install -r requirements.txt

This will install all our dependencies for Slack’s RTM. We then need to install the Algorithmia Python client.

pip install Algorithmia

Next, copy the example config file to our root directory like this:

cp docs/example-config/rtmbot.conf .

Add your Slack token to the file. Since we’re here, let’s also add the Algorithmia key, too. Sign up for free using the promo code ‘slack’ and get an additional 10,000 credits.

The rtmbot.conf file should look like this when you’re done:

DEBUG: True

SLACK_TOKEN: "YOUR_SLACK_TOKEN"
ALGORITHMIA_KEY: "YOUR_ALGORITHMIA_KEY"

Slack’s rtmbot is pretty simple. It passes every message that happens in your Slack channel to the plugin, which we’ll write next to analyze the sentiment of each message.

Step 2: Check the Sentiment Analysis of Messages

Create a Python file in the /plugins folder. We’ll import the dependencies and load the Algorithmia Client like this:

import Algorithmia
import yaml
import traceback

CONFIG = yaml.load(file("rtmbot.conf", "r"))

ALGORITHMIA_CLIENT = Algorithmia.client(CONFIG["ALGORITHMIA_KEY"])
#ALGORITHM = ALGORITHMIA_CLIENT.algo("nlp/SentimentAnalysis/0.1.2")
ALGORITHM = ALGORITHMIA_CLIENT.algo('nlp/SocialSentimentAnalysis/0.1.3')

outputs = []

I’ve also added the outputs array we’ll use in the next step. The outputs array is a global variable for sending messages. The first item in the array is the channel ID, and the second is the message. It’ll be used like this:

outputs = []
outputs.append(["C12345667", "hello world"])

Now we need to write two functions. The first to process incoming messages, and the second to get the current mood of the channel.

Step 3: Process Incoming Slack Messages

To process the incoming messages from Slack, we grab the text of the response. The Social Sentiment Analysis algorithm requires an object with the sentence as a string.

We then pass the message to Algorithmia to analyze. We’re only going to use the compound result, which is how positive or negative the sentiment of the sentence is on a scale of -1 (very negative) to 1 (very positive).

If the compound sentiment is -.75 or less, then we send a message to the Slack channel it originated in, and let the user know that the previous comment was pretty negative.

def process_message(data):

    text = data.get("text", None)

    try:
        sentence = {
            "sentence": text
        }
     
        # Pass the message to Algorithmia
        result = ALGORITHM.pipe(sentence)

        results = result.result[0]

        compound_result = results.get('compound', 0)

        # Send message to channel if too negative
        if compound_result < -0.75:
           outputs.append([data["channel"], "Easy there, negative Nancy!"])

That’s the basic step for sending messages to Slack in real-time. In order to ask our bot for current overall sentiment of the channel, we need to add some filtering, and math to average the sentiment over time.

Step 4: Track and Filter Slack Messages

The only slight gotcha is that you get all the messages for a channel. This includes people joining the channel, leaving, etc. Let’s filter this noise out, and UTF-8 encode the message while we’re at it.

if not text or data.get("subtype", "") == "channel_join":
return

# remove any odd encoding
text = text.encode('utf-8')

We’re also going to add a counter to track our sentiment over time. In our try statement, we’re going to add the following just before we send our message:

        verdict = "neutral"
        compound_result = results.get('compound', 0)

        if compound_result == 0:
            sentiment_results["neutral"] += 1
        elif compound_result > 0:
            sentiment_results["positive"] += 1
            verdict = "positive"
        elif compound_result < 0:
            sentiment_results["negative"] += 1
            verdict = "negative"

        # increment counter so we can work out averages
        sentiment_averages["total"] += 1

        for k, v in sentiment_results.iteritems():
            if k == "total":
                continue
            if v == 0:
                continue
            sentiment_averages[k] = round(
                float(v) / float(sentiment_averages["total"]) * 100, 2)

Now we just need to add two objects to keep track of the results of every message sent in Slack. This gets added just below our outputs object like this:

outputs = []

sentiment_results = {
    "negative": 0,
    "neutral": 0,
    "positive": 0
}

sentiment_averages = {
    "negative": 0,
    "neutral": 0,
    "positive": 0,
    "total": 0,
}

Now we’re ready to run our Slack bot.

Step 5: Run Your Chat Bot

By now you should a Python file in your /plugins directory that looks something like this:

import Algorithmia
import yaml
import traceback


CONFIG = yaml.load(file("rtmbot.conf", "r"))

ALGORITHMIA_CLIENT = Algorithmia.client(CONFIG["ALGORITHMIA_KEY"])
#ALGORITHM = ALGORITHMIA_CLIENT.algo("nlp/SentimentAnalysis/0.1.2")
ALGORITHM = ALGORITHMIA_CLIENT.algo('nlp/SocialSentimentAnalysis/0.1.3')

outputs = []

sentiment_results = {
    "negative": 0,
    "neutral": 0,
    "positive": 0
}

sentiment_averages = {
    "negative": 0,
    "neutral": 0,
    "positive": 0,
    "total": 0,
}


def display_current_mood(channel):
    reply = ""

    # something has gone wrong if we don't have a channel do nothing
    if not channel:
        return

    # loop over our stats and send them in the
    # best layout we can.
    for k, v in sentiment_averages.iteritems():
        if k == "total":
            continue
        reply += "{}: {}%\n ".format(k.capitalize(), v)

    outputs.append([channel, str(reply)])
    return

def process_message(data):

    text = data.get("text", None)

    if not text or data.get("subtype", "") == "channel_join":
        return

    # remove any odd encoding
    text = text.encode('utf-8')

    if "current mood?" in text:
        return display_current_mood(data.get("channel", None))

    # don't log the current mood reply!
    if text.startswith('Positive:'):
        return

    try:
        sentence = {
            "sentence": text
        }

        result = ALGORITHM.pipe(sentence)

        results = result.result[0]

        verdict = "neutral"
        compound_result = results.get('compound', 0)

        if compound_result == 0:
            sentiment_results["neutral"] += 1
        elif compound_result > 0:
            sentiment_results["positive"] += 1
            verdict = "positive"
        elif compound_result < 0:
            sentiment_results["negative"] += 1
            verdict = "negative"

        # increment counter so we can work out averages
        sentiment_averages["total"] += 1

        for k, v in sentiment_results.iteritems():
            if k == "total":
                continue
            if v == 0:
                continue
            sentiment_averages[k] = round(
                float(v) / float(sentiment_averages["total"]) * 100, 2)

        if compound_result < -0.75:
            outputs.append([data["channel"], "Easy there, negative Nancy!"])

        # print to the console what just happened
        print 'Comment "{}" was {}, compound result {}'.format(text, verdict, compound_result)

    except Exception as exception:
        # a few things can go wrong but the important thing is keep going
        # print the error and then move on
        print "Something went wrong processing the text: {}".format(text)
        print traceback.format_exc(exception)

Don’t worry if it doesn’t. Copy the above or get the complete code example here.

Run your bot from the command line:
python rtmbot.py

Putting It All Together

Once your bot is up and running, go to your Slack team, and invite it to a channel. Every message – except for channel joins – will now flow through your plugin, and then the Social Sentiment Analysis algorithm.

Try it out by typing a few nice things. Then check your terminal:
Comment "i think you’re great" was positive, compound result 0.6249

Now type some really terrible things, like:
Comment "idiot dumb" was negative, compound result -0.765

If things are working correctly, your bot will remind you to be nicer.

To get the current mood of the Slack channel, type current mood?. You’ll get a percentage breakdown of the positive, negative, and neutral sentiment from the past 24-hours.

The only thing left to do is deploy your bot to a host, like one of Slack’s recommended hosting providers.

With a little more work, your Slack chatbot could be left in charge of looking after an entire Slack team, keeping the tone from ever slipping into negative territory. This approach could be extended to monitor social media, like Twitter or Instagram comments. For instance, you could use the Retrieve Tweets With Keyword microservice to check the sentiment of a topic, follower, or even your own tweets, like this NLP approach to analyzing profanity of tweets.

Tools Used:

Want to build your own Slack chatbot? Get the code from this Github repo.

I have been playing with servers and writing code professional for around 15 years. My main language is Python, with a solid history of Java mixed in.

More Posts - Website

Follow Me:
Twitter