11import os
2+ import re
23import json
34import boto3
45from slack_bolt import App
56from slack_bolt .adapter .aws_lambda import SlackRequestHandler
7+ from slack_sdk import WebClient
68from aws_lambda_powertools import Logger
79from aws_lambda_powertools .utilities .parameters import get_parameter
810from 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
1113processed_events = set ()
12- MAX_PROCESSED_EVENTS = 1000 # Prevent memory growth
1314
1415# Initialize Powertools Logger
1516logger = 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
10768def 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
204198def 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