-
Notifications
You must be signed in to change notification settings - Fork 8
/
slack.py
249 lines (209 loc) · 9.68 KB
/
slack.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
import os
import traceback
from dotenv import load_dotenv
from slack_bolt.async_app import AsyncApp
from slack_bolt.oauth.async_oauth_settings import AsyncOAuthSettings
from slack_sdk.oauth.installation_store import FileInstallationStore
from slack_sdk.oauth.state_store import FileOAuthStateStore
from ai import ai_chat_thread, summarize_thread
from inference import get_query_response
from posthog import Posthog
CHAT_HISTORY_LIMIT = 20
load_dotenv()
posthog = Posthog(os.environ.get("POSTHOG_API_KEY"), os.environ.get("POSTHOG_HOST"))
oauth_settings = AsyncOAuthSettings(
client_id=os.environ["SLACK_CLIENT_ID"],
client_secret=os.environ["SLACK_CLIENT_SECRET"],
scopes=[
"app_mentions:read",
"bookmarks:read",
"channels:history",
"channels:join",
"channels:read",
"chat:write",
"chat:write.customize",
"chat:write.public",
"emoji:read",
"files:write",
"groups:history",
"groups:read",
"im:history",
"im:write",
"im:read",
"metadata.message:read",
"commands",
"links:read",
"links:write",
"links.embed:write",
"mpim:history",
"users:write",
"users:read.email",
"users:read",
"users.profile:read",
"team:read",
"team.preferences:read",
"reactions:write",
"reactions:read",
"dnd:read",
"files:read"
],
installation_store=FileInstallationStore(base_dir="./data/installations"),
state_store=FileOAuthStateStore(expiration_seconds=600, base_dir="./data/states")
)
# Initializes your app with your bot token and signing secret
app = AsyncApp(
oauth_settings=oauth_settings,
signing_secret=os.environ.get("SLACK_SIGNING_SECRET"),
)
# Add functionality here
# @app.event("app_home_opened") etc
@app.event("app_home_opened")
async def update_home_tab(client, event, logger):
try:
# views.publish is the method that your app uses to push a view to the Home tab
await client.views_publish(
# the user that opened your app's app home
user_id=event["user"],
# the view object that appears in the app home
view={
"type": "home",
"callback_id": "home_view",
# body of the view
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Hi there! I'm Max!* :wave:",
},
},
{"type": "divider"},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Hello! As PostHog's trusty support AI, I'm happy to answer any questions you may have about PostHog. If you're curious about our product, features, or pricing, I'm here to help. As an open-source company, we want to provide an excellent user experience, and we're always happy to receive feedback. If you have any suggestions, please let us know.\n\n *How to interact with Max* \n It's simple. Just @ mention @max_ai in any thread and ask what you would like done. Examples may look like:\n- @max_ai can you try answering the question here?\n- @max_ai can you summarize this?\n- @max_ai I have a question about <something awesome>\n- @max_ai Who is the current support hero that I can talk to about this? \n\n *How does max work?!*\nYou can find out more about how Max is built on GitHub!\nhttps://github.com/posthog/max-ai\nOf course it's Open Source :hog-excited:\n\n*Disclaimer!*\n_Max may display inaccurate or offensive information that doesn’t represent PostHog's views._\nThis is the case with LLMs in the current state. We try our best here to have a system prompt that keeps Max on topic.\nFeel free to question and chat with Max but do keep in mind that this is experimental.",
},
},
],
},
)
except Exception as e:
logger.error(f"Error publishing home tab: {e}")
def preprocess_slack_thread(bot_id, thread):
thread = [(msg["user"], msg["text"]) for msg in thread["messages"]]
history = [{"role": "assistant" if user == bot_id else "user", "content": msg} for user, msg in thread]
return history
@app.command("/summarize")
async def handle_summarize_slash_command(ack, say, command):
ack()
await send_message(text="Hi there")
@app.event("message")
async def handle_message_events(client, body, logger, say):
event_type = body["event"]["channel_type"]
event = body["event"]
bot_id = body['authorizations'][0]['user_id']
print(body)
if event_type == "im":
thread = await client.conversations_history(channel=event["channel"], limit=CHAT_HISTORY_LIMIT)
thread = preprocess_slack_thread(bot_id, thread)
response = await ai_chat_thread(thread)
await send_message(say, response)
# new message in a public channel
elif "thread_ts" not in event and event["type"] == "message" and event["channel_type"] == "channel":
# follow_up = classify_question(event["text"])
# if follow_up:
# send_message(say, text=response, thread_ts=event["ts"])
return
# thread response in a public channel
elif "thread_ts" in event and event["channel_type"] == "channel":
return
thread_ts = event["thread_ts"]
# Call the conversations.replies method with the channel ID and thread timestamp
# try:
result = await client.conversations_replies(channel=event["channel"], ts=thread_ts)
result["messages"]
thread = preprocess_slack_thread(bot_id, result)
# except Exception as e:
# print("Error retrieving thread messages: {}".format(e))
# return
if "assistant" not in [msg["role"] for msg in thread]:
# we haven't responded and it's a thread, which meant the classification said no, so don't try to respond
return
if len(thread) >= 4:
# This is too long, not worth responding to
return
if thread[-1]["role"] == "assistant":
# we just responded, don't respond to ourselves
return
# get first message in thread
question = thread[0]["content"]
response = get_query_response(question, thread)
await send_message(say, text=response, thread_ts=event["thread_ts"])
@app.event("emoji_changed")
async def handle_emoji_changed_events(body, logger, say):
print(body)
@app.event("app_mention")
async def handle_app_mention_events(client, body, logger, say):
try:
await _handle_app_mention_events(client, body, logger, say)
except Exception as e:
traceback.print_exc()
await send_message(say, text="I'm a little over capacity right now. Please try again in a few minutes! :sleeping-hog:")
posthog.capture(
"max-ai",
"max-ai mention error",
properties={
"error": str(e),
"user": body["event"]["user"],
"channel": body["event"]["channel"],
"text": body["event"]["text"],
},
)
raise e
async def _handle_app_mention_events(client, body, logger, say):
logger.info(body)
print(body)
posthog.capture(
"max-ai",
"max-ai mention",
properties={
"user": body["event"]["user"],
"channel": body["event"]["channel"],
"text": body["event"]["text"],
},
)
user_id = get_user_id(body)
bot_id = body['authorizations'][0]['user_id']
event = body["event"]
thread_ts = event["thread_ts"] if "thread_ts" in event else event["ts"]
thread = await client.conversations_replies(
channel=event["channel"], ts=thread_ts, limit=CHAT_HISTORY_LIMIT
)
if "please summarize this" in event["text"].lower():
await send_message(say, text="On it!", thread_ts=thread_ts, user_id=user_id, thread=thread)
summary = summarize_thread(thread)
await send_message(say, text=summary, thread_ts=thread_ts, user_id=user_id, thread=thread)
return
thread = preprocess_slack_thread(bot_id, thread)
# first_relevant_message = thread[0]["content"]
# Disabling this for launch because it can be confusing and jarring when these are incorrect
# use_feature_flag_prompt = await classify_question(first_relevant_message)
# if use_feature_flag_prompt:
# print("using feature flag prompt for ", first_relevant_message)
# response = await get_query_response(first_relevant_message, thread[1:])
# await send_message(say, text=response, thread_ts=thread_ts, user_id=user_id, thread=thread)
# return
response = await ai_chat_thread(thread)
await send_message(say, text=response, thread_ts=thread_ts, user_id=user_id, thread=thread)
async def send_message(say, text, thread_ts=None, user_id=None, thread=None):
posthog.capture("max-ai", "max-ai message sent", {"message": text, "thread_ts": thread_ts, "sender": user_id, "context": thread})
if thread_ts:
await say(text=text, thread_ts=thread_ts)
else:
await say(text)
def get_user_id(body):
return body.get("event", {}).get("user", None)
# Start your app
if __name__ == "__main__":
app.start(port=int(os.environ.get("PORT", 3000)))