-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
eca892f
commit ca5e723
Showing
13 changed files
with
249 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
SLACK_SIGNING_SECRET="" | ||
SLACK_BOT_TOKEN="" | ||
OPENAI_API_KEY="" | ||
OPENAI_ORGANIZATION="" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
# How to install the AI Slack App | ||
|
||
In this guide, it is assumed the web app has a valid url address. | ||
|
||
## Steps | ||
1. Go to [https://api.slack.com/apps](https://api.slack.com/apps). Log in. then click on the big green button "Create an App". | ||
2. It will ask you if you want to create one from scratch or use a template. We will create one from scratch | ||
3. Enter a name, say *AssistantAI* | ||
4. Choose the workspace. Note that you can only select those in which you have access. | ||
5. Click on the green button "Create". | ||
6. Set permissions | ||
7. Install on workspace | ||
8. Add the bot to the channels of your interest. | ||
9. Connect the Web App to the bot | ||
|
||
### Setting permissions | ||
On the left menu select "OAuth & Permissions" under **Features**. Scroll down to the **Scopes** section. | ||
Add bot scopes. In our case we will give the following: | ||
- `chat:write` | ||
- `app_mentions:read` | ||
- `channels:read` | ||
- `groups:read` | ||
|
||
 | ||
|
||
### Install on workspace | ||
On the left menu select "Basic Information" under **Settings**. Click on "Install to Workspace". | ||
|
||
 | ||
|
||
You will see a confirmation page in which it is summarized the app permissions and capabilities. Click on the green button "Allow". | ||
|
||
 | ||
|
||
Now the app is now installed on your workspace. You can check it on the left menu in your Slack application under "Apps" | ||
|
||
 | ||
|
||
### Add the bot to a channel | ||
Right click on the channel of your interest and select "View channels details". Select the "Integrations" tab and select "Add an App". Select your app from the list. | ||
|
||
 | ||
|
||
### Connect the Web App to the bot | ||
In [https://api.slack.com/apps](https://api.slack.com/apps) select your bot from the dropdown menu on the top left. | ||
|
||
Under **Features** select "OAuth & Permissions". Copy the OAuth Token for the Workspace and save it in the environment variable `SLACK_BOT_TOKEN`. | ||
|
||
Under **Settings** select "Basic Information". Copy the "Signing Secret" in the **App Credentials** section and save it in the environment variable `SLACK_SIGNING_SECRET`. | ||
|
||
Under **Features** select "Event Subscriptions". Toggle "Enable Events" (if it is not on already) and paste the url of your web app in the *Request Url* field and add the endpoint `/slack/event` to it. You should see a green check mark and *Verified* | ||
|
||
 | ||
|
||
Scroll down to the **Subscribe to bot events** section and click "Add Bot User Event". Select `app_mention` and `message.channels` and click the green button on the bottom that says "Save changes". The last event that we added will require to reinstall your app because it requires more permission that we have given it, so do it. | ||
|
||
 | ||
|
||
That's it! The app is now installed. Make sure you also retrieve the OpenAI API key and OpenAI Organization ID and save them in the `.env` file. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,16 @@ | ||
# ai-slack-app | ||
Create an AI assistant as a Slack App | ||
An AI assistant as a Slack App | ||
|
||
## Disclaimer | ||
The code in `main.py` works fine, however as of 05/16/2023 it seems like the bot continues to answer to the same prompt without being asked to. I have not figured out the full reason for such a bug, however the issue seems to be with how I use the openAI API. | ||
|
||
## How it works | ||
This is a Flask web app so it must have a valid url. If you want to test it out locally you can use [ngrok](https://ngrok.com/). | ||
I have tested it using [replit](replit.com) but without deploying it. | ||
|
||
Once it is installed on your Slack channel simply mention the bot (in my case I called it *AssistantAI*, very clever I know) and it will respond to you in a thread. | ||
|
||
 | ||
|
||
## References | ||
To integrate the App on Slack I have mostly followed [DavidAtReplit guide](https://youtu.be/Rw84iRwFbJQ) but I modified it a little using the `slack-sdk` and `slackeventsapi` documentation. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import os | ||
|
||
from typing import Union | ||
from pathlib import Path | ||
|
||
from flask import Flask | ||
from slack_sdk.web import WebClient | ||
from slackeventsapi import SlackEventAdapter | ||
from dotenv import load_dotenv | ||
import re | ||
import logging | ||
import openai | ||
|
||
# create a console logger | ||
logger = logging.getLogger('chatLog') | ||
logger.setLevel(logging.DEBUG) | ||
ch = logging.StreamHandler() | ||
ch.setLevel(logging.DEBUG) | ||
formatter = logging.Formatter('%(asctime)s - %(levelname)-8s : %(message)s') | ||
ch.setFormatter(formatter) | ||
logger.addHandler(ch) | ||
|
||
env_path = Path('.') / '.env' | ||
load_dotenv(dotenv_path=env_path) | ||
|
||
# constants | ||
SLACK_SIGNING_SECRET = os.getenv("SLACK_SIGNING_SECRET") | ||
SLACK_BOT_TOKEN = os.getenv("SLACK_BOT_TOKEN") | ||
|
||
# Authenticate OpenAI | ||
openai.api_key = os.getenv("OPENAI_API_KEY") | ||
openai.organization = os.getenv("OPENAI_ORGANIZATION") | ||
|
||
# Helping classes for the AI bot | ||
class Message: | ||
""" | ||
A basic class to create a message as a dict for chat | ||
""" | ||
def __init__(self, role:str, content:str, name:str = None)->None: | ||
self.role = role | ||
self.content = content | ||
self.name = name | ||
|
||
def message_for_ai(self)->dict: | ||
if self.name is not None: | ||
return {"role": self.role, "name": self.name, "content": self.content} | ||
else: | ||
return {"role": self.role, "content": self.content} | ||
|
||
class Chat: | ||
""" | ||
The Chat class is used to preserve the conversation between the user and the bot and to send new messages to it. | ||
""" | ||
def __init__(self)->None: | ||
self.conversation_history = [] | ||
|
||
def _get_assistant_response(self, prompt:list)->Union[Message,str]: | ||
try: | ||
completion = openai.ChatCompletion.create( | ||
model="gpt-3.5-turbo", | ||
messages=prompt | ||
) | ||
except openai.error.APIError as e: | ||
# Handle API error here, e.g. retry or log | ||
err_msg = f"OpenAI API returned an API Error: {e}" | ||
logger.error(err_msg) | ||
return err_msg | ||
except openai.error.AuthenticationError as e: | ||
# Handle Authentication error here, e.g. invalid API key | ||
err_msg = f"OpenAI API returned an Authentication Error: {e}" | ||
logger.error(err_msg) | ||
return err_msg | ||
except openai.error.APIConnectionError as e: | ||
# Handle connection error here | ||
err_msg = f"Failed to connect to OpenAI API: {e}" | ||
logger.error(err_msg) | ||
return err_msg | ||
except openai.error.InvalidRequestError as e: | ||
# Handle connection error here | ||
err_msg = f"Invalid Request Error: {e}" | ||
logger.error(err_msg) | ||
return err_msg | ||
except openai.error.RateLimitError as e: | ||
# Handle rate limit error | ||
err_msg = f"OpenAI API request exceeded rate limit: {e}" | ||
logger.error(err_msg) | ||
return err_msg | ||
except openai.error.ServiceUnavailableError as e: | ||
# Handle Service Unavailable error | ||
err_msg = f"Service Unavailable: {e}" | ||
logger.error(err_msg) | ||
except openai.error.Timeout as e: | ||
# Handle request timeout | ||
err_msg = f"Request timed out: {e}" | ||
logger.error(err_msg) | ||
except Exception as e: | ||
# Handle all other exceptions | ||
err_msg = f"An exception has occured: {e}" | ||
logger.error(err_msg) | ||
return err_msg | ||
else: | ||
response_message = Message( | ||
completion['choices'][0]['message']['role'], | ||
completion['choices'][0]['message']['content'] | ||
) | ||
return response_message | ||
|
||
def ask_assistant(self, next_user_prompt:Union[list,dict])->Union[Message,str]: | ||
if type(next_user_prompt)==dict: | ||
self.conversation_history.append(next_user_prompt) | ||
else: | ||
[self.conversation_history.append(x) for x in next_user_prompt] | ||
assistant_response = self._get_assistant_response(self.conversation_history) | ||
if type(assistant_response)==Message: | ||
self.conversation_history.append(assistant_response.message_for_ai()) | ||
return assistant_response | ||
|
||
def _del_conversation(self)->None: | ||
self.conversation_history = [] | ||
|
||
|
||
SYSTEM_PROMPTS = [ | ||
Message('system',"You are AssistantAI, a seasoned full-stack developer with expertise in Django, React, Flutter, Java Spring framework, and SAS software.You have a strong background in software engineering principles and have extensive experience in managing complex software projects in many programming languages like Python and Javascript. You are well-versed in Agile methodologies and have worked on projects in different domains, including e-commerce, education, healthcare, and finance. As an SEO advisor, you have helped many businesses improve their online visibility and conversion rates through effective search engine optimization strategies. You have a deep understanding of keyword research, on-page optimization, backlink analysis, and content marketing. Your proficiency in database management and design allows you to design efficient databases that optimize performance and scalability. You are highly proficient in SQL and have experience in working with different databases like MySQL, PostgreSQL, Oracle, and MongoDB."), | ||
Message('system',"For what does SQL stand?", "example_user"), | ||
Message('system',"SQL stands for Structured Query Language.", "example_assistant") | ||
] | ||
SYSTEM_MSGS = [prompt.message_for_ai() for prompt in SYSTEM_PROMPTS] | ||
|
||
history = {} # conversation histories of the users history[userId] = Chat | ||
|
||
# Initialize app | ||
app = Flask(__name__) | ||
|
||
# Set the callback to validate the bot to be an authorized user | ||
# Bind the Events API route to the existing Flask app by passing the server instance as the last param, or with \`server=app\`. | ||
slack_event_adapter = SlackEventAdapter(SLACK_SIGNING_SECRET, endpoint='/slack/events', server=app) | ||
|
||
client = WebClient(token=SLACK_BOT_TOKEN) | ||
|
||
# Get the bot id | ||
BOT_ID = client.api_call("auth.test")["user_id"] | ||
|
||
@slack_event_adapter.on("app_mention") # subscribe to only the message events that mention your app or bot | ||
def on_slack_message(event_data): | ||
event = event_data["event"] # get the event | ||
channel = event["channel"] # get the channel id | ||
user = event["user"] # get the user id | ||
text = event["text"] # get the body of the message | ||
ts = event["ts"] # get the individual id of the message so we can respond to it in a thread | ||
|
||
if BOT_ID in text: #is the bot being addressed? | ||
try: | ||
user_assistant = history[user] | ||
except KeyError: # the user has never messaged the bot | ||
history[user] = Chat() # initialize the chat | ||
user_assistant = history[user] | ||
user_assistant.ask_assistant(SYSTEM_MSGS) # provide the bot with the system prompts | ||
finally: | ||
prompt = "".join(re.split(r"(?:<@[A-Z0-9]*>)", text)) # remove the bot mention (<@BOT_ID>) from the message | ||
user_input = Message("user", prompt) | ||
response = user_assistant.ask_assistant(user_input.message_for_ai()) # prompt the bot | ||
answer = response if type(response)==str else response.content | ||
client.chat_postMessage(channel=channel, thread_ts=ts, text=answer) | ||
|
||
|
||
|
||
app.run(host="0.0.0.0", port=5000) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
flask==2.3.2 | ||
slack-sdk==3.21.3 | ||
slackeventsapi==3.0.1 | ||
openai==0.27.6 |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.