Skip to content

Commit 8018e71

Browse files
committed
Refactor store_feedback_with_qa function to reduce its cognitive complexity
1 parent 47c7690 commit 8018e71

File tree

1 file changed

+49
-38
lines changed

1 file changed

+49
-38
lines changed

packages/slackBotFunction/app/slack/slack_events.py

Lines changed: 49 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
239269
def 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

Comments
 (0)