Skip to content
Slack AI Bot with AWS Bedrock Part 2

In part one of this blog, we set up Slack events to connect to AWS Bedrock, but we did not have a conversation system, so any message sent to the LLM Model was a brand-new message. In this part, we will fix that so the LLM has your previous message and the message it gives you.

To get started, please follow part one of this blog as a prerequisite to get things going. In Slack, the following is how your event subscriptions should look. We have added Channel History to allow us to get the conversation history from Slack.

We also need to create an AWS DynamoDB to cache the last message sent by the user to ensure we are handling retries and duplicate requests. We will only have one record in the database. We also need to ensure we set up DynamoDB in the transactional mode so that reads and writes are consistent. Let's set the DynamoDB table.

Give your table a name and set the partition key as id.

Pick customized settings and DynamoDB standard.

We will not need more than one read or write per second, so make sure you set the consistency to Transactional.

I will go with On-Demand, but you can set up provisioning. I recommend On-Demand as we don’t know the workload demands.

Always enable encryption even when we are not storing anything sensitive. Turn on deletion protection if doing this in production.

Add any tags needed and click Create Table.

Now, we need to update the code in our lambda function. This is an improved version of the code, but you might need to adjust it more to fit what you are doing. Note: We have switched from AI21 Labs Jurassic-2 Ultra to Anthropic Claude V2.1 as it is more powerful. To access Anthropic Claude, you must submit a use case, which could be using the model for education purposes.

import json

import boto3

import urllib3

import hashlib

import os

import re





bedrock = boto3.client(service_name='bedrock-runtime')

dynamodb = boto3.resource('dynamodb')

table = dynamodb.Table('AIState')

id = "123e4567-e89b-12d3-a456-426614174000"

slackUrl = 'https://slack.com/api/chat.postMessage'

SlackChatHistoryUrl = 'https://slack.com/api/conversations.replies'

slackToken = os.environ.get('token')



http = urllib3.PoolManager()



def call_bedrock(msg):

    body = json.dumps({

        "prompt": '\n'.join(msg),

        "max_tokens_to_sample": 300,

        "temperature": 0.1,

        "top_p": 0.9,

    })



    response = bedrock.invoke_model(body=body, modelId='anthropic.claude-v2:1', accept='application/json', contentType='application/json')

    response_body = json.loads(response.get('body').read())



    return response_body.get('completion').replace('\nAssistantt: ','')



def hash_message(message):

    msg_bytes = message.encode('utf-8')

    sha1 = hashlib.sha1(msg_bytes)

    hex_digest = sha1.hexdigest()

    

    return hex_digest

    

def get_message():

    

    response = table.get_item(Key={'id': id})

    if 'Item' in response and 'message' in response['Item']:

        return response['Item']['message']

    return None



    

def set_message(message):

    

    table.update_item(

        Key={'id': id},

        UpdateExpression="SET message = :value",

        ExpressionAttributeValues={':value': hash_message(message)}

    )



def lambda_handler(event, context):

    headers = {

        'Authorization': f'Bearer {slackToken}',

        'Content-Type': 'application/json',

    }

    slackBody = json.loads(event['body'])

    print(json.dumps(slackBody))

    slackText = slackBody.get('event').get('text')

    slackUser = slackBody.get('event').get('user')

    channel =  slackBody.get('event').get('channel')

    thread_ts = slackBody.get('event').get('thread_ts')

    ts = slackBody.get('event').get('ts')

    eventType = slackBody.get('event').get('type')

    subtype = slackBody.get('event').get('subtype')

    bot_id = slackBody.get('event').get('bot_id')

    is_last_message_from_bot = False

    bedrockMsg = []

    

    if eventType == 'message' and bot_id is None and subtype is None and thread_ts is not None:

        if get_message() != hash_message(slackText):

            set_message(slackText)

            # We got a new message in the thread lets pull from history

            historyResp = http.request('GET', f"{SlackChatHistoryUrl}?channel={channel}&ts={thread_ts}", headers=headers)

            messages = historyResp.json().get('messages')

            for message in messages:

                cleanMsg = re.sub(r'<@.*?>', '', message.get('text'))

                bot_profile = message.get('bot_profile')

                if bot_profile is None:

                    bedrockMsg.append(f'Human: {cleanMsg}')

                    is_last_message_from_bot = False

                else:

                    bedrockMsg.append(f'\n\nAssistant: {cleanMsg}')

                    is_last_message_from_bot = True

            bedrockMsg.append('\n\nAssistant:') # Message must always end with \n\nAssistant:

 

            if not is_last_message_from_bot: # Do not respond if the last message was a response

                msg = call_bedrock(bedrockMsg)

                data = {'channel': channel, 'text': f"<@{slackUser}> {msg}", 'thread_ts': thread_ts}

                response = http.request('POST', slackUrl, headers=headers, body=json.
                    

Related Articles

Moving at the Speed of Cryptocurrency with Infrastructure as Code

Read more

Automating API Information Storage with AWS - Introduction

Read more

AWS EKS Identity is Not Mapped Error

Read more

Contact Us

Achieve a competitive advantage through BSC data analytics and cloud solutions.

Contact Us