-
Notifications
You must be signed in to change notification settings - Fork 8
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
Initial submission #1
Changes from 2 commits
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,3 +1,3 @@ | ||
[flake8] | ||
max-line-length = 125 | ||
max-line-length = 200 | ||
exclude = .gitignore,venv |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,4 +35,5 @@ tmp.txt | |
.DS_Store | ||
logs/ | ||
*.db | ||
.pytype/ | ||
.pytype/ | ||
.idea/ | ||
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. for PyCharm users 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. If we don't already have this in place in the main template, can we open a PR to update it? 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. Here you are: slack-samples/bolt-python-starter-template#42 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. Appreciate you! 🙇🏼♀️ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,5 @@ | ||
from listeners import actions | ||
from listeners import commands | ||
from listeners import events | ||
from listeners import messages | ||
from listeners import shortcuts | ||
from listeners import views | ||
|
||
|
||
def register_listeners(app): | ||
actions.register(app) | ||
commands.register(app) | ||
events.register(app) | ||
messages.register(app) | ||
shortcuts.register(app) | ||
views.register(app) |
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,29 @@ | ||
from typing import Dict, Any | ||
|
||
from slack_bolt import App | ||
from .app_home_opened import app_home_opened_callback | ||
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("app_home_opened")(app_home_opened_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) | ||
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. Other message events such as message_changed, message_deleted etc. are produced by an assistant thread's behavior. Just acknowledging them. |
||
|
||
|
||
def is_message_event_in_assistant_thread(body: Dict[str, Any]) -> bool: | ||
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. These is_xxx methods will be unnecessary once bolt-python's full support is released: slackapi/bolt-python#1162 |
||
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 |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
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})", | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
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"), | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
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]+?)(?<!\s)\*\*\*", | ||
r"_*\1*_", | ||
), # ***bold italic*** to *_bold italic_* | ||
( | ||
r"(?<![\*_])\*(?!\s)([^\*\n]+?)(?<!\s)\*(?![\*_])", | ||
r"_\1_", | ||
), # *italic* to _italic_ | ||
(r"\*\*(?!\s)([^\*\n]+?)(?<!\s)\*\*", r"*\1*"), # **bold** to *bold* | ||
(r"__(?!\s)([^_\n]+?)(?<!\s)__", r"*\1*"), # __bold__ to *bold* | ||
(r"~~(?!\s)([^~\n]+?)(?<!\s)~~", r"~\1~"), # ~~strike~~ to ~strike~ | ||
]: | ||
part = re.sub(o, n, part) | ||
result += part | ||
return result |
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.
For gen AI apps, this length is too small, thus I've changed it this time