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...
Mahmood
2024-06-25

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.dumps(data))
Mahmood
Engineer
Read More
View all posts
Cloud Engineering
Automating API Information Storage with AWS - Introduction
APIs serve as the backbone of software development, enabling applications to communicate with one another seamlessly.

Todd Bernson
2024-06-25

Cloud Engineering
Automating API Information Storage with AWS - Technical Deep Dive into Automated API Information Storage System
Managing API information is a big challenge, demanding streamlined solutions for efficiency and reliability. My introduction to automating API informati...

Todd Bernson
2024-06-25

Cloud Engineering
AWS EKS Identity is Not Mapped Error
If you are using AWS IAM Identity Center and grant a role access to an AWS EKS (Elastic Kubernetes Service) and you are unable to access the cluster when you run a KubeCTL command. You have...
Mahmood
2024-06-25