From c08721c2fb192abf041419c314e496ac90611838 Mon Sep 17 00:00:00 2001 From: Abhishek Singh <76961066+Abhi2april@users.noreply.github.com> Date: Fri, 28 Feb 2025 20:48:56 +0530 Subject: [PATCH 1/7] Delete listeners/__init__.py --- listeners/__init__.py | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 listeners/__init__.py diff --git a/listeners/__init__.py b/listeners/__init__.py deleted file mode 100644 index 523ce09..0000000 --- a/listeners/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from .assistant import assistant - - -def register_listeners(app): - # Using assistant middleware is the recommended way. - app.assistant(assistant) - - # The following event listeners demonstrate how to implement the same on your own. - # from listeners import events - # events.register(app) From 63955d2bd45ce07b2f6e381a6e55e4ca997e3a5c Mon Sep 17 00:00:00 2001 From: Abhishek Singh <76961066+Abhi2april@users.noreply.github.com> Date: Fri, 28 Feb 2025 20:49:06 +0530 Subject: [PATCH 2/7] Delete listeners/assistant.py --- listeners/assistant.py | 109 ----------------------------------------- 1 file changed, 109 deletions(-) delete mode 100644 listeners/assistant.py diff --git a/listeners/assistant.py b/listeners/assistant.py deleted file mode 100644 index 817030f..0000000 --- a/listeners/assistant.py +++ /dev/null @@ -1,109 +0,0 @@ -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 .llm_caller import call_llm - -# 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.", - }, - ] - - 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: Something went wrong! ({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_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) - 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 = call_llm(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 = call_llm(messages_in_thread) - say(returned_message) - - except Exception as e: - logger.exception(f"Failed to handle a user message event: {e}") - say(f":warning: Something went wrong! ({e})") From 59d24371cddfcd7ae9cd417f5f3f43f91bfe987c Mon Sep 17 00:00:00 2001 From: Abhishek Singh <76961066+Abhi2april@users.noreply.github.com> Date: Fri, 28 Feb 2025 20:49:17 +0530 Subject: [PATCH 3/7] Delete listeners/llm_caller.py --- listeners/llm_caller.py | 59 ----------------------------------------- 1 file changed, 59 deletions(-) delete mode 100644 listeners/llm_caller.py diff --git a/listeners/llm_caller.py b/listeners/llm_caller.py deleted file mode 100644 index 863c0eb..0000000 --- a/listeners/llm_caller.py +++ /dev/null @@ -1,59 +0,0 @@ -import os -import re -from typing import List, Dict - -import openai - -DEFAULT_SYSTEM_CONTENT = """ -You're an assistant in a Slack workspace. -Users in the workspace will ask you to help them write something or to think better about a specific topic. -You'll respond to those questions in a professional way. -When you include markdown text, convert them to Slack compatible ones. -When a prompt has Slack's special syntax like <@USER_ID> or <#CHANNEL_ID>, you must keep them as-is in your response. -""" - - -def call_llm( - messages_in_thread: List[Dict[str, str]], - system_content: str = DEFAULT_SYSTEM_CONTENT, -) -> str: - openai_client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY")) - messages = [{"role": "system", "content": system_content}] - messages.extend(messages_in_thread) - response = openai_client.chat.completions.create( - model="gpt-4o-mini", - n=1, - messages=messages, - max_tokens=16384, - ) - return markdown_to_slack(response.choices[0].message.content) - - -# Conversion from OpenAI markdown to Slack mrkdwn -# See also: https://api.slack.com/reference/surfaces/formatting#basics -def markdown_to_slack(content: str) -> str: - # Split the input string into parts based on code blocks and inline code - parts = re.split(r"(?s)(```.+?```|`[^`\n]+?`)", content) - - # Apply the bold, italic, and strikethrough formatting to text not within code - result = "" - for part in parts: - if part.startswith("```") or part.startswith("`"): - result += part - else: - for o, n in [ - ( - r"\*\*\*(?!\s)([^\*\n]+?)(? Date: Fri, 28 Feb 2025 20:51:09 +0530 Subject: [PATCH 4/7] Add files via upload --- listeners/__init__.py | 5 ++ listeners/assistant.py | 109 ++++++++++++++++++++++++++++++++++++++++ listeners/llm_caller.py | 49 ++++++++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 listeners/__init__.py create mode 100644 listeners/assistant.py create mode 100644 listeners/llm_caller.py diff --git a/listeners/__init__.py b/listeners/__init__.py new file mode 100644 index 0000000..9a239d3 --- /dev/null +++ b/listeners/__init__.py @@ -0,0 +1,5 @@ +from .assistant import assistant + + +def register_listeners(app): + app.assistant(assistant) diff --git a/listeners/assistant.py b/listeners/assistant.py new file mode 100644 index 0000000..e90b7e0 --- /dev/null +++ b/listeners/assistant.py @@ -0,0 +1,109 @@ +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 .llm_caller import call_llm + +# 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.", + }, + ] + + 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: Something went wrong! ({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_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) + 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 = call_llm(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 = call_llm(messages_in_thread) + say(returned_message) + + except Exception as e: + logger.exception(f"Failed to handle a user message event: {e}") + say(f":warning: Something went wrong! ({e})") diff --git a/listeners/llm_caller.py b/listeners/llm_caller.py new file mode 100644 index 0000000..c20414c --- /dev/null +++ b/listeners/llm_caller.py @@ -0,0 +1,49 @@ +import os +from typing import List, Dict +from langchain_groq import ChatGroq + +# Set Groq API Key (consider using environment variables instead of hardcoding) +GROQ_API_KEY = os.getenv("GROQ_API_KEY") +if not GROQ_API_KEY: + raise ValueError("GROQ_API_KEY environment variable is not set") + +DEFAULT_SYSTEM_CONTENT = """ +You are EthicALL, a smart, empathetic AI assistant. When mentioned directly: +1. Respond to general questions while maintaining your compliance focus +2. Politely redirect non-compliance questions to your core purpose +3. Always maintain professional tone +4. Keep responses concise (1-3 paragraphs max) +You are strictly prohibited from answering irrelevant questions. Do only what is your profession. +Your purpose is to protect the organization and its people while maintaining a positive, collaborative environment. +""" + +def call_llm( + messages_in_thread: List[Dict[str, str]], + system_content: str = DEFAULT_SYSTEM_CONTENT, +) -> str: + """ + Call the Groq LLM with the given messages and system content. + + Args: + messages_in_thread: List of message dictionaries with 'role' and 'content' keys + system_content: System message content to set context for the LLM + + Returns: + str: Raw response from the LLM + """ + if not isinstance(messages_in_thread, list): + raise TypeError("messages_in_thread must be a list of dictionaries") + + llm = ChatGroq(model="llama3-8b-8192", groq_api_key=GROQ_API_KEY) + + messages = [{"role": "system", "content": system_content}] + messages.extend(messages_in_thread) + + try: + response = llm.invoke(messages) + if isinstance(response, dict) and "content" in response: + return response["content"] + else: + return str(response) + except Exception as e: + raise RuntimeError(f"Error calling Groq LLM: {str(e)}") From 171d446e0625dffc6dc55de8c987a8303b27f91b Mon Sep 17 00:00:00 2001 From: Abhishek Singh <76961066+Abhi2april@users.noreply.github.com> Date: Fri, 28 Feb 2025 20:52:00 +0530 Subject: [PATCH 5/7] Add files via upload --- listeners/events/__init__.py | 76 ++++---- listeners/events/assistant_thread_started.py | 132 +++++++------ .../asssistant_thread_context_changed.py | 42 ++-- listeners/events/thread_context_store.py | 128 ++++++------- listeners/events/user_message.py | 180 +++++++++--------- 5 files changed, 276 insertions(+), 282 deletions(-) diff --git a/listeners/events/__init__.py b/listeners/events/__init__.py index a2029af..90e804d 100644 --- a/listeners/events/__init__.py +++ b/listeners/events/__init__.py @@ -1,33 +1,43 @@ -# This sample app repository contains event listener code to help developers understand what's happening under the hood. -# 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 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 - - -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) - - -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 just_ack(): - pass +from typing import Dict, Any + +from slack_bolt import App +from slack_bolt.request.payload_utils import is_event +from .app_mention import respond_to_mention +from .assistant_thread_started import start_thread_with_suggested_prompts +from .asssistant_thread_context_changed import save_new_thread_context + +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("message")(handle_app_mention) + +def handle_app_mention(payload: dict, client, logger): + event = payload.get("event", {}) + channel_id = event.get("channel") + thread_ts = event.get("ts") + user = event.get("user") + + try: + client.chat_postMessage( + channel=channel_id, + thread_ts=thread_ts, + text=f"Hi <@{user}>, how can I help you?" + ) + except Exception as e: + logger.exception(f"Failed to handle app_mention event: {e}") + + + +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 just_ack(): + pass diff --git a/listeners/events/assistant_thread_started.py b/listeners/events/assistant_thread_started.py index 0d5abc6..972d89e 100644 --- a/listeners/events/assistant_thread_started.py +++ b/listeners/events/assistant_thread_started.py @@ -1,68 +1,64 @@ -# This sample app repository contains event listener code to help developers understand what's happening under the hood. -# 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 List, Dict -from logging import Logger - -from slack_sdk import WebClient - - -def start_thread_with_suggested_prompts( - payload: dict, - client: WebClient, - logger: Logger, -): - thread = payload["assistant_thread"] - channel_id, thread_ts = thread["channel_id"], thread["thread_ts"] - try: - thread_context = thread.get("context") - message_metadata = ( - { - "event_type": "assistant_thread_context", - "event_payload": thread_context, - } - if bool(thread_context) is True # the dict is not empty - else None - ) - client.chat_postMessage( - text="How can I help you?", - channel=channel_id, - thread_ts=thread_ts, - metadata=message_metadata, - ) - - 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.", - }, - ] - if message_metadata is not None: - prompts.append( - { - "title": "Summarize the referred channel", - "message": "Can you generate a brief summary of the referred channel?", - } - ) - - client.assistant_threads_setSuggestedPrompts( - channel_id=channel_id, - thread_ts=thread_ts, - prompts=prompts, - ) - except Exception as e: - logger.exception(f"Failed to handle an assistant_thread_started event: {e}", e) - client.chat_postMessage( - channel=channel_id, - thread_ts=thread_ts, - text=f":warning: Something went wrong! ({e})", - ) +from typing import List, Dict +from logging import Logger + +from slack_sdk import WebClient + + +def start_thread_with_suggested_prompts( + payload: dict, + client: WebClient, + logger: Logger, +): + thread = payload["assistant_thread"] + channel_id, thread_ts = thread["channel_id"], thread["thread_ts"] + try: + thread_context = thread.get("context") + message_metadata = ( + { + "event_type": "assistant_thread_context", + "event_payload": thread_context, + } + if bool(thread_context) is True # the dict is not empty + else None + ) + client.chat_postMessage( + text="How can I help you?", + channel=channel_id, + thread_ts=thread_ts, + metadata=message_metadata, + ) + + 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.", + }, + ] + if message_metadata is not None: + prompts.append( + { + "title": "Summarize the referred channel", + "message": "Can you generate a brief summary of the referred channel?", + } + ) + + client.assistant_threads_setSuggestedPrompts( + channel_id=channel_id, + thread_ts=thread_ts, + prompts=prompts, + ) + except Exception as e: + logger.exception(f"Failed to handle an assistant_thread_started event: {e}", e) + client.chat_postMessage( + channel=channel_id, + thread_ts=thread_ts, + text=f":warning: Something went wrong! ({e})", + ) diff --git a/listeners/events/asssistant_thread_context_changed.py b/listeners/events/asssistant_thread_context_changed.py index 4541f20..229bab7 100644 --- a/listeners/events/asssistant_thread_context_changed.py +++ b/listeners/events/asssistant_thread_context_changed.py @@ -1,23 +1,19 @@ -# This sample app repository contains event listener code to help developers understand what's happening under the hood. -# We recommend using assistant middleware instead of these event listeners. -# For more details, refer to https://tools.slack.dev/bolt-python/concepts/assistant/. - -from slack_sdk import WebClient -from slack_bolt import BoltContext - -from .thread_context_store import save_thread_context - - -def save_new_thread_context( - payload: dict, - client: WebClient, - context: BoltContext, -): - thread = payload["assistant_thread"] - save_thread_context( - context=context, - client=client, - channel_id=thread["channel_id"], - thread_ts=thread["thread_ts"], - new_context=thread.get("context"), - ) +from slack_sdk import WebClient +from slack_bolt import BoltContext + +from .thread_context_store import save_thread_context + + +def save_new_thread_context( + payload: dict, + client: WebClient, + context: BoltContext, +): + thread = payload["assistant_thread"] + save_thread_context( + context=context, + client=client, + channel_id=thread["channel_id"], + thread_ts=thread["thread_ts"], + new_context=thread.get("context"), + ) diff --git a/listeners/events/thread_context_store.py b/listeners/events/thread_context_store.py index 282cdfb..12b426d 100644 --- a/listeners/events/thread_context_store.py +++ b/listeners/events/thread_context_store.py @@ -1,66 +1,62 @@ -# This sample app repository contains event listener code to help developers understand what's happening under the hood. -# 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 Optional -from slack_sdk import WebClient -from slack_bolt import BoltContext - - -def _find_parent_message( - *, - context: BoltContext, - client: WebClient, - channel_id: str, - thread_ts: str, -) -> Optional[dict]: - response = client.conversations_replies( - channel=channel_id, - ts=thread_ts, - oldest=thread_ts, - include_all_metadata=True, - limit=4, - ) - if response.get("messages"): - for message in response.get("messages"): - if message.get("subtype") is None and message.get("user") == context.bot_user_id: - return message - - -def get_thread_context( - *, - context: BoltContext, - client: WebClient, - channel_id: str, - thread_ts: str, -) -> Optional[dict]: - parent_message = _find_parent_message(context=context, client=client, channel_id=channel_id, thread_ts=thread_ts) - if parent_message is not None and parent_message.get("metadata") is not None: - return parent_message["metadata"]["event_payload"] - - -def save_thread_context( - *, - context: BoltContext, - client: WebClient, - channel_id: str, - thread_ts: str, - new_context: dict, -) -> None: - parent_message = _find_parent_message( - context=context, - client=client, - channel_id=channel_id, - thread_ts=thread_ts, - ) - if parent_message is not None: - client.chat_update( - channel=channel_id, - ts=parent_message["ts"], - text=parent_message["text"], - blocks=parent_message.get("blocks"), - metadata={ - "event_type": "assistant_thread_context", - "event_payload": new_context, - }, - ) +from typing import Optional +from slack_sdk import WebClient +from slack_bolt import BoltContext + + +def _find_parent_message( + *, + context: BoltContext, + client: WebClient, + channel_id: str, + thread_ts: str, +) -> Optional[dict]: + response = client.conversations_replies( + channel=channel_id, + ts=thread_ts, + oldest=thread_ts, + include_all_metadata=True, + limit=4, + ) + if response.get("messages"): + for message in response.get("messages"): + if message.get("subtype") is None and message.get("user") == context.bot_user_id: + return message + + +def get_thread_context( + *, + context: BoltContext, + client: WebClient, + channel_id: str, + thread_ts: str, +) -> Optional[dict]: + parent_message = _find_parent_message(context=context, client=client, channel_id=channel_id, thread_ts=thread_ts) + if parent_message is not None and parent_message.get("metadata") is not None: + return parent_message["metadata"]["event_payload"] + + +def save_thread_context( + *, + context: BoltContext, + client: WebClient, + channel_id: str, + thread_ts: str, + new_context: dict, +) -> None: + parent_message = _find_parent_message( + context=context, + client=client, + channel_id=channel_id, + thread_ts=thread_ts, + ) + if parent_message is not None: + client.chat_update( + channel=channel_id, + ts=parent_message["ts"], + text=parent_message["text"], + blocks=parent_message.get("blocks"), + metadata={ + "event_type": "assistant_thread_context", + "event_payload": new_context, + }, + ) diff --git a/listeners/events/user_message.py b/listeners/events/user_message.py index 3d3b5cf..e3a5909 100644 --- a/listeners/events/user_message.py +++ b/listeners/events/user_message.py @@ -1,92 +1,88 @@ -# This sample app repository contains event listener code to help developers understand what's happening under the hood. -# 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 List, Dict -from logging import Logger - -from slack_sdk.web import WebClient -from slack_sdk.errors import SlackApiError -from slack_bolt import BoltContext -from ..llm_caller import call_llm -from .thread_context_store import get_thread_context - - -def respond_to_user_message( - payload: dict, - client: WebClient, - context: BoltContext, - logger: Logger, -): - channel_id, thread_ts = payload["channel"], payload["thread_ts"] - try: - user_message = payload["text"] - thread_context = get_thread_context( - context=context, - client=client, - channel_id=channel_id, - thread_ts=thread_ts, - ) - - client.assistant_threads_setStatus( - channel_id=channel_id, - thread_ts=thread_ts, - 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 - 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) - 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 = call_llm(messages_in_thread) - client.chat_postMessage( - channel=channel_id, - thread_ts=thread_ts, - text=returned_message, - ) - return - - replies = client.conversations_replies( - channel=channel_id, - ts=thread_ts, - oldest=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 = call_llm(messages_in_thread) - client.chat_postMessage( - channel=channel_id, - thread_ts=thread_ts, - text=returned_message, - ) - except Exception as e: - logger.exception(f"Failed to handle a user message event: {e}") - client.chat_postMessage( - channel=channel_id, - thread_ts=thread_ts, - text=f":warning: Something went wrong! ({e})", - ) +from typing import List, Dict +from logging import Logger + +from slack_sdk.web import WebClient +from slack_sdk.errors import SlackApiError +from slack_bolt import BoltContext +from ..llm_caller import call_llm +from .thread_context_store import get_thread_context + + +def respond_to_user_message( + payload: dict, + client: WebClient, + context: BoltContext, + logger: Logger, +): + channel_id, thread_ts = payload["channel"], payload["thread_ts"] + try: + user_message = payload["text"] + thread_context = get_thread_context( + context=context, + client=client, + channel_id=channel_id, + thread_ts=thread_ts, + ) + + client.assistant_threads_setStatus( + channel_id=channel_id, + thread_ts=thread_ts, + 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 + 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) + 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 = call_llm(messages_in_thread) + client.chat_postMessage( + channel=channel_id, + thread_ts=thread_ts, + text=returned_message, + ) + return + + replies = client.conversations_replies( + channel=channel_id, + ts=thread_ts, + oldest=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 = call_llm(messages_in_thread) + client.chat_postMessage( + channel=channel_id, + thread_ts=thread_ts, + text=returned_message, + ) + except Exception as e: + logger.exception(f"Failed to handle a user message event: {e}") + client.chat_postMessage( + channel=channel_id, + thread_ts=thread_ts, + text=f":warning: Something went wrong! ({e})", + ) From cff848e7d922dcd5568e496724668ca168bb9481 Mon Sep 17 00:00:00 2001 From: Abhishek Singh <76961066+Abhi2april@users.noreply.github.com> Date: Sat, 1 Mar 2025 01:31:03 +0530 Subject: [PATCH 6/7] Update README.md --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c2019a5..6cde8f2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# App Agent & Assistant Template (Bolt for Python) - +# EthicAll For Your Slack workspace moderation +You Don't need any more to pay for you OpenAI's API keys-> Just use your free Groq's Inference Tokens and change to your favourite model to work in backend from [here](https://console.groq.com/docs/models) and copy it to listeners/llm_caller.py This Bolt for Python template demonstrates how to build [Agents & Assistants](https://api.slack.com/docs/apps/ai) in Slack. ## Setup @@ -29,14 +29,13 @@ Before you can run the app, you'll need to store some environment variables. export SLACK_BOT_TOKEN= export SLACK_APP_TOKEN= # This sample uses OpenAI's API by default, but you can switch to any other solution! -export OPENAI_API_KEY= +export GROQ_API_KEY= ``` ### Setup Your Local Project ```zsh # Clone this project onto your machine -git clone https://github.com/slack-samples/bolt-python-assistant-template.git - +git clone https://github.com/Abhi2april/bolt-python-assistant-template # Change into this project directory cd bolt-python-assistant-template From dcfa67c6d72a296e7cb13cbb688200bf466320cc Mon Sep 17 00:00:00 2001 From: Abhishek Singh <76961066+Abhi2april@users.noreply.github.com> Date: Sat, 1 Mar 2025 01:35:06 +0530 Subject: [PATCH 7/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6cde8f2..c615cb9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# EthicAll For Your Slack workspace moderation +# EthicAll For Your Slack workspace moderation --> By Abhishek Singh You Don't need any more to pay for you OpenAI's API keys-> Just use your free Groq's Inference Tokens and change to your favourite model to work in backend from [here](https://console.groq.com/docs/models) and copy it to listeners/llm_caller.py This Bolt for Python template demonstrates how to build [Agents & Assistants](https://api.slack.com/docs/apps/ai) in Slack.