From 66b5404ae167795dcb2b6938fd31bd9c6eb6e912 Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Wed, 24 Sep 2025 13:46:17 -0400 Subject: [PATCH 1/5] feat: feedback block --- listeners/__init__.py | 2 + listeners/assistant/assistant.py | 69 +++++++++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/listeners/__init__.py b/listeners/__init__.py index bf9d7a2..b5f00a5 100644 --- a/listeners/__init__.py +++ b/listeners/__init__.py @@ -1,9 +1,11 @@ from listeners.assistant import assistant +from listeners.assistant.assistant import handle_feedback def register_listeners(app): # Using assistant middleware is the recommended way. app.assistant(assistant) + app.action("feedback")(handle_feedback) # The following event listeners demonstrate how to implement the same on your own. # from listeners import events diff --git a/listeners/assistant/assistant.py b/listeners/assistant/assistant.py index 1e37bda..70eb7c0 100644 --- a/listeners/assistant/assistant.py +++ b/listeners/assistant/assistant.py @@ -1,5 +1,5 @@ import logging -from typing import List, Dict +from typing import Any, List, Dict from slack_bolt import Assistant, BoltContext, Say, SetSuggestedPrompts from slack_bolt.context.get_thread_context import GetThreadContext from slack_sdk import WebClient @@ -10,6 +10,35 @@ assistant = Assistant() +def create_feedback_block(user_id: str) -> Dict[str, Any]: + """ + Create feedback block with thumbs up/down buttons + + Args: + user_id: User ID for user-specific controls + + Returns: + Block Kit context_actions block + """ + elements = [ + { + "type": "feedback_buttons", + "action_id": "feedback", + "positive_button": { + "text": {"type": "plain_text", "text": "Good Response"}, + "accessibility_label": "Submit positive feedback on this response", + "value": "good-feedback", + }, + "negative_button": { + "text": {"type": "plain_text", "text": "Bad Response"}, + "accessibility_label": "Submit negative feedback on this response", + "value": "bad-feedback", + }, + } + ] + return [{"type": "context_actions", "elements": elements}] + + # This listener is invoked when a human user opened an assistant thread @assistant.thread_started def start_assistant_thread( @@ -60,6 +89,7 @@ def respond_in_assistant_thread( say: Say, ): try: + user_id = payload["user"] channel_id = payload["channel"] thread_ts = payload["thread_ts"] @@ -97,12 +127,39 @@ def respond_in_assistant_thread( client.chat_appendStream(channel=channel_id, ts=stream_ts, markdown_text=f"{event.delta}") else: continue - - client.chat_stopStream( - channel=channel_id, - ts=stream_ts, - ) + feedback_block = create_feedback_block(user_id=user_id) + client.chat_stopStream(channel=channel_id, ts=stream_ts, blocks=feedback_block) except Exception as e: logger.exception(f"Failed to handle a user message event: {e}") say(f":warning: Something went wrong! ({e})") + + +# Handle feedback buttons (thumbs up/down) +def handle_feedback(ack, body, client, logger): + ack() + + try: + message_ts = body["message"]["ts"] + channel_id = body["channel"]["id"] + feedback_type = body["actions"][0]["value"] + is_positive = feedback_type == "good-feedback" + + if is_positive: + client.chat_postEphemeral( + channel=channel_id, + user=body["user"]["id"], + thread_ts=message_ts, + text="We're glad you found this useful.", + ) + else: + client.chat_postEphemeral( + channel=channel_id, + user=body["user"]["id"], + thread_ts=message_ts, + text="Sorry to hear that response wasn't up to par :slightly_frowning_face: Starting a new chat may help with AI mistakes and hallucinations.", + ) + + logger.debug(f"Handled feedback: type={feedback_type}, message_ts={message_ts}") + except Exception as error: + logger.error(f"Error handling feedback action: {error}") From bd2e8374caf3637f144ee5eedacc244e9cbd31dd Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Wed, 24 Sep 2025 15:17:08 -0400 Subject: [PATCH 2/5] docs: update README --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b03ebb5..91f57b0 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ pip install -r requirements.txt python3 app.py ``` +Start talking to the bot! Start a new DM or thread and click the feedback button when it responds. + #### Linting ```zsh # Run flake8 from root directory for linting @@ -74,8 +76,8 @@ black . Every incoming request is routed to a "listener". This directory groups each listener based on the Slack Platform feature used, so `/listeners/events` handles incoming events, `/listeners/shortcuts` would handle incoming [Shortcuts](https://docs.slack.dev/interactivity/implementing-shortcuts/) requests, and so on. -:::info[The `listeners/events` folder is purely educational and demonstrates alternative approaches to implementation] -These listeners are **not registered** and are not used in the actual application. For the working implementation, refer to `listeners/assistant.py`. +> [!NOTE] +> The `listeners/events` folder is purely educational and demonstrates alternative approaches to implementation. These listeners are **not registered** and are not used in the actual application. For the working implementation, refer to `listeners/assistant/assistant.py`. **`/listeners/assistant`** From a4309b492a3c33596c0bd47d370bffb099bcc8c3 Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Wed, 24 Sep 2025 18:02:37 -0400 Subject: [PATCH 3/5] feat: update feedback block using FeedbackButtonsElement --- listeners/assistant/assistant.py | 47 ++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/listeners/assistant/assistant.py b/listeners/assistant/assistant.py index 70eb7c0..37755f5 100644 --- a/listeners/assistant/assistant.py +++ b/listeners/assistant/assistant.py @@ -1,8 +1,9 @@ import logging -from typing import Any, List, Dict +from typing import List, Dict from slack_bolt import Assistant, BoltContext, Say, SetSuggestedPrompts from slack_bolt.context.get_thread_context import GetThreadContext from slack_sdk import WebClient +from slack_sdk.models.blocks import FeedbackButtonsElement, FeedbackButtonObject, ContextActionsBlock from ..llm_caller import call_llm @@ -10,7 +11,7 @@ assistant = Assistant() -def create_feedback_block(user_id: str) -> Dict[str, Any]: +def create_feedback_block(user_id: str) -> ContextActionsBlock: """ Create feedback block with thumbs up/down buttons @@ -20,23 +21,26 @@ def create_feedback_block(user_id: str) -> Dict[str, Any]: Returns: Block Kit context_actions block """ - elements = [ - { - "type": "feedback_buttons", - "action_id": "feedback", - "positive_button": { - "text": {"type": "plain_text", "text": "Good Response"}, - "accessibility_label": "Submit positive feedback on this response", - "value": "good-feedback", - }, - "negative_button": { - "text": {"type": "plain_text", "text": "Bad Response"}, - "accessibility_label": "Submit negative feedback on this response", - "value": "bad-feedback", - }, - } + block = [ + ContextActionsBlock( + elements=[ + FeedbackButtonsElement( + action_id="feedback", + positive_button=FeedbackButtonObject( + text="Good Response", + accessibility_label="Submit positive feedback on this response", + value="good-feedback", + ), + negative_button=FeedbackButtonObject( + text="Bad Response", + accessibility_label="Submit negative feedback on this response", + value="bad-feedback", + ), + ) + ] + ) ] - return [{"type": "context_actions", "elements": elements}] + return block # This listener is invoked when a human user opened an assistant thread @@ -113,20 +117,24 @@ def respond_in_assistant_thread( messages_in_thread.append({"role": role, "content": message["text"]}) returned_message = call_llm(messages_in_thread) + client.assistant_threads_setStatus( channel_id=channel_id, thread_ts=thread_ts, status="Bolt is typing", loading_messages=loading_messages ) + stream_response = client.chat_startStream( channel=channel_id, thread_ts=thread_ts, ) stream_ts = stream_response["ts"] + # use of this for loop is specific to openai response method for event in returned_message: if event.type == "response.output_text.delta": client.chat_appendStream(channel=channel_id, ts=stream_ts, markdown_text=f"{event.delta}") else: continue + feedback_block = create_feedback_block(user_id=user_id) client.chat_stopStream(channel=channel_id, ts=stream_ts, blocks=feedback_block) @@ -138,7 +146,6 @@ def respond_in_assistant_thread( # Handle feedback buttons (thumbs up/down) def handle_feedback(ack, body, client, logger): ack() - try: message_ts = body["message"]["ts"] channel_id = body["channel"]["id"] @@ -162,4 +169,4 @@ def handle_feedback(ack, body, client, logger): logger.debug(f"Handled feedback: type={feedback_type}, message_ts={message_ts}") except Exception as error: - logger.error(f"Error handling feedback action: {error}") + logger.error(f":warning: Something went wrong! {error}") From 94e0d29a0cf7938014df5548247546e27ead6f46 Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Thu, 25 Sep 2025 10:35:09 -0400 Subject: [PATCH 4/5] build: bump slack-sdk version to dev3 --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0eec2bf..f73759d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ ---extra-index-url=https://test.pypi.org/simple/ -slack_sdk==3.36.0.dev2 +slack-sdk==3.36.0.dev3 slack-bolt>=1.21,<2 # If you use a different LLM vendor, replace this dependency From 9a2df8d8590646aa508dbd3b7782edff02e5d54f Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Thu, 25 Sep 2025 11:16:43 -0400 Subject: [PATCH 5/5] refactor: move feedback action to standalone path --- listeners/__init__.py | 10 ++++---- listeners/actions/__init__.py | 6 +++++ listeners/actions/actions.py | 30 ++++++++++++++++++++++ listeners/assistant/__init__.py | 5 +++- listeners/assistant/assistant.py | 43 ++++---------------------------- 5 files changed, 50 insertions(+), 44 deletions(-) create mode 100644 listeners/actions/__init__.py create mode 100644 listeners/actions/actions.py diff --git a/listeners/__init__.py b/listeners/__init__.py index b5f00a5..5ca09f3 100644 --- a/listeners/__init__.py +++ b/listeners/__init__.py @@ -1,11 +1,11 @@ -from listeners.assistant import assistant -from listeners.assistant.assistant import handle_feedback +from listeners import actions +from listeners import assistant def register_listeners(app): - # Using assistant middleware is the recommended way. - app.assistant(assistant) - app.action("feedback")(handle_feedback) + + actions.register(app) + assistant.register(app) # The following event listeners demonstrate how to implement the same on your own. # from listeners import events diff --git a/listeners/actions/__init__.py b/listeners/actions/__init__.py new file mode 100644 index 0000000..e42fd04 --- /dev/null +++ b/listeners/actions/__init__.py @@ -0,0 +1,6 @@ +from slack_bolt import App +from .actions import handle_feedback + + +def register(app: App): + app.action("feedback")(handle_feedback) diff --git a/listeners/actions/actions.py b/listeners/actions/actions.py new file mode 100644 index 0000000..8dd151c --- /dev/null +++ b/listeners/actions/actions.py @@ -0,0 +1,30 @@ +import logging + + +# Handle feedback buttons (thumbs up/down) +def handle_feedback(ack, body, client, logger: logging.Logger): + try: + ack() + message_ts = body["message"]["ts"] + channel_id = body["channel"]["id"] + feedback_type = body["actions"][0]["value"] + is_positive = feedback_type == "good-feedback" + + if is_positive: + client.chat_postEphemeral( + channel=channel_id, + user=body["user"]["id"], + thread_ts=message_ts, + text="We're glad you found this useful.", + ) + else: + client.chat_postEphemeral( + channel=channel_id, + user=body["user"]["id"], + thread_ts=message_ts, + text="Sorry to hear that response wasn't up to par :slightly_frowning_face: Starting a new chat may help with AI mistakes and hallucinations.", + ) + + logger.debug(f"Handled feedback: type={feedback_type}, message_ts={message_ts}") + except Exception as error: + logger.error(f":warning: Something went wrong! {error}") diff --git a/listeners/assistant/__init__.py b/listeners/assistant/__init__.py index 1bbba68..7457ba6 100644 --- a/listeners/assistant/__init__.py +++ b/listeners/assistant/__init__.py @@ -1,3 +1,6 @@ +from slack_bolt import App from .assistant import assistant -__all__ = ["assistant"] + +def register(app: App): + app.assistant(assistant) diff --git a/listeners/assistant/assistant.py b/listeners/assistant/assistant.py index 37755f5..52adbb4 100644 --- a/listeners/assistant/assistant.py +++ b/listeners/assistant/assistant.py @@ -3,7 +3,7 @@ from slack_bolt import Assistant, BoltContext, Say, SetSuggestedPrompts from slack_bolt.context.get_thread_context import GetThreadContext from slack_sdk import WebClient -from slack_sdk.models.blocks import FeedbackButtonsElement, FeedbackButtonObject, ContextActionsBlock +from slack_sdk.models.blocks import Block, ContextActionsBlock, FeedbackButtonsElement, FeedbackButtonObject from ..llm_caller import call_llm @@ -11,17 +11,14 @@ assistant = Assistant() -def create_feedback_block(user_id: str) -> ContextActionsBlock: +def create_feedback_block() -> List[Block]: """ Create feedback block with thumbs up/down buttons - Args: - user_id: User ID for user-specific controls - Returns: Block Kit context_actions block """ - block = [ + blocks: List[Block] = [ ContextActionsBlock( elements=[ FeedbackButtonsElement( @@ -40,7 +37,7 @@ def create_feedback_block(user_id: str) -> ContextActionsBlock: ] ) ] - return block + return blocks # This listener is invoked when a human user opened an assistant thread @@ -93,7 +90,6 @@ def respond_in_assistant_thread( say: Say, ): try: - user_id = payload["user"] channel_id = payload["channel"] thread_ts = payload["thread_ts"] @@ -135,38 +131,9 @@ def respond_in_assistant_thread( else: continue - feedback_block = create_feedback_block(user_id=user_id) + feedback_block = create_feedback_block() client.chat_stopStream(channel=channel_id, ts=stream_ts, blocks=feedback_block) except Exception as e: logger.exception(f"Failed to handle a user message event: {e}") say(f":warning: Something went wrong! ({e})") - - -# Handle feedback buttons (thumbs up/down) -def handle_feedback(ack, body, client, logger): - ack() - try: - message_ts = body["message"]["ts"] - channel_id = body["channel"]["id"] - feedback_type = body["actions"][0]["value"] - is_positive = feedback_type == "good-feedback" - - if is_positive: - client.chat_postEphemeral( - channel=channel_id, - user=body["user"]["id"], - thread_ts=message_ts, - text="We're glad you found this useful.", - ) - else: - client.chat_postEphemeral( - channel=channel_id, - user=body["user"]["id"], - thread_ts=message_ts, - text="Sorry to hear that response wasn't up to par :slightly_frowning_face: Starting a new chat may help with AI mistakes and hallucinations.", - ) - - logger.debug(f"Handled feedback: type={feedback_type}, message_ts={message_ts}") - except Exception as error: - logger.error(f":warning: Something went wrong! {error}")