12
12
import logging
13
13
import boto3
14
14
import json
15
- import base64
16
15
import requests
17
16
import re
18
17
@@ -72,7 +71,7 @@ def ask_bedrock_llm_with_knowledge_base(flat_conversation, knowledge_base_id) ->
72
71
region_name = model_region_name
73
72
)
74
73
75
- # uses embedding model to retrieve and generate a response
74
+ # Uses model to retrieve related vectors from knowledge base
76
75
response = bedrock_agent_runtime_client .retrieve (
77
76
retrievalQuery = {
78
77
'text' : flat_conversation
@@ -175,8 +174,48 @@ def register_slack_app(token, signing_secret):
175
174
return app , registered_bot_id
176
175
177
176
178
- # Function to handle ai request input and response
179
- def ai_request (bedrock_client , messages ):
177
+ # Receives the streaming response and updates the slack message, chunk by chunk
178
+ def response_on_slack (client , streaming_response , initial_response , channel_id , thread_ts ):
179
+
180
+ # Print streaming response
181
+ if os .environ .get ("VERA_DEBUG" , "False" ) == "True" :
182
+ print ("🚀 Streaming response:" , streaming_response ["stream" ])
183
+
184
+ # Counter and buffer vars for streaming response
185
+ response = ""
186
+ token_counter = 0
187
+ buffer = ""
188
+
189
+ # Iterate over streamed chunks
190
+ for chunk in streaming_response ["stream" ]:
191
+ if "contentBlockDelta" in chunk :
192
+ text = chunk ["contentBlockDelta" ]["delta" ]["text" ]
193
+ response += text
194
+ buffer += text
195
+ token_counter += 1
196
+
197
+ if token_counter >= 10 :
198
+ client .chat_update (
199
+ text = response ,
200
+ channel = channel_id ,
201
+ ts = initial_response ['ts' ]
202
+ )
203
+ # Every time we update to slack, we zero out the token counter and buffer
204
+ token_counter = 0
205
+ buffer = ""
206
+
207
+ # If buffer contains anything after iterating over any chunks, add it also
208
+ # This completes the update
209
+ if buffer :
210
+ client .chat_update (
211
+ text = response ,
212
+ channel = channel_id ,
213
+ ts = initial_response ['ts' ]
214
+ )
215
+
216
+
217
+ # Handle ai request input and response
218
+ def ai_request (bedrock_client , messages , say , thread_ts , client , initial_response , channel_id ):
180
219
181
220
# Format model system prompt for the request
182
221
model_prompt = [
@@ -194,13 +233,13 @@ def ai_request(bedrock_client, messages):
194
233
additional_model_fields = {
195
234
"top_k" : top_k
196
235
}
197
-
236
+
198
237
# If enable_guardrails is set to True, include guardrailIdentifier and guardrailVersion in the request
199
238
if enable_guardrails :
200
239
201
240
# Try to make the request
202
241
try :
203
- response = bedrock_client .converse (
242
+ streaming_response = bedrock_client .converse_stream (
204
243
modelId = model_id ,
205
244
guardrailConfig = {
206
245
"guardrailIdentifier" : guardrailIdentifier ,
@@ -211,31 +250,38 @@ def ai_request(bedrock_client, messages):
211
250
inferenceConfig = inference_config ,
212
251
additionalModelRequestFields = additional_model_fields
213
252
)
214
- # Find the response text
215
- response = response ["output" ]["message" ]["content" ][0 ]["text" ]
253
+
254
+ # Call function to respond on slack
255
+ response_on_slack (client , streaming_response , initial_response , channel_id , thread_ts )
256
+
216
257
except Exception as error :
217
258
# If the request fails, print the error
218
259
print (f"🚀 Error making request to Bedrock: { error } " )
219
260
220
261
# Clean up error message, grab everything after the first :
221
- error = str (error ).split (":" , 1 )[ 1 ]
262
+ error = str (error ).split (":" )[ - 1 ]. strip ()
222
263
223
264
# Return error as response
224
- response = "😔 Error with request: " + str (error )
265
+ say (
266
+ text = "😔 Error with request: " + str (error ),
267
+ thread_ts = thread_ts ,
268
+ )
225
269
226
- # If enable_guardrails is set to False, do not include guardrailIdentifier and guardrailVersion in the request
270
+ # If enable_guardrails is set to False, do not include guardrailIdentifier and guardrailVersion in the request
227
271
else :
228
272
# Try to make the request
229
273
try :
230
- response = bedrock_client .converse (
274
+ streaming_response = bedrock_client .converse_stream (
231
275
modelId = model_id ,
232
276
messages = messages ,
233
277
system = model_prompt ,
234
278
inferenceConfig = inference_config ,
235
279
additionalModelRequestFields = additional_model_fields
236
280
)
237
- # Find the response text
238
- response = response ["output" ]["message" ]["content" ][0 ]["text" ]
281
+
282
+ # Respond on slack
283
+ response_on_slack (client , streaming_response , initial_response , channel_id , thread_ts )
284
+
239
285
except Exception as error :
240
286
# If the request fails, print the error
241
287
print (f"🚀 Error making request to Bedrock: { error } " )
@@ -244,10 +290,10 @@ def ai_request(bedrock_client, messages):
244
290
error = str (error ).split (":" , 1 )[1 ]
245
291
246
292
# Return error as response
247
- response = "😔 Error with request: " + str ( error )
248
-
249
- # Return the response
250
- return response
293
+ say (
294
+ text = "😔 Error with request: " + str ( error ),
295
+ thread_ts = thread_ts ,
296
+ )
251
297
252
298
253
299
# Check for duplicate events
@@ -258,11 +304,16 @@ def check_for_duplicate_event(headers, payload):
258
304
print ("🚀 Headers:" , headers )
259
305
print ("🚀 Payload:" , payload )
260
306
307
+ # Checking for webhook when we edit our own message, which happens all the time with streaming tokens
308
+ if payload .get ("event" , {}).get ("subtype" ) == "message_changed" :
309
+ print ("Detected a message changed event, discarding" )
310
+ logging .info ("Detected a message changed event, discarding" )
311
+ return True
312
+
261
313
# Check headers, if x-slack-retry-num is present, this is a re-send
262
314
# Really we should be doing async lambda model, but for now detecting resends and exiting
263
315
if "x-slack-retry-num" in headers :
264
- print ("❌ Detected a re-send, exiting" )
265
- logging .info ("❌ Detected a re-send, exiting" )
316
+ print ("Detected a Slack re-try, exiting" )
266
317
return True
267
318
268
319
# Check if edited message in local development
@@ -505,7 +556,7 @@ def build_conversation_content(payload, token):
505
556
# Common function to handle both DMs and app mentions
506
557
def handle_message_event (client , body , say , bedrock_client , app , token , registered_bot_id ):
507
558
508
- #user_id = body["event"]["user "]
559
+ channel_id = body ["event" ]["channel " ]
509
560
event = body ["event" ]
510
561
511
562
# Determine the thread timestamp
@@ -605,6 +656,12 @@ def handle_message_event(client, body, say, bedrock_client, app, token, register
605
656
if enable_knowledge_base :
606
657
print ("🚀 Knowledge base enabled, fetching citations" )
607
658
659
+ # Respond to the user that we're fetching citations
660
+ initial_response = say (
661
+ text = f"Checking the knowledge base :waiting:" ,
662
+ thread_ts = thread_ts ,
663
+ )
664
+
608
665
if os .environ .get ("VERA_DEBUG" , "False" ) == "True" :
609
666
print ("🚀 State of conversation before AI request:" , conversation )
610
667
@@ -623,7 +680,22 @@ def handle_message_event(client, body, say, bedrock_client, app, token, register
623
680
print (f"🚀 Flat conversation: { flat_conversation } " )
624
681
625
682
# Get context data from the knowledge base
626
- knowledge_base_response = ask_bedrock_llm_with_knowledge_base (flat_conversation , ConfluenceKnowledgeBaseId )
683
+ try :
684
+ knowledge_base_response = ask_bedrock_llm_with_knowledge_base (flat_conversation , ConfluenceKnowledgeBaseId )
685
+ except Exception as error :
686
+ # If the request fails, print the error
687
+ print (f"🚀 Error making request to Bedrock: { error } " )
688
+
689
+ # Split the error message at a colon, grab everything after the third colon
690
+ error = str (error ).split (":" , 2 )[- 1 ].strip ()
691
+
692
+ # Return error as response
693
+ client .chat_update (
694
+ text = f"😔 Error fetching from knowledge base: " + error ,
695
+ channel = channel_id ,
696
+ ts = initial_response ['ts' ],
697
+ )
698
+ return
627
699
628
700
if os .environ .get ("VERA_DEBUG" , "False" ) == "True" :
629
701
print (f"🚀 Knowledge base response: { knowledge_base_response } " )
@@ -645,26 +717,23 @@ def handle_message_event(client, body, say, bedrock_client, app, token, register
645
717
}
646
718
)
647
719
720
+ # Update the initial response
721
+ if enable_knowledge_base :
722
+ client .chat_update (
723
+ text = f"Chatting with the AI :waiting:" ,
724
+ channel = channel_id ,
725
+ ts = initial_response ['ts' ],
726
+ )
727
+ else :
728
+ initial_response = say (
729
+ text = f"Chatting with the AI :waiting:" ,
730
+ thread_ts = thread_ts ,
731
+ )
732
+
648
733
# Call the AI model with the conversation
649
734
if os .environ .get ("VERA_DEBUG" , "False" ) == "True" :
650
735
print ("🚀 State of conversation before AI request:" , conversation )
651
- response_text = ai_request (bedrock_client , conversation )
652
-
653
- # Check if unsupported_file_type_found
654
- if unsupported_file_type_found == True :
655
- # If true, prepend error to response text
656
- response_text = f"> `Error`: Unsupported file type found, please ensure you are sending a supported file type. Supported file types are: images (png, jpeg, gif, webp).\n {
657
- response_text } "
658
-
659
- if os .environ .get ("VERA_DEBUG" , "False" ) == "True" :
660
- print ("🚀 Response text after adding errors:" , response_text )
661
-
662
- # Return response in the thread
663
- say (
664
- # text=f"Oh hi <@{user_id}>!\n\n{response_text}",
665
- text = f"{ response_text } " ,
666
- thread_ts = thread_ts ,
667
- )
736
+ ai_request (bedrock_client , conversation , say , thread_ts , client , initial_response , channel_id )
668
737
669
738
# Print success
670
739
print ("🚀 Successfully responded to message, exiting" )
0 commit comments