-
Notifications
You must be signed in to change notification settings - Fork 46
feat: assistant class #84
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
base: main
Are you sure you want to change the base?
Changes from all commits
85c0bdc
b3d9a59
f69ad05
162ed91
a55c3c4
c68a9a9
b602aa3
342c10d
a20d5e1
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 |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| from .assistant import assistant | ||
|
|
||
|
|
||
| def register(app): | ||
| # Using assistant middleware is the recommended way. | ||
| app.assistant(assistant) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| import logging | ||
| from typing import List, Dict | ||
| from slack_bolt import Assistant, BoltContext, Say, SetSuggestedPrompts, SetStatus | ||
| from slack_bolt.context.get_thread_context import GetThreadContext | ||
| from slack_sdk import WebClient | ||
| from slack_sdk.errors import SlackApiError | ||
|
|
||
| from ai.providers import get_provider_response | ||
|
|
||
| # Refer to https://tools.slack.dev/bolt-python/concepts/assistant/ for more details | ||
| assistant = Assistant() | ||
|
|
||
|
|
||
| # This listener is invoked when a human user opened an assistant thread | ||
| @assistant.thread_started | ||
| def start_assistant_thread( | ||
| say: Say, | ||
| get_thread_context: GetThreadContext, | ||
| set_suggested_prompts: SetSuggestedPrompts, | ||
| logger: logging.Logger, | ||
| ): | ||
| try: | ||
| say("How can I help you?") | ||
|
|
||
| prompts: List[Dict[str, str]] = [ | ||
| { | ||
| "title": "What does Slack stand for?", | ||
| "message": "Slack, a business communication service, was named after an acronym. Can you guess what it stands for?", | ||
| }, | ||
| { | ||
| "title": "Write a draft announcement", | ||
| "message": "Can you write a draft announcement about a new feature my team just released? It must include how impactful it is.", | ||
| }, | ||
| { | ||
| "title": "Suggest names for my Slack app", | ||
| "message": "Can you suggest a few names for my Slack app? The app helps my teammates better organize information and plan priorities and action items.", | ||
| }, | ||
| ] | ||
|
Comment on lines
+25
to
+38
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. ⭐ praise: Nice alignment with the assistant template - it makes me wonder if we wonder how maintenance and updates of both projects might continue going forward... |
||
|
|
||
| thread_context = get_thread_context() | ||
| if thread_context is not None and thread_context.channel_id is not None: | ||
| summarize_channel = { | ||
| "title": "Summarize the referred channel", | ||
| "message": "Can you generate a brief summary of the referred channel?", | ||
| } | ||
| prompts.append(summarize_channel) | ||
|
|
||
| set_suggested_prompts(prompts=prompts) | ||
| except Exception as e: | ||
| logger.exception(f"Failed to handle an assistant_thread_started event: {e}", e) | ||
| say(f":warning: Received an error from Bolty:\n{e}") | ||
|
|
||
|
|
||
| # This listener is invoked when the human user sends a reply in the assistant thread | ||
| @assistant.user_message | ||
| def respond_in_assistant_thread( | ||
| payload: dict, | ||
| logger: logging.Logger, | ||
| context: BoltContext, | ||
| set_status: SetStatus, | ||
| get_thread_context: GetThreadContext, | ||
| client: WebClient, | ||
| say: Say, | ||
| ): | ||
| try: | ||
| user_id = context.user_id | ||
| user_message = payload["text"] | ||
| set_status("is typing...") | ||
|
|
||
| if user_message == "Can you generate a brief summary of the referred channel?": | ||
| # the logic here requires the additional bot scopes: | ||
| # channels:join, channels:history, groups:history | ||
| thread_context = get_thread_context() | ||
| referred_channel_id = thread_context.get("channel_id") | ||
| try: | ||
| channel_history = client.conversations_history( | ||
| channel=referred_channel_id, limit=50 | ||
| ) | ||
| except SlackApiError as e: | ||
| if e.response["error"] == "not_in_channel": | ||
| # If this app's bot user is not in the public channel, | ||
| # we'll try joining the channel and then calling the same API again | ||
| client.conversations_join(channel=referred_channel_id) | ||
|
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. 🤖 suggestion: The
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. thanks for catching this! |
||
| channel_history = client.conversations_history( | ||
| channel=referred_channel_id, limit=50 | ||
| ) | ||
| else: | ||
| raise e | ||
|
|
||
| prompt = f"Can you generate a brief summary of these messages in a Slack channel <#{referred_channel_id}>?\n\n" | ||
| for message in reversed(channel_history.get("messages")): | ||
| if message.get("user") is not None: | ||
| prompt += f"\n<@{message['user']}> says: {message['text']}\n" | ||
| messages_in_thread = [{"role": "user", "content": prompt}] | ||
| returned_message = get_provider_response(user_id, messages_in_thread) | ||
| say(returned_message) | ||
| return | ||
|
|
||
| replies = client.conversations_replies( | ||
| channel=context.channel_id, | ||
| ts=context.thread_ts, | ||
| oldest=context.thread_ts, | ||
| limit=10, | ||
| ) | ||
| messages_in_thread: List[Dict[str, str]] = [] | ||
| for message in replies["messages"]: | ||
| role = "user" if message.get("bot_id") is None else "assistant" | ||
| messages_in_thread.append({"role": role, "content": message["text"]}) | ||
| returned_message = get_provider_response(user_id, messages_in_thread) | ||
| say(returned_message) | ||
|
|
||
| except Exception as e: | ||
| logger.exception(f"Failed to handle a user message event: {e}") | ||
| say(f":warning: Received an error from Bolty:\n{e}") | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,13 @@ | |
| from ..listener_utils.parse_conversation import parse_conversation | ||
|
|
||
| """ | ||
| Handles the event when a direct message is sent to the bot, retrieves the conversation context, | ||
| WARNING: This callback is the traditional way of handling direct messages and is only used | ||
| when Slack Assistant features are turned off (when assistant:write scope is removed from manifest.json). | ||
|
|
||
| When assistant:write is enabled in the manifest, messages are handled through the Assistant handlers | ||
| in listeners/assistant/assistant.py instead of this callback. | ||
|
|
||
| Handles the event when a direct message is sent to non-assistant bots, retrieves the conversation context, | ||
| and generates an AI response. | ||
| """ | ||
|
|
||
|
|
@@ -40,5 +46,5 @@ def app_messaged_callback(client: WebClient, event: dict, logger: Logger, say: S | |
| client.chat_update( | ||
| channel=channel_id, | ||
| ts=waiting_message["ts"], | ||
| text=f"Received an error from Bolty:\n{e}", | ||
| text=f":warning: Received an error from Bolty:\n{e}", | ||
|
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. ⭐ praise: This was helpful for me in testing just now! |
||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| slack-bolt==1.24.0 | ||
| pytest | ||
| ruff | ||
| ruff==0.13.0 | ||
| slack-cli-hooks==0.1.0 | ||
| openai==1.102.0 | ||
| anthropic==0.65.0 | ||
|
|
||
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.
🤔 question: I notice debug logs aren't appearing for me... Does the ordering of logger initialization matter for this?
🗣️ thought: We might hold off on such changes for another PR but this
diffcaught my eye for now-