diff --git a/.env.example b/.env.example index 0f856a2..e6f81fa 100644 --- a/.env.example +++ b/.env.example @@ -3,4 +3,7 @@ OLLAMA_CHAT_MODEL=mistral OLLAMA_URL=http://host.docker.internal:11434 OPENAI_API_KEY= OPENAI_CHAT_MODEL='gpt-3.5-turbo-0125' +SLACK_APP_TOKEN= +SLACK_BOT_TOKEN= +SLACK_SIGNING_SECRET= VERBOSE=0 diff --git a/README.md b/README.md index 8647c79..2e2136d 100644 --- a/README.md +++ b/README.md @@ -43,3 +43,9 @@ langchain serve ```shell docker compose up --build ``` + +## Start Slack socket mode + +```shell +slack_socket_mode +``` diff --git a/app/config.py b/app/config.py index 6db51cd..f61b479 100644 --- a/app/config.py +++ b/app/config.py @@ -6,10 +6,13 @@ load_dotenv() DEBUG = os.getenv("DEBUG", "0") == "1" -VERBOSE = os.getenv("VERBOSE", "0") == "1" OLLAMA_CHAT_MODEL = os.getenv("OLLAMA_CHAT_MODEL", "mistral") OLLAMA_URL = os.getenv("OLLAMA_URL", "http://localhost:11434") OPENAI_CHAT_MODEL = os.getenv("OPENAI_CHAT_MODEL", "gpt-3.5-turbo") +SLACK_APP_TOKEN = os.getenv("SLACK_APP_TOKEN") +SLACK_BOT_TOKEN = os.getenv("SLACK_BOT_TOKEN") +SLACK_SIGNING_SECRET = os.getenv("SLACK_SIGNING_SECRET") +VERBOSE = os.getenv("VERBOSE", "0") == "1" set_debug(DEBUG) set_verbose(VERBOSE) diff --git a/app/dependencies/slack.py b/app/dependencies/slack.py new file mode 100644 index 0000000..7373f03 --- /dev/null +++ b/app/dependencies/slack.py @@ -0,0 +1,38 @@ +from typing import Annotated + +from fastapi import Depends +from slack_bolt import App +from slack_bolt.adapter.fastapi import SlackRequestHandler +from slack_bolt.adapter.socket_mode import SocketModeHandler + +from app import config + +app = App( + token=config.SLACK_BOT_TOKEN, + signing_secret=config.SLACK_SIGNING_SECRET +) + +slack_request_handler = SlackRequestHandler(app) + +slack_socket_model_handler = SocketModeHandler(app, config.SLACK_APP_TOKEN) + + +def get_slack_request_handler() -> SlackRequestHandler: + return slack_request_handler + + +SlackRequestHandlerDep = Annotated[SlackRequestHandler, Depends(get_slack_request_handler)] + +@app.event("message") +def handle_message_events(body, say, logger): + logger.info(body) + text = body.get('event', {}).get('text', '') + if 'hello' in text.lower(): + say("Hello there! :wave:") + + +# Define an event listener for "app_mention" events +@app.event("app_mention") +def handle_app_mention_events(body, say, logger): + logger.info(body) + say("Hello SRE slackers! :blush:") diff --git a/app/routers/__init__.py b/app/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/routers/slack.py b/app/routers/slack.py new file mode 100644 index 0000000..e75ac8a --- /dev/null +++ b/app/routers/slack.py @@ -0,0 +1,10 @@ +from fastapi import APIRouter, Request + +from app.dependencies.slack import SlackRequestHandlerDep + +router = APIRouter() + + +@router.post("/slack/events") +async def slack_events(request: Request, handler: SlackRequestHandlerDep): + return await handler.handle(request) diff --git a/app/server.py b/app/server.py index 78ce8ab..d8c0a93 100644 --- a/app/server.py +++ b/app/server.py @@ -6,6 +6,7 @@ from app.chains.extraction import extraction_chain from app.chains.supervisor import build_supervisor_chain from app.dependencies.ollama_chat_model import ollama_chat_model +from app.routers import slack app = FastAPI() @@ -39,6 +40,8 @@ async def redirect_root_to_docs(): path="/supervisor", ) +app.include_router(slack.router) + if __name__ == "__main__": import uvicorn diff --git a/app/slack_socket_mode.py b/app/slack_socket_mode.py new file mode 100755 index 0000000..3035cf2 --- /dev/null +++ b/app/slack_socket_mode.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 + +from app import config +from app.dependencies.slack import slack_socket_model_handler + + +def run(): + if config.SLACK_APP_TOKEN: + slack_socket_model_handler.start() + else: + print("SLACK_APP_TOKEN not found.") + + +if __name__ == '__main__': + run() diff --git a/app/tools/slack_bolt_env/requirements.txt b/app/tools/slack_bolt_env/requirements.txt deleted file mode 100644 index d121534..0000000 --- a/app/tools/slack_bolt_env/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -slack-bolt==1.18.1 -fastapi==0.79.0 -uvicorn==0.17.6 -python-dotenv==0.20.0 -requests==2.27.1 diff --git a/app/tools/slack_bolt_env/setup.sh b/app/tools/slack_bolt_env/setup.sh deleted file mode 100644 index 1730999..0000000 --- a/app/tools/slack_bolt_env/setup.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -# Create the virtual environment -echo "Creating virtual environment..." -python3 -m venv .venv - -# Activate the virtual environment -echo "Activating virtual environment..." -source .venv/bin/activate - -# Install dependencies -echo "Installing dependencies from requirements.txt..." -pip install -r requirements.txt - -echo "Setup completed. Virtual environment is ready and dependencies are installed." - -# Run the app -echo "Running sre-ascent-dev.py..." -python3 sre-ascent-dev.py diff --git a/app/tools/slack_bolt_env/sre-ascent-dev.py b/app/tools/slack_bolt_env/sre-ascent-dev.py deleted file mode 100755 index 6f5d579..0000000 --- a/app/tools/slack_bolt_env/sre-ascent-dev.py +++ /dev/null @@ -1,47 +0,0 @@ -import os -from slack_bolt import App -from slack_bolt.adapter.socket_mode import SocketModeHandler -from slack_bolt.adapter.fastapi import SlackRequestHandler -from fastapi import FastAPI, Request - -# Initialize a Bolt for sre-ascent-dev app -app = App( - token=os.environ.get("SLACK_BOT_TOKEN"), - signing_secret=os.environ.get("SLACK_SIGNING_SECRET") -) - -# Define an event listener for "message" events -@app.event("message") -def handle_message_events(body, say, logger): - logger.info(body) - text = body.get('event', {}).get('text', '') - if 'hello' in text.lower(): - say("Hello there! :wave:") - -# Define an event listener for "app_mention" events -@app.event("app_mention") -def handle_app_mention_events(body, say, logger): - logger.info(body) - say("Hello SRE slackers! :blush:") - -# FastAPI app to handle routes -fastapi_app = FastAPI() - -# Create a request handler for the app -handler = SlackRequestHandler(app) - -@fastapi_app.post("/slack/events") -async def slack_events(request: Request): - return await handler.handle(request) - -# Check if the script is being run directly -if __name__ == "__main__": - # Check if SLACK_APP_TOKEN is set for Socket Mode - if 'SLACK_APP_TOKEN' in os.environ: - # Start the app in Socket Mode - handler = SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]) - handler.start() - else: - # If not using socket mode, we can start the FastAPI app using the command in terminal: - # uvicorn app:fastapi_app --reload --port 3000 - print("SLACK_APP_TOKEN not found. Please set it to run in Socket Mode or to run the FastAPI app with uvicorn from the terminal.") diff --git a/poetry.lock b/poetry.lock index fe8e69e..72543ab 100644 --- a/poetry.lock +++ b/poetry.lock @@ -430,20 +430,20 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.41" +version = "3.1.42" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" files = [ - {file = "GitPython-3.1.41-py3-none-any.whl", hash = "sha256:c36b6634d069b3f719610175020a9aed919421c87552185b085e04fbbdb10b7c"}, - {file = "GitPython-3.1.41.tar.gz", hash = "sha256:ed66e624884f76df22c8e16066d567aaa5a37d5b5fa19db2c6df6f7156db9048"}, + {file = "GitPython-3.1.42-py3-none-any.whl", hash = "sha256:1bf9cd7c9e7255f77778ea54359e54ac22a72a5b51288c457c881057b7bb9ecd"}, + {file = "GitPython-3.1.42.tar.gz", hash = "sha256:2d99869e0fef71a73cbd242528105af1d6c1b108c60dfabd994bf292f76c3ceb"}, ] [package.dependencies] gitdb = ">=4.0.1,<5" [package.extras] -test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "sumtypes"] +test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar"] [[package]] name = "greenlet" @@ -1437,6 +1437,41 @@ files = [ {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, ] +[[package]] +name = "slack-bolt" +version = "1.18.1" +description = "The Bolt Framework for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "slack_bolt-1.18.1-py2.py3-none-any.whl", hash = "sha256:2509e5bb43898a593667bf37965057a9b9a41008b29628e3b57a6136b650b90e"}, + {file = "slack_bolt-1.18.1.tar.gz", hash = "sha256:694f84a81ba1c4c428ba7daa01d599d3e9fba7a54ad10c11008aa22573b23ff0"}, +] + +[package.dependencies] +slack-sdk = ">=3.25.0,<4" + +[package.extras] +adapter = ["CherryPy (>=18,<19)", "Django (>=3,<5)", "Flask (>=1,<3)", "Werkzeug (>=2,<3)", "boto3 (<=2)", "bottle (>=0.12,<1)", "chalice (>=1.28,<2)", "falcon (>=2,<4)", "fastapi (>=0.70.0,<1)", "gunicorn (>=20,<21)", "pyramid (>=1,<3)", "sanic (>=22,<23)", "starlette (>=0.14,<1)", "tornado (>=6,<7)", "uvicorn (<1)", "websocket-client (>=1.2.3,<2)"] +adapter-testing = ["Flask (>=1,<2)", "Werkzeug (>=1,<2)", "boddle (>=0.2,<0.3)", "docker (>=5,<6)", "moto (>=3,<4)", "requests (>=2,<3)", "sanic-testing (>=0.7)"] +async = ["aiohttp (>=3,<4)", "websockets (>=10,<11)"] +testing = ["Flask-Sockets (>=0.2,<1)", "Jinja2 (==3.0.3)", "Werkzeug (>=1,<2)", "aiohttp (>=3,<4)", "black (==22.8.0)", "click (<=8.0.4)", "itsdangerous (==2.0.1)", "pytest (>=6.2.5,<7)", "pytest-asyncio (>=0.18.2,<1)", "pytest-cov (>=3,<4)"] +testing-without-asyncio = ["Flask-Sockets (>=0.2,<1)", "Jinja2 (==3.0.3)", "Werkzeug (>=1,<2)", "black (==22.8.0)", "click (<=8.0.4)", "itsdangerous (==2.0.1)", "pytest (>=6.2.5,<7)", "pytest-cov (>=3,<4)"] + +[[package]] +name = "slack-sdk" +version = "3.27.0" +description = "The Slack API Platform SDK for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "slack_sdk-3.27.0-py2.py3-none-any.whl", hash = "sha256:a901c68cb5547d5459cdefd81343d116db56d65f6b33f4081ddf1cdd243bf07e"}, + {file = "slack_sdk-3.27.0.tar.gz", hash = "sha256:811472ce598db855ab3c02f098fa430323ccb253cfe17ba20c7b05ab206d984d"}, +] + +[package.extras] +optional = ["SQLAlchemy (>=1.4,<3)", "aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "websocket-client (>=1,<2)", "websockets (>=10,<11)", "websockets (>=9.1,<10)"] + [[package]] name = "smmap" version = "5.0.1" @@ -1868,4 +1903,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "a9727f4760f3a2da6df020743103768e52170e10c17a63cc4d7f410d5d8b70ee" +content-hash = "dadf32f2261703aa3f4eaf2da781a85eb3b4b05cc2413875d28450a0eae2098d" diff --git a/pyproject.toml b/pyproject.toml index c9ec85b..ac9c27e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,12 +24,16 @@ langserve = {extras = ["server"], version = ">=0.0.41"} pydantic = "<2" python = "^3.11" python-dotenv = "^1.0.1" +slack-bolt = "^1.18.1" uvicorn = "^0.23.2" [tool.poetry.group.dev.dependencies] langchain-cli = ">=0.0.15" ruff = "^0.2.1" +[tool.poetry.scripts] +slack_socket_mode = "app.slack_socket_mode:run" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api"