Skip to content

Commit 39e08c2

Browse files
committed
Use Slack Web API to process async Slack events
1 parent c2bc24c commit 39e08c2

File tree

1 file changed

+67
-68
lines changed
  • packages/slackBotFunction

1 file changed

+67
-68
lines changed

packages/slackBotFunction/app.py

Lines changed: 67 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import os
2+
import re
23
import json
34
import boto3
45
from slack_bolt import App
56
from slack_bolt.adapter.aws_lambda import SlackRequestHandler
7+
from slack_sdk import WebClient
68
from aws_lambda_powertools import Logger
79
from aws_lambda_powertools.utilities.parameters import get_parameter
810
from aws_lambda_powertools.utilities.typing import LambdaContext
911

10-
# In-memory cache for processed events (Lambda container reuse)
12+
# Simple deduplication cache for edge case retries
1113
processed_events = set()
12-
MAX_PROCESSED_EVENTS = 1000 # Prevent memory growth
1314

1415
# Initialize Powertools Logger
1516
logger = Logger(service="slackBotFunction")
@@ -52,72 +53,27 @@ def log_request(slack_logger, body, next):
5253
return next()
5354

5455

55-
def manage_processed_events_cache():
56+
def trigger_async_processing(event_data, context):
5657
"""
57-
Manage the size of processed_events cache to prevent memory issues.
58+
Trigger async processing of the Slack event to avoid timeout issues.
5859
"""
59-
global processed_events
60-
if len(processed_events) > MAX_PROCESSED_EVENTS:
61-
# Keep only the most recent half to prevent memory growth
62-
processed_events = set(list(processed_events)[-MAX_PROCESSED_EVENTS // 2 :])
63-
logger.info(f"Cleaned processed_events cache, now has {len(processed_events)} items")
64-
65-
66-
def process_mention_request(event, say, event_id):
67-
"""
68-
Process the @mention user query and proxy the query to Bedrock Knowledge base RetrieveAndGenerate API
69-
and return the response to Slack to be presented in the thread.
70-
"""
71-
try:
72-
# Extract the user's query, removing the bot mention
73-
raw_text = event["text"]
74-
user_id = event["user"]
75-
thread_ts = event.get("thread_ts", event["ts"]) # Use thread_ts if in thread, otherwise use message ts
76-
77-
# Remove bot mention from the text to get clean query
78-
# Bot mentions come in format <@U1234567890> or <@U1234567890|botname>
79-
import re
80-
81-
user_query = re.sub(r"<@[UW][A-Z0-9]+(\|[^>]+)?>", "", raw_text).strip()
82-
83-
logger.info(
84-
f"Processing @mention from user {user_id}",
85-
extra={"user_query": user_query, "thread_ts": thread_ts, "event_id": event_id},
86-
)
87-
88-
if not user_query:
89-
say(
90-
text="Hi there! Please ask me a question and I'll help you find information from our knowledge base.",
91-
thread_ts=thread_ts,
92-
)
93-
return
94-
95-
kb_response = get_bedrock_knowledgebase_response(user_query)
96-
response_text = kb_response["output"]["text"]
97-
98-
# Reply in thread with the response
99-
say(text=response_text, thread_ts=thread_ts)
100-
101-
except Exception as err:
102-
logger.error(f"Error processing @mention: {err}", extra={"event_id": event_id})
103-
thread_ts = event.get("thread_ts", event.get("ts"))
104-
say(text="Sorry, an error occurred while processing your request. Please try again later.", thread_ts=thread_ts)
60+
lambda_client = boto3.client("lambda")
61+
lambda_client.invoke(
62+
FunctionName=context.function_name,
63+
InvocationType="Event",
64+
Payload=json.dumps({"async_processing": True, "slack_event": event_data}),
65+
)
10566

10667

10768
def get_bedrock_knowledgebase_response(user_query):
10869
"""
10970
Get and return the Bedrock Knowledge Base RetrieveAndGenerate response.
110-
Do all init tasks here instead of globally as initial invocation of this lambda
111-
provides Slack required ack in 3 sec. It doesn't trigger any bedrock functions and is
112-
time sensitive.
11371
"""
114-
# Initialise the bedrock-runtime client (in default / running region).
11572
client = boto3.client(
11673
service_name="bedrock-agent-runtime",
11774
region_name=AWS_REGION,
11875
)
11976

120-
# Create the RetrieveAndGenerateCommand input with the user query.
12177
query_input = {
12278
"text": user_query,
12379
}
@@ -141,11 +97,53 @@ def get_bedrock_knowledgebase_response(user_query):
14197
return response
14298

14399

100+
def process_async_slack_event(slack_event_data):
101+
"""Process Slack event asynchronously"""
102+
event = slack_event_data["event"]
103+
event_id = slack_event_data["event_id"]
104+
token = slack_event_data["bot_token"]
105+
106+
client = WebClient(token=token)
107+
108+
try:
109+
raw_text = event["text"]
110+
user_id = event["user"]
111+
channel = event["channel"]
112+
thread_ts = event.get("thread_ts", event["ts"])
113+
114+
user_query = re.sub(r"<@[UW][A-Z0-9]+(\|[^>]+)?>", "", raw_text).strip()
115+
116+
logger.info(
117+
f"Processing async @mention from user {user_id}",
118+
extra={"user_query": user_query, "thread_ts": thread_ts, "event_id": event_id},
119+
)
120+
121+
if not user_query:
122+
client.chat_postMessage(
123+
channel=channel,
124+
text="Hi there! Please ask me a question and I'll help you find information from our knowledge base.",
125+
thread_ts=thread_ts,
126+
)
127+
return
128+
129+
kb_response = get_bedrock_knowledgebase_response(user_query)
130+
response_text = kb_response["output"]["text"]
131+
132+
client.chat_postMessage(channel=channel, text=response_text, thread_ts=thread_ts)
133+
134+
except Exception as err:
135+
logger.error(f"Error processing async @mention: {err}", extra={"event_id": event_id})
136+
client.chat_postMessage(
137+
channel=channel,
138+
text="Sorry, an error occurred while processing your request. Please try again later.",
139+
thread_ts=thread_ts,
140+
)
141+
142+
144143
# Handle @mentions in channels and DMs
145144
@app.event("app_mention")
146-
def handle_app_mention(event, say, ack, body):
145+
def handle_app_mention(event, say, ack, body, context):
147146
"""Handle when the bot is @mentioned"""
148-
# Use the official event_id from Slack for deduplication
149147
event_id = body.get("event_id")
150148

151149
# Check if we've already processed this event
@@ -154,9 +152,8 @@ def handle_app_mention(event, say, ack, body):
154152
ack()
155153
return
156154

157-
# Mark event as processed and manage cache size
155+
# Mark event as processed
158156
processed_events.add(event_id)
159-
manage_processed_events_cache()
160157

161158
# Acknowledge immediately to prevent Slack retries
162159
ack()
@@ -165,17 +162,16 @@ def handle_app_mention(event, say, ack, body):
165162
user_id = event.get("user", "unknown")
166163
logger.info(f"Acknowledged @mention from user {user_id}", extra={"event_id": event_id})
167164

168-
# Process the actual request
169-
process_mention_request(event, say, event_id)
165+
# Trigger async processing
166+
trigger_async_processing({"event": event, "event_id": event_id, "bot_token": bot_token}, context)
170167

171168

172169
# Handle direct messages
173170
@app.event("message")
174-
def handle_direct_message(event, say, ack, body):
171+
def handle_direct_message(event, say, ack, body, context):
175172
"""Handle direct messages to the bot"""
176173
# Only respond to direct messages (not channel messages)
177174
if event.get("channel_type") == "im":
178-
# Use the official event_id from Slack for deduplication
179175
event_id = body.get("event_id")
180176

181177
# Check if we've already processed this event
@@ -184,9 +180,8 @@ def handle_direct_message(event, say, ack, body):
184180
ack()
185181
return
186182

187-
# Mark event as processed and manage cache size
183+
# Mark event as processed
188184
processed_events.add(event_id)
189-
manage_processed_events_cache()
190185

191186
# Acknowledge immediately to prevent Slack retries
192187
ack()
@@ -195,13 +190,17 @@ def handle_direct_message(event, say, ack, body):
195190
user_id = event.get("user", "unknown")
196191
logger.info(f"Acknowledged DM from user {user_id}", extra={"event_id": event_id})
197192

198-
# Process the actual request
199-
process_mention_request(event, say, event_id)
193+
# Trigger async processing
194+
trigger_async_processing({"event": event, "event_id": event_id, "bot_token": bot_token}, context)
200195

201196

202-
# Lambda handler method.
203197
@logger.inject_lambda_context
204198
def handler(event: dict, context: LambdaContext) -> dict:
205199
logger.info("Lambda invoked for Slack bot", extra={"event": event})
200+
201+
if event.get("async_processing"):
202+
process_async_slack_event(event["slack_event"])
203+
return {"statusCode": 200}
204+
206205
slack_handler = SlackRequestHandler(app=app)
207206
return slack_handler.handle(event, context)

0 commit comments

Comments
 (0)