Skip to content

Commit 7b59f28

Browse files
committed
Replace slash command handler with event handlers
1 parent 6fe1923 commit 7b59f28

File tree

4 files changed

+74
-36
lines changed

4 files changed

+74
-36
lines changed

packages/cdk/resources/Apis.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,17 @@ export class Apis extends Construct {
2727
// Create /slack resource path
2828
const slackResource = apiGateway.api.root.addResource("slack")
2929

30-
// Create the '/slack/ask-eps' POST endpoint and integrate it with the SlackBot Lambda
30+
// Create the '/slack/events' POST endpoint for Slack Events API
31+
// This endpoint will handle @mentions, direct messages, and other Slack events
3132
// eslint-disable-next-line @typescript-eslint/no-unused-vars
32-
const slackAskEpsEndpoint = new LambdaEndpoint(this, "SlackAskEpsEndpoint", {
33+
const slackEventsEndpoint = new LambdaEndpoint(this, "SlackEventsEndpoint", {
3334
parentResource: slackResource,
34-
resourceName: "ask-eps",
35+
resourceName: "events",
3536
method: HttpMethod.POST,
3637
restApiGatewayRole: apiGateway.role,
3738
lambdaFunction: props.functions.slackBot
3839
})
40+
3941
this.apis = {
4042
api: apiGateway
4143
}

packages/cdk/resources/Functions.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {Secret} from "aws-cdk-lib/aws-secretsmanager"
66

77
// Claude model for RAG responses
88
const RAG_MODEL_ID = "anthropic.claude-3-sonnet-20240229-v1:0"
9-
const SLACK_SLASH_COMMAND = "/ask-eps"
109
const BEDROCK_KB_DATA_SOURCE = "eps-assist-kb-ds"
1110
const LAMBDA_MEMORY_SIZE = "265"
1211

@@ -48,7 +47,7 @@ export class Functions extends Construct {
4847
additionalPolicies: [props.createIndexManagedPolicy]
4948
})
5049

51-
// Lambda function to handle Slack bot interactions
50+
// Lambda function to handle Slack bot interactions (events and @mentions)
5251
const slackBotLambda = new LambdaFunction(this, "SlackBotLambda", {
5352
stackName: props.stackName,
5453
functionName: `${props.stackName}-SlackBotFunction`,
@@ -59,7 +58,6 @@ export class Functions extends Construct {
5958
additionalPolicies: [props.slackBotManagedPolicy],
6059
environmentVariables: {
6160
"RAG_MODEL_ID": RAG_MODEL_ID,
62-
"SLACK_SLASH_COMMAND": SLACK_SLASH_COMMAND,
6361
"KNOWLEDGEBASE_ID": props.knowledgeBaseId || "placeholder",
6462
"BEDROCK_KB_DATA_SOURCE": BEDROCK_KB_DATA_SOURCE,
6563
"LAMBDA_MEMORY_SIZE": LAMBDA_MEMORY_SIZE,

packages/cdk/stacks/EpsAssistMeStack.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,9 @@ export class EpsAssistMeStack extends Stack {
130130
})
131131

132132
// Output: SlackBot Endpoint
133-
new CfnOutput(this, "SlackBotEndpoint", {
134-
value: `https://${apis.apis["api"].api.domainName?.domainName}/slack/ask-eps`
133+
new CfnOutput(this, "SlackBotEventsEndpoint", {
134+
value: `https://${apis.apis["api"].api.domainName?.domainName}/slack/events`,
135+
description: "Slack Events API endpoint for @mentions and direct messages"
135136
})
136137

137138
// Final CDK Nag Suppressions

packages/slackBotFunction/app.py

Lines changed: 65 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@
2929
signing_secret=signing_secret,
3030
)
3131

32-
# Get the expected slack and AWS account params to local vars.
33-
SLACK_SLASH_COMMAND = os.environ["SLACK_SLASH_COMMAND"]
32+
# Get the expected AWS account params to local vars.
3433
KNOWLEDGEBASE_ID = os.environ["KNOWLEDGEBASE_ID"]
3534
RAG_MODEL_ID = os.environ["RAG_MODEL_ID"]
3635
AWS_REGION = os.environ["AWS_REGION"]
@@ -49,53 +48,79 @@ def log_request(slack_logger, body, next):
4948
return next()
5049

5150

52-
def respond_to_slack_within_3_seconds(body, ack):
51+
def respond_to_mention_within_3_seconds(message, say):
5352
"""
54-
Slack Bot Slash Command requires an Ack response within 3 seconds or it
53+
Slack Bot @mention requires an Ack response within 3 seconds or it
5554
messages an operation timeout error to the user in the chat thread.
5655
5756
The SlackBolt library provides a Async Ack function then re-invokes this Lambda
58-
to LazyLoad the process_command_request command that calls the Bedrock KB ReteriveandGenerate API.
57+
to LazyLoad the process_mention_request that calls the Bedrock KB RetrieveAndGenerate API.
5958
60-
This function is called initially to acknowledge the Slack Slash command within 3 secs.
59+
This function is called initially to acknowledge the @mention within 3 secs.
6160
"""
6261
try:
63-
user_query = body["text"]
62+
user_query = message["text"]
63+
user_id = message["user"]
64+
thread_ts = message.get("thread_ts", message["ts"]) # Use thread_ts if in thread, otherwise use message ts
65+
6466
logger.info(
65-
f"Acknowledging command: {SLACK_SLASH_COMMAND}",
66-
extra={"user_query": user_query},
67+
f"Acknowledging @mention from user {user_id}",
68+
extra={"user_query": user_query, "thread_ts": thread_ts},
6769
)
68-
ack(f"\n{SLACK_SLASH_COMMAND} - Processing Request: {user_query}")
70+
71+
# Respond in thread with a processing message
72+
say(text="Processing your request...", thread_ts=thread_ts)
6973

7074
except Exception as err:
71-
logger.error(f"Error acknowledging command: {err}")
72-
ack(f"{SLACK_SLASH_COMMAND} - Sorry an error occurred. Please try again later.")
75+
logger.error(f"Error acknowledging @mention: {err}")
76+
thread_ts = message.get("thread_ts", message["ts"])
77+
say(text="Sorry, an error occurred. Please try again later.", thread_ts=thread_ts)
7378

7479

75-
def process_command_request(respond, body):
80+
def process_mention_request(message, say):
7681
"""
77-
Receive the Slack Slash Command user query and proxy the query to Bedrock Knowledge base ReteriveandGenerate API
78-
and return the response to Slack to be presented in the users chat thread.
82+
Process the @mention user query and proxy the query to Bedrock Knowledge base RetrieveAndGenerate API
83+
and return the response to Slack to be presented in the thread.
7984
"""
8085
try:
81-
user_query = body["text"]
86+
# Extract the user's query, removing the bot mention
87+
raw_text = message["text"]
88+
user_id = message["user"]
89+
thread_ts = message.get("thread_ts", message["ts"]) # Use thread_ts if in thread, otherwise use message ts
90+
91+
# Remove bot mention from the text to get clean query
92+
# Bot mentions come in format <@U1234567890> or <@U1234567890|botname>
93+
import re
94+
95+
user_query = re.sub(r"<@[UW][A-Z0-9]+(\|[^>]+)?>", "", raw_text).strip()
96+
8297
logger.info(
83-
f"Processing command: {SLACK_SLASH_COMMAND}",
84-
extra={"user_query": user_query},
98+
f"Processing @mention from user {user_id}",
99+
extra={"user_query": user_query, "thread_ts": thread_ts},
85100
)
86101

102+
if not user_query:
103+
say(
104+
text="Hi there! Please ask me a question and I'll help you find information from our knowledge base.",
105+
thread_ts=thread_ts,
106+
)
107+
return
108+
87109
kb_response = get_bedrock_knowledgebase_response(user_query)
88110
response_text = kb_response["output"]["text"]
89-
respond(f"\n{SLACK_SLASH_COMMAND} - Response: {response_text}\n")
111+
112+
# Reply in thread with the response
113+
say(text=response_text, thread_ts=thread_ts)
90114

91115
except Exception as err:
92-
logger.error(f"Error processing command: {err}")
93-
respond(f"{SLACK_SLASH_COMMAND} - Sorry an error occurred. Please try again later.")
116+
logger.error(f"Error processing @mention: {err}")
117+
thread_ts = message.get("thread_ts", message["ts"])
118+
say(text="Sorry, an error occurred while processing your request. Please try again later.", thread_ts=thread_ts)
94119

95120

96121
def get_bedrock_knowledgebase_response(user_query):
97122
"""
98-
Get and return the Bedrock Knowledge Base ReteriveAndGenerate response.
123+
Get and return the Bedrock Knowledge Base RetrieveAndGenerate response.
99124
Do all init tasks here instead of globally as initial invocation of this lambda
100125
provides Slack required ack in 3 sec. It doesn't trigger any bedrock functions and is
101126
time sensitive.
@@ -130,16 +155,28 @@ def get_bedrock_knowledgebase_response(user_query):
130155
return response
131156

132157

133-
# Init the Slack Slash '/' command handler.
134-
app.command(SLACK_SLASH_COMMAND)(
135-
ack=respond_to_slack_within_3_seconds,
136-
lazy=[process_command_request],
137-
)
158+
# Handle @mentions in channels and DMs
159+
@app.event("app_mention")
160+
def handle_app_mention(message, say):
161+
"""Handle when the bot is @mentioned"""
162+
respond_to_mention_within_3_seconds(message, say)
163+
# Process the actual request asynchronously
164+
process_mention_request(message, say)
165+
166+
167+
# Handle direct messages
168+
@app.event("message")
169+
def handle_direct_message(message, say):
170+
"""Handle direct messages to the bot"""
171+
# Only respond to direct messages (not channel messages)
172+
if message.get("channel_type") == "im":
173+
respond_to_mention_within_3_seconds(message, say)
174+
process_mention_request(message, say)
138175

139176

140177
# Lambda handler method.
141178
@logger.inject_lambda_context
142179
def handler(event: dict, context: LambdaContext) -> dict:
143-
logger.info(f"Lambda invoked for {SLACK_SLASH_COMMAND}", extra={"event": event})
180+
logger.info("Lambda invoked for Slack bot", extra={"event": event})
144181
slack_handler = SlackRequestHandler(app=app)
145182
return slack_handler.handle(event, context)

0 commit comments

Comments
 (0)