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`** diff --git a/listeners/__init__.py b/listeners/__init__.py index bf9d7a2..5ca09f3 100644 --- a/listeners/__init__.py +++ b/listeners/__init__.py @@ -1,9 +1,11 @@ -from listeners.assistant import assistant +from listeners import actions +from listeners import assistant def register_listeners(app): - # Using assistant middleware is the recommended way. - app.assistant(assistant) + + 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 1e37bda..52adbb4 100644 --- a/listeners/assistant/assistant.py +++ b/listeners/assistant/assistant.py @@ -3,6 +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 Block, ContextActionsBlock, FeedbackButtonsElement, FeedbackButtonObject from ..llm_caller import call_llm @@ -10,6 +11,35 @@ assistant = Assistant() +def create_feedback_block() -> List[Block]: + """ + Create feedback block with thumbs up/down buttons + + Returns: + Block Kit context_actions block + """ + blocks: List[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 blocks + + # This listener is invoked when a human user opened an assistant thread @assistant.thread_started def start_assistant_thread( @@ -83,14 +113,17 @@ 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": @@ -98,10 +131,8 @@ def respond_in_assistant_thread( else: continue - client.chat_stopStream( - channel=channel_id, - ts=stream_ts, - ) + 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}") 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