Repository navigation
feat: add app_mention event handler #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
0f19ab1
8752dfc
7db511f
97b43a0
b41054a
9b9b051
38c8883
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,9 @@ | ||
| from listeners import actions | ||
| from listeners import assistant | ||
| from listeners import events | ||
|
|
||
|
|
||
| def register_listeners(app): | ||
|
|
||
| actions.register(app) | ||
| assistant.register(app) | ||
|
|
||
| # The following event listeners demonstrate how to implement the same on your own. | ||
| # from listeners import events | ||
| # events.register(app) | ||
| events.register(app) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,32 +2,37 @@ | |
| # We recommend using assistant middleware instead of these event listeners. | ||
| # For more details, refer to https://tools.slack.dev/bolt-python/concepts/assistant/. | ||
|
|
||
| from typing import Dict, Any | ||
| # from typing import Dict, Any | ||
|
|
||
| from slack_bolt import App | ||
| from slack_bolt.request.payload_utils import is_event | ||
|
|
||
| from .assistant_thread_started import start_thread_with_suggested_prompts | ||
| from .asssistant_thread_context_changed import save_new_thread_context | ||
| from .user_message import respond_to_user_message | ||
| # from slack_bolt.request.payload_utils import is_event | ||
|
|
||
| # from .assistant_thread_started import start_thread_with_suggested_prompts | ||
| # from .asssistant_thread_context_changed import save_new_thread_context | ||
| # from .user_message import respond_to_user_message | ||
| from .assistant_mentioned import assistant_mentioned_callback | ||
|
|
||
|
|
||
| def register(app: App): | ||
| app.event("assistant_thread_started")(start_thread_with_suggested_prompts) | ||
| app.event("assistant_thread_context_changed")(save_new_thread_context) | ||
| app.event("message", matchers=[is_user_message_event_in_assistant_thread])(respond_to_user_message) | ||
| app.event("message", matchers=[is_message_event_in_assistant_thread])(just_ack) | ||
| app.event("app_mention")(assistant_mentioned_callback) | ||
|
|
||
|
|
||
| # app.event("assistant_thread_started")(start_thread_with_suggested_prompts) | ||
| # app.event("assistant_thread_context_changed")(save_new_thread_context) | ||
| # app.event("message", matchers=[is_user_message_event_in_assistant_thread])(respond_to_user_message) | ||
| # app.event("message", matchers=[is_message_event_in_assistant_thread])(just_ack) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👁️🗨️ thought: I'm wondering if we want to move the explicit event listener implementation to the Bolt Python upstream examples instead of commenting this out here? 🔍 ramble: We have a few related
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes i like this idea!
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❓ Do we want to keep this commented out rather than deleted?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i left it commented out so that listeners/event could stay somewhat of an example folder. i can see how it might be confusing to have all of these comments in there though |
||
|
|
||
|
|
||
| def is_message_event_in_assistant_thread(body: Dict[str, Any]) -> bool: | ||
| if is_event(body): | ||
| return body["event"]["type"] == "message" and body["event"].get("channel_type") == "im" | ||
| return False | ||
| # def is_message_event_in_assistant_thread(body: Dict[str, Any]) -> bool: | ||
| # if is_event(body): | ||
| # return body["event"]["type"] == "message" and body["event"].get("channel_type") == "im" | ||
| # return False | ||
|
|
||
|
|
||
| def is_user_message_event_in_assistant_thread(body: Dict[str, Any]) -> bool: | ||
| return is_message_event_in_assistant_thread(body) and body["event"].get("subtype") in (None, "file_share") | ||
| # def is_user_message_event_in_assistant_thread(body: Dict[str, Any]) -> bool: | ||
| # return is_message_event_in_assistant_thread(body) and body["event"].get("subtype") in (None, "file_share") | ||
|
|
||
|
|
||
| def just_ack(): | ||
| pass | ||
| # def just_ack(): | ||
| # pass | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,62 @@ | ||||||
| from logging import Logger | ||||||
| from slack_sdk import WebClient | ||||||
| from slack_bolt import Say | ||||||
| from slack_bolt.context.get_thread_context import GetThreadContext | ||||||
| from typing import List, Dict | ||||||
|
|
||||||
| from ..llm_caller import call_llm | ||||||
| from ..views.feedback_block import create_feedback_block | ||||||
| from ..listeners_constants import loading_messages | ||||||
|
|
||||||
| """ | ||||||
| Handles the event when the app is mentioned in a Slack channel, retrieves the conversation context, | ||||||
| and generates an AI response if text is provided, otherwise sends a default response | ||||||
| """ | ||||||
|
|
||||||
|
|
||||||
| def assistant_mentioned_callback( | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
🤖 quibble: Matching the event might make this clear in lookups without limiting this to an "assistant" paradigm! |
||||||
| client: WebClient, event: dict, get_thread_context: GetThreadContext, logger: Logger, say: Say | ||||||
| ): | ||||||
| try: | ||||||
|
|
||||||
| channel_id = event.get("channel") | ||||||
| thread_ts = event.get("thread_ts") | ||||||
| user_id = event.get("user") | ||||||
| team_id = event.get("team") | ||||||
| text = event.get("text") | ||||||
|
|
||||||
| if thread_ts: | ||||||
| conversation_context = client.conversations_replies(channel=channel_id, ts=thread_ts, limit=10) | ||||||
| else: | ||||||
| conversation_context = client.conversations_history(channel=channel_id, limit=50) | ||||||
| thread_ts = event["ts"] | ||||||
|
|
||||||
| messages_in_thread: List[Dict[str, str]] = [] | ||||||
| for message in conversation_context["messages"]: | ||||||
| role = "user" if message.get("bot_id") is None else "assistant" | ||||||
| messages_in_thread.append({"role": role, "content": message["text"]}) | ||||||
| if 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, recipient_team_id=team_id, recipient_user_id=user_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() | ||||||
| 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})") | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| # This file defines default loading messages used by the Slack bot. | ||
| # Used in `assistant_mentioned.py` and `assistant.py`. | ||
|
|
||
|
|
||
| loading_messages = [ | ||
| "Teaching the hamsters to type faster…", | ||
| "Untangling the internet cables…", | ||
| "Consulting the office goldfish…", | ||
| "Polishing up the response just for you…", | ||
| "Convincing the AI to stop overthinking…", | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| from typing import List | ||
| from slack_sdk.models.blocks import Block, ContextActionsBlock, FeedbackButtonsElement, FeedbackButtonObject | ||
|
|
||
|
|
||
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,12 +20,15 @@ | |
| "oauth_config": { | ||
| "scopes": { | ||
| "bot": [ | ||
| "app_mentions:read", | ||
| "assistant:write", | ||
| "channels:join", | ||
| "im:history", | ||
| "channels:history", | ||
| "channels:read", | ||
| "groups:history", | ||
| "chat:write" | ||
| "chat:write", | ||
| "users:read" | ||
| ] | ||
| } | ||
| }, | ||
|
|
@@ -34,7 +37,9 @@ | |
| "bot_events": [ | ||
| "assistant_thread_context_changed", | ||
| "assistant_thread_started", | ||
| "message.im" | ||
| "message.im", | ||
| "app_mention", | ||
| "message.channels" | ||
|
Comment on lines
+40
to
+42
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧮 quibble: Hm perhaps in follow ups we revist the scopes and ordering here? I believe this makes inspecting the app a bit more simple at first glance- |
||
| ] | ||
| }, | ||
| "interactivity": { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🪓 suggestion(non-blocking): I think later we should remove this in favor of a search method to showcase other AI focused features?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super +1