
Enhancing IVS Customer Success with Generative AI: Leveraging Amazon Q with Slack
a journey on how an internal chatbot helps to tap into our vast knowledge-base with Amazon Q and Slack
- An existing support Slack Bot is expanded to work with Amazon Q to facilitate the look up past conversations and access a large knowledge base for IVS questions.
- Using an Amazon Q native retrieval augmentation pipeline to create embedding to run inference on 2000+ redacted/sanitized tickets, documents and public user guides.
- The bot provides attribution to the both internal and external source document URLs.
1
2
3
4
5
6
7
8
9
10
11
12
func sanitizeWord(word string, customerNames []string) string {
// Regular expressions trying to filter text by ines and then each line by words
stageArnRegex := regexp.MustCompile(`arn:aws:ivs:[\w-]+:\d+:stage/[A-Za-z0-9]+`)
starfruitRegex := regexp.MustCompile(`\bStarfruit\b|AWSStarfruit`)
rtmpsSupportUrlRegex := regexp.MustCompile(`rtmps://[\w-]+\.support\.contribute\.live-video\.net(:\d+)?/[\w-]*/`)
tinyAmazonUrlRegex := regexp.MustCompile(`https:\/\/tiny\.amazon\.com\/[\w]+`)
dateRegex := regexp.MustCompile(`\d{4}-\d{2}-\d{2}`)
emailRegex := regexp.MustCompile(`\S+@\S+`)
accountIdRegex := regexp.MustCompile(`\b\d{12}\b`)
caseIdRegex := regexp.MustCompile(`\b\d{11}\b`)
arnRegex := regexp.MustCompile(`(arn:aws:|)\b[\w-]+:[\w-]+:\d+:[\w-/]+`)
urlRegex := regexp.MustCompile(`https?:\/\/\S*\.(mp4|m3u8|ts)`)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Apply regex patterns
switch {
case stageArnRegex.MatchString(word):
fmt.Println("Matched Stage ARN Regex:", word)
return "[Statge ARN]"
case starfruitRegex.MatchString(word):
fmt.Println("Matched starfruitRegex:", word)
return "[Starfruit]"
case rtmpsSupportUrlRegex.MatchString(word):
fmt.Println("Matched rtmpsSupportUrlRegex:", word)
return "[RTMPSSupportURL]"
...
case accountIdRegex.MatchString(word):
fmt.Println("Matched accountIdRegex:", word)
return "[AccountId]"
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func queueEventForProcessing(eventDetails EventDetails) error {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
log.Fatalf("Unable to load SDK config, %v", err)
}
sqsClient := sqs.NewFromConfig(cfg)
queueURL := "https://sqs.us-west-2.amazonaws.com/<accountID>/q-bot-queue.fifo"
// serealizing eventDetails to JSON
messageBody, err := json.Marshal(eventDetails)
if err != nil {
return fmt.Errorf("error marshalling event details: %w", err)
}
// sending the message to the SQS queue
_, err = sqsClient.SendMessage(context.TODO(), &sqs.SendMessageInput{
QueueUrl: aws.String(queueURL),
MessageBody: aws.String(string(messageBody)), // the message we are sending
MessageGroupId: aws.String("Group1337"), // some unique identifier for the group of messages
})
if err != nil {
return fmt.Errorf("error sending message to SQS: %w", err)
}
return nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func handleQBusinessQueryWithAttribution(ctx context.Context, applicationID, userMessage string, wg *sync.WaitGroup, responses map[string]string, label string) {
defer wg.Done()
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
log.Printf("Unable to load SDK config: %v", err)
responses[label] = fmt.Sprintf("%s: Error loading SDK config", err)
return
}
qClient := qbusiness.NewFromConfig(cfg)
input := &qbusiness.ChatSyncInput{
ApplicationId: aws.String(applicationID),
UserId: aws.String("<accountID>"),
UserMessage: aws.String(userMessage),
}
resp, err := qClient.ChatSync(ctx, input)
if err != nil {
log.Printf("Failed to send message to qClient: %v", err)
responses[label] = fmt.Sprintf("%s: Failed to query Qbusiness service", err)
return
}
log.Printf("Received response from Amazon Q (%s): %+v", label, resp)
responseMessage := formatResponseAttribution(resp)
responses[label] = fmt.Sprintf("%s: %s", label, responseMessage)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// sendMessageToSlack posts a message back to Slack. Ensure this uses the correct access token.
func sendMessageToSlack(userInput, messageText, threadTimestamp, userChannelID string) error {
slackToken := os.Getenv("SLACK_TOKEN")
api := slack.New(slackToken)
// Log the message details before sending to Slack
log.Printf("Sending message to Slack: ChannelID=%s, ThreadTimestamp=%s, Message=%s", userChannelID, threadTimestamp, messageText)
_, _, err := api.PostMessage(
userChannelID,
slack.MsgOptionText(messageText, false),
slack.MsgOptionPostMessageParameters(
slack.PostMessageParameters{
UnfurlLinks: true,
ThreadTimestamp: threadTimestamp,
},
),
)
if err != nil {
log.Printf("Failed to send message to Slack: %s", err.Error())
return errors.New("Failed to send message to Slack: " + err.Error())
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Append attribution information if available
if len(resp.SourceAttributions) > 0 {
message += "\n\n*Source Attributions*:\n"
re := regexp.MustCompile(`([a-zA-Z0-9-]+)_sanitized(\.json|_claude\.txt)`)
for _, attribution := range resp.SourceAttributions {
// Check if the title matches the pattern
match := re.FindStringSubmatch(*attribution.Title)
if len(match) > 1 {
// If the title matches the regex, generate a new URL based on the match
alphanumericName := match[1]
newURL := "https://a2z.amazon.com/" + alphanumericName
cleanSnippet := cleanSnippetText(*attribution.Snippet) // HTML Unescape string formatting
message += fmt.Sprintf("\nTitle: %s\n\nURL: %s\n\nSnippet: %s\n\n", *attribution.Title, newURL, cleanSnippet)
} else {
// If the title does not match the regex, use the original URL
originalURL := "URL not available"
if attribution.Url != nil {
originalURL = *attribution.Url
}
cleanSnippet := cleanSnippetText(*attribution.Snippet) // this helps with unescape HMTL
message += fmt.Sprintf("\nTitle: %s\n\nURL: %s\n\nSnippet: %s\n\n", *attribution.Title, originalURL, cleanSnippet)
}
}
} else {
message += "\nNo source attributions provided."
}
Any opinions in this post are those of the individual author and may not reflect the opinions of AWS.