@@ -37,14 +37,15 @@ def cleanup_previous_unfeedback_qa(conversation_key, current_message_ts, session
3737 """Delete previous Q&A pair if no feedback received"""
3838 try :
3939 previous_message_ts = session_data .get ("latest_message_ts" )
40+ # Skip if no previous message or it's the same as current
4041 if not previous_message_ts or previous_message_ts == current_message_ts :
4142 return
4243
4344 # Check if previous Q&A has any feedback
4445 feedback_exists = check_feedback_exists (conversation_key , previous_message_ts )
4546
4647 if not feedback_exists :
47- # Delete unfeedback Q&A pair for privacy
48+ # Delete unfeedback Q&A pair for privacy compliance
4849 previous_qa_key = f"qa#{ conversation_key } #{ previous_message_ts } "
4950 table .delete_item (Key = {"pk" : previous_qa_key , "sk" : "turn" })
5051 logger .info ("Deleted unfeedback Q&A for privacy" , extra = {"message_ts" : previous_message_ts })
@@ -106,7 +107,8 @@ def process_async_slack_event(slack_event_data):
106107 raw_text = event ["text" ]
107108 user_id = event ["user" ]
108109 channel = event ["channel" ]
109- # figure out if this is a DM or channel thread
110+
111+ # Determine conversation context: DM vs channel thread
110112 if event .get ("channel_type" ) == CHANNEL_TYPE_IM :
111113 conversation_key = f"{ DM_PREFIX } { channel } "
112114 context_type = CONTEXT_TYPE_DM
@@ -117,7 +119,7 @@ def process_async_slack_event(slack_event_data):
117119 context_type = CONTEXT_TYPE_THREAD
118120 thread_ts = thread_root
119121
120- # clean up the user's message
122+ # Remove Slack user mentions from message text
121123 user_query = re .sub (r"<@[UW][A-Z0-9]+(\|[^>]+)?>" , "" , raw_text ).strip ()
122124
123125 logger .info (
@@ -134,14 +136,14 @@ def process_async_slack_event(slack_event_data):
134136 )
135137 return
136138
137- # Reformulate query for better RAG retrieval
139+ # Reformulate query for better RAG retrieval using Claude
138140 reformulated_query = reformulate_query (logger , user_query )
139141
140- # check if we have an existing conversation
142+ # Check if we have an existing Bedrock conversation session
141143 session_data = get_conversation_session_data (conversation_key )
142144 session_id = session_data .get ("session_id" ) if session_data else None
143145
144- # Query the knowledge base with reformulated query
146+ # Query Bedrock Knowledge Base with conversation context
145147 kb_response = query_bedrock (reformulated_query , session_id )
146148
147149 response_text = kb_response ["output" ]["text" ]
@@ -154,8 +156,9 @@ def process_async_slack_event(slack_event_data):
154156 )
155157 message_ts = post ["ts" ]
156158
157- # store a new session if we just started a conversation
159+ # Handle conversation session management
158160 if not session_id and "sessionId" in kb_response :
161+ # Store new Bedrock session for conversation continuity
159162 store_conversation_session (
160163 conversation_key ,
161164 kb_response ["sessionId" ],
@@ -165,9 +168,9 @@ def process_async_slack_event(slack_event_data):
165168 message_ts ,
166169 )
167170 elif session_id :
168- # Clean up previous unfeedback Q&A before storing new one
171+ # Clean up previous unfeedback Q&A for privacy compliance
169172 cleanup_previous_unfeedback_qa (conversation_key , message_ts , session_data )
170- # Update existing session with latest message timestamp
173+ # Track latest bot message for feedback validation
171174 update_session_latest_message (conversation_key , message_ts )
172175
173176 # Store Q&A pair for feedback correlation
@@ -180,7 +183,7 @@ def process_async_slack_event(slack_event_data):
180183 user_id = user_id ,
181184 )
182185
183- # Attach feedback buttons via chat_update
186+ # Create compact feedback payload for button actions
184187 feedback_value = json .dumps (
185188 {"ck" : conversation_key , "ch" : channel , "tt" : thread_ts , "mt" : message_ts }, separators = ("," , ":" )
186189 )
@@ -236,6 +239,33 @@ def process_async_slack_event(slack_event_data):
236239 logger .error ("Failed to post error message" , extra = {"error" : str (post_err )})
237240
238241
242+ def _retrieve_qa_data (conversation_key , message_ts , user_query , bot_response ):
243+ """Retrieve Q&A data from DynamoDB if not provided in function call"""
244+ # Return early if we have all data or no message timestamp
245+ if not message_ts or (user_query and bot_response ):
246+ return user_query , bot_response
247+
248+ try :
249+ # Fetch stored Q&A pair from previous interaction
250+ qa_response = table .get_item (Key = {"pk" : f"qa#{ conversation_key } #{ message_ts } " , "sk" : "turn" })
251+ if "Item" in qa_response :
252+ qa_item = qa_response ["Item" ]
253+ return (user_query or qa_item .get ("user_query" ), bot_response or qa_item .get ("bot_response" ))
254+ except Exception as e :
255+ logger .error (f"Error retrieving Q&A data: { e } " )
256+
257+ return user_query , bot_response
258+
259+
260+ def _build_feedback_keys (conversation_key , message_ts , user_id , now ):
261+ """Build DynamoDB primary and sort keys for feedback storage"""
262+ if message_ts :
263+ # Per-message feedback: enables vote deduplication
264+ return (f"{ FEEDBACK_PREFIX_KEY } { conversation_key } #{ message_ts } " , f"{ USER_PREFIX } { user_id } " )
265+ # Conversation-level feedback: fallback for general notes
266+ return (f"{ FEEDBACK_PREFIX_KEY } { conversation_key } " , f"{ USER_PREFIX } { user_id } { NOTE_SUFFIX } { now } " )
267+
268+
239269def store_feedback_with_qa (
240270 conversation_key ,
241271 user_query ,
@@ -251,31 +281,11 @@ def store_feedback_with_qa(
251281 Store user feedback with Q&A context using message_ts linking
252282 """
253283 try :
254- # Use latest_message_ts for both button and text feedback
255- if not message_ts :
256- message_ts = get_latest_message_ts (conversation_key )
257-
258- # Retrieve Q&A data if not provided and we have message_ts
259- if message_ts and (not user_query or not bot_response ):
260- try :
261- qa_response = table .get_item (Key = {"pk" : f"qa#{ conversation_key } #{ message_ts } " , "sk" : "turn" })
262- if "Item" in qa_response :
263- qa_item = qa_response ["Item" ]
264- user_query = user_query or qa_item .get ("user_query" )
265- bot_response = bot_response or qa_item .get ("bot_response" )
266- except Exception as e :
267- logger .error (f"Error retrieving Q&A data: { e } " )
284+ message_ts = message_ts or get_latest_message_ts (conversation_key )
285+ user_query , bot_response = _retrieve_qa_data (conversation_key , message_ts , user_query , bot_response )
268286
269287 now = int (time .time ())
270- ttl = now + TTL_FEEDBACK
271-
272- if message_ts :
273- pk = f"{ FEEDBACK_PREFIX_KEY } { conversation_key } #{ message_ts } "
274- sk = f"{ USER_PREFIX } { user_id } "
275- else :
276- # Fallback if no message_ts available (shouldn't happen in normal flow)
277- pk = f"{ FEEDBACK_PREFIX_KEY } { conversation_key } "
278- sk = f"{ USER_PREFIX } { user_id } { NOTE_SUFFIX } { now } "
288+ pk , sk = _build_feedback_keys (conversation_key , message_ts , user_id , now )
279289
280290 feedback_item = {
281291 "pk" : pk ,
@@ -285,7 +295,7 @@ def store_feedback_with_qa(
285295 "user_id" : user_id ,
286296 "channel_id" : channel_id ,
287297 "created_at" : now ,
288- "ttl" : ttl ,
298+ "ttl" : now + TTL_FEEDBACK ,
289299 "user_query" : user_query [:1000 ] if user_query else None ,
290300 "bot_response" : bot_response [:2000 ] if bot_response else None ,
291301 }
@@ -328,16 +338,17 @@ def store_feedback(
328338 now = int (time .time ())
329339 ttl = now + TTL_FEEDBACK
330340
331- # Use latest_message_ts consistently for both button and text feedback
341+ # Get latest bot message timestamp for feedback linking
332342 if not message_ts :
333343 message_ts = get_latest_message_ts (conversation_key )
334344
335345 if message_ts :
346+ # Per-message feedback with deduplication
336347 pk = f"{ FEEDBACK_PREFIX_KEY } { conversation_key } #{ message_ts } "
337348 sk = f"{ USER_PREFIX } { user_id } "
338- condition = "attribute_not_exists(pk) AND attribute_not_exists(sk)"
349+ condition = "attribute_not_exists(pk) AND attribute_not_exists(sk)" # Prevent double-voting
339350 else :
340- # Fallback if no message_ts available (shouldn't happen in normal flow)
351+ # Fallback for conversation-level feedback
341352 pk = f"{ FEEDBACK_PREFIX_KEY } { conversation_key } "
342353 sk = f"{ USER_PREFIX } { user_id } { NOTE_SUFFIX } { now } "
343354 condition = None
@@ -506,7 +517,7 @@ def query_bedrock(user_query, session_id=None):
506517 },
507518 }
508519
509- # add session if we have one for conversation continuity
520+ # Include session ID for conversation continuity across messages
510521 if session_id :
511522 request_params ["sessionId" ] = session_id
512523 logger .info ("Using existing session" , extra = {"session_id" : session_id })
0 commit comments