Let's say you run a company with a customer assistance call center or a website where clients give feedback about your services. After operating for a while your company starts to scale up, more traffic, hence more users sharing their feedback. So it's the right time to begin implementing chatbots and even integrating them with AI.
Alright without further let's jump in. In this article, I will show you how you can create such smart Chatbots using AWS services. Our chatbot will be smart to receive messages from clients, then it will process the user tone, and based (on positive, negative, or neutral) it will categorize the messages using Amazon Comprehend for sentiment analysis.
The main parts of this article:
1- About AWS Services
2- Architecture overview (Terraform)
3- Technical Part (GO code)
4- Result
5- Conclusion
About AWS Services
1- Amazon Lex to handle the chatbot interactions.
To learn more about Amazon Lex: Official Page
2- Amazon Comprehend for sentiment analysis.
To learn more about Amazon Comprehend: Official Page
3- AWS Lambda will coordinate the process by taking input, querying Lex, analyzing the sentiment with Comprehend, and categorizing the message.
To learn more about AWS Lambda: Official Page
Architecture Overview
Our architecture will be serverless, mainly everything will be done event-driven however you can implement something similar using containers.
terraform {
required_version = "1.5.1"
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.70.0"
}
}
}
provider "aws" {
region = var.region
}
resource "aws_iam_role" "lambda_execution_role" {
name = "lets-build-lambda-role"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = "sts:AssumeRole",
Effect = "Allow",
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
resource "aws_iam_policy" "lambda_lex_comprehend_policy" {
name = "lambda_lex_comprehend_policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"lex:PostText",
"comprehend:DetectSentiment",
"logs:*"
]
Resource = "*"
Effect = "Allow"
}
]
})
}
resource "aws_iam_role_policy_attachment" "lambda_policy_attachment" {
role = aws_iam_role.lambda_execution_role.name
policy_arn = aws_iam_policy.lambda_lex_comprehend_policy.arn
}
resource "aws_lambda_function" "chatbot_function" {
filename = "./bootstrap.zip"
function_name = "lets-build-function"
handler = "main"
role = aws_iam_role.lambda_execution_role.arn
memory_size = "128"
timeout = "3"
source_code_hash = filebase64sha256("./bootstrap.zip")
runtime = "provided.al2"
architectures = ["arm64"]
environment {
variables = {
LEX_BOT_NAME = "Chatbot"
LEX_BOT_ALIAS = "Prod"
REGION = "${var.region}"
}
}
}
resource "aws_lambda_permission" "lex_lambda_invocation" {
statement_id = "AllowLexInvocation"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.chatbot_function.arn
principal = "lex.amazonaws.com"
}
resource "aws_lex_bot" "chatbot" {
name = "Chatbot"
child_directed = false
process_behavior = "BUILD"
enable_model_improvements = true
detect_sentiment = true
intent {
intent_name = aws_lex_intent.customer_intent.name
intent_version = aws_lex_intent.customer_intent.version
}
abort_statement {
message {
content_type = "PlainText"
content = "Sorry, I am not able to assist you at this time"
}
}
}
resource "aws_lex_intent" "customer_intent" {
name = "ExampleIntent"
sample_utterances = [
"I need help",
"Something is not working",
"Please assist me",
]
fulfillment_activity {
type = "ReturnIntent"
}
}
resource "aws_lex_bot_alias" "prod" {
name = "Prod"
bot_name = aws_lex_bot.chatbot.name
bot_version = aws_lex_bot.chatbot.version
}
output "lex_bot_arn" {
value = aws_lex_bot.chatbot.arn
}
Technical Part (GO code)
package main
import (
"context"
"log"
"strings"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/comprehend"
"github.com/aws/aws-sdk-go/service/lexruntimeservice"
)
type ChatInput struct {
Message string `json:"message"`
UserID string `json:"user_id"`
}
type ChatOutput struct {
ResponseMessage string `json:"response_message"`
Category string `json:"category"`
}
var lexClient *lexruntimeservice.LexRuntimeService
var comprehendClient *comprehend.Comprehend
func handler(ctx context.Context, chatInput ChatInput) (ChatOutput, error) {
log.Printf("-- hello --")
sess := session.Must(session.NewSession())
lexClient = lexruntimeservice.New(sess)
comprehendClient = comprehend.New(sess)
lexResp, err := lexClient.PostText(&lexruntimeservice.PostTextInput{
BotName: aws.String("Chatbot"),
BotAlias: aws.String("Prod"),
UserId: aws.String(chatInput.UserID),
InputText: aws.String(chatInput.Message),
})
if err != nil {
log.Printf("Error communicating with Lex: %v", err)
return ChatOutput{}, err
}
responseMessage := aws.StringValue(lexResp.Message)
compResp, err := comprehendClient.DetectSentiment(&comprehend.DetectSentimentInput{
Text: aws.String(chatInput.Message),
LanguageCode: aws.String("en"),
})
if err != nil {
log.Printf("Error detecting sentiment with Comprehend: %v", err)
return ChatOutput{}, err
}
category := categorizeBySentiment(*compResp.Sentiment)
return ChatOutput{
ResponseMessage: responseMessage,
Category: category,
}, nil
}
func categorizeBySentiment(sentiment string) string {
switch strings.ToLower(sentiment) {
case "positive":
return "Positive Feedback"
case "negative":
return "Critical Feedback"
case "neutral":
return "General Inquiry"
case "mixed":
return "Mixed Feedback"
default:
return "Uncategorized"
}
}
func main() {
lambda.Start(handler)
}
Result
Now let's test by triggering our Lambda function. I will input two different JSONs, one for good feedback and one for bad, and we should be able to detect them.
{
"user_id": "1234",
"message": "Something is not working, I need help please"
}
As we can see Amazon Comprehend was able to detect the tone.
responseMessage := aws.StringValue(lexResp.Message)
Since in this section I am returning the response directly from the Lex it returned an empty string, but you can add a response there or even edit the Lambda code to return content.
{
"user_id": "12345",
"message": "Thank you, it worked as expected"
}
Here again, it was able to detect that it was positive, but since we had an abort statement as a fallback it returned that content.
Conclusion
In today's world data is everything, and you always need to collect this data (feedback) from your clients, which will lead to the success of your corporation. I hope my article helped you to achieve something or even added some motivation to your to-do tasks.
Happy coding 👨🏻💻
If you'd like more content like this, please connect with me on LinkedIn.
Top comments (0)