Skip to content

Commit ea6a3cf

Browse files
mwbrookssrtaalejzimeg
authored
feat: add ai-enabled features text streaming, loading states, and feedback buttons (#23)
Co-authored-by: Maria Alejandra <104795114+srtaalej@users.noreply.github.com> Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com> Co-authored-by: Ale Mercado <maria.mercado@slack-corp.com>
1 parent 3181e43 commit ea6a3cf

22 files changed

+369
-478
lines changed

.env.sample

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
SLACK_APP_TOKEN=YOUR_SLACK_APP_TOKEN
2+
SLACK_BOT_TOKEN=YOUR_SLACK_BOT_TOKEN
3+
# SLACK_API_URL=YOUR_SLACK_API_URL
4+
# This template uses OpenAI, but you can use any other provider!
5+
OPENAI_API_KEY=YOUR_OPENAI_API_KEY

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ env*/
1616
venv/
1717
.venv*
1818
.env*
19+
!.env.sample
1920

2021
# codecov / coverage
2122
.coverage

README.md

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# App Agent & Assistant Template (Bolt for Python)
1+
# AI Agent App Template (Bolt for Python)
22

3-
This Bolt for Python template demonstrates how to build [Agents & Assistants](https://api.slack.com/docs/apps/ai) in Slack.
3+
This Bolt for Python template demonstrates how to build [AI Apps](https://docs.slack.dev/ai/) in Slack.
44

55
## Setup
66
Before getting started, make sure you have a development workspace where you have permissions to install apps. If you don’t have one setup, go ahead and [create one](https://slack.com/create).
@@ -17,20 +17,25 @@ Join the [Slack Developer Program](https://api.slack.com/developer-program) for
1717
4. Review the configuration and click *Create*
1818
5. Click *Install to Workspace* and *Allow* on the screen that follows. You'll then be redirected to the App Configuration dashboard.
1919

20-
#### Environment Variables
20+
### Environment Variables
21+
2122
Before you can run the app, you'll need to store some environment variables.
2223

23-
1. Open your app configuration page from this list, click **OAuth & Permissions** in the left hand menu, then copy the Bot User OAuth Token. You will store this in your environment as `SLACK_BOT_TOKEN`.
24-
2. Click **Basic Information** from the left hand menu and follow the steps in the App-Level Tokens section to create an app-level token with the `connections:write` scope. Copy this token. You will store this in your environment as `SLACK_APP_TOKEN`.
2524

25+
1. Rename `.env.sample` to `.env`.
26+
2. Open your apps setting page from [this list](https://api.slack.com/apps), click _OAuth & Permissions_ in the left hand menu, then copy the _Bot User OAuth Token_ into your `.env` file under `SLACK_BOT_TOKEN`.
27+
```zsh
28+
SLACK_BOT_TOKEN=YOUR_SLACK_BOT_TOKEN
29+
```
30+
3. Click _Basic Information_ from the left hand menu and follow the steps in the _App-Level Tokens_ section to create an app-level token with the `connections:write` scope. Copy that token into your `.env` as `SLACK_APP_TOKEN`.
2631
```zsh
27-
# Replace with your app token and bot token
28-
# For Windows OS, env:SLACK_BOT_TOKEN = <your-bot-token> works
29-
export SLACK_BOT_TOKEN=<your-bot-token>
30-
export SLACK_APP_TOKEN=<your-app-token>
31-
# This sample uses OpenAI's API by default, but you can switch to any other solution!
32-
export OPENAI_API_KEY=<your-openai-api-key>
32+
SLACK_APP_TOKEN=YOUR_SLACK_APP_TOKEN
3333
```
34+
4. Save your OpenAI key into `.env` under `OPENAI_API_KEY`.
35+
```zsh
36+
OPENAI_API_KEY=YOUR_OPEN_API_KEY
37+
```
38+
3439

3540
### Setup Your Local Project
3641
```zsh
@@ -51,6 +56,8 @@ pip install -r requirements.txt
5156
python3 app.py
5257
```
5358

59+
Start talking to the bot! Start a new DM or thread and click the feedback button when it responds.
60+
5461
#### Linting
5562
```zsh
5663
# Run flake8 from root directory for linting
@@ -72,15 +79,25 @@ black .
7279

7380
### `/listeners`
7481

75-
Every incoming request is routed to a "listener". Inside this directory, we group each listener based on the Slack Platform feature used, so `/listeners/events` handles incoming events, `/listeners/shortcuts` would handle incoming [Shortcuts](https://api.slack.com/interactivity/shortcuts) requests, and so on.
82+
Every incoming request is routed to a "listener". This directory groups each listener based on the Slack Platform feature used, so `/listeners/events` handles incoming events, `/listeners/shortcuts` would handle incoming [Shortcuts](https://docs.slack.dev/interactivity/implementing-shortcuts/) requests, and so on.
83+
84+
**`/listeners/assistant`**
85+
86+
Configures the new Slack Assistant features, providing a dedicated side panel UI for users to interact with the AI chatbot. This module includes:
87+
88+
`assistant.py`, which contains two listeners:
89+
* The `@assistant.thread_started` listener receives an event when users start new app thread.
90+
* The `@assistant.user_message` listener processes user messages in app threads or from the app **Chat** and **History** tab.
91+
92+
`ai/llm_caller.py`, which handles OpenAI API integration and message formatting. It includes the `call_llm()` function that sends conversation threads to OpenAI's models.
7693

7794
## App Distribution / OAuth
7895

7996
Only implement OAuth if you plan to distribute your application across multiple workspaces. A separate `app_oauth.py` file can be found with relevant OAuth settings.
8097

8198
When using OAuth, Slack requires a public URL where it can send requests. In this template app, we've used [`ngrok`](https://ngrok.com/download). Checkout [this guide](https://ngrok.com/docs#getting-started-expose) for setting it up.
8299

83-
Start `ngrok` to access the app on an external network and create a redirect URL for OAuth.
100+
Start `ngrok` to access the app on an external network and create a redirect URL for OAuth.
84101

85102
```
86103
ngrok http 3000

ai/llm_caller.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import os
2+
from typing import Dict, List
3+
4+
import openai
5+
from openai import Stream
6+
from openai.types.responses import ResponseStreamEvent
7+
8+
DEFAULT_SYSTEM_CONTENT = """
9+
You're an assistant in a Slack workspace.
10+
Users in the workspace will ask you to help them write something or to think better about a specific topic.
11+
You'll respond to those questions in a professional way.
12+
When you include markdown text, convert them to Slack compatible ones.
13+
When a prompt has Slack's special syntax like <@USER_ID> or <#CHANNEL_ID>, you must keep them as-is in your response.
14+
"""
15+
16+
17+
def call_llm(
18+
messages_in_thread: List[Dict[str, str]],
19+
system_content: str = DEFAULT_SYSTEM_CONTENT,
20+
) -> Stream[ResponseStreamEvent]:
21+
openai_client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
22+
messages = [{"role": "system", "content": system_content}]
23+
messages.extend(messages_in_thread)
24+
response = openai_client.responses.create(model="gpt-4o-mini", input=messages, stream=True)
25+
return response

app.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
1-
import os
21
import logging
2+
import os
3+
4+
from dotenv import load_dotenv
35

46
from slack_bolt import App
57
from slack_bolt.adapter.socket_mode import SocketModeHandler
8+
from slack_sdk import WebClient
69

710
from listeners import register_listeners
811

12+
# Load environment variables
13+
load_dotenv(dotenv_path=".env", override=False)
14+
915
# Initialization
1016
logging.basicConfig(level=logging.DEBUG)
11-
app = App(token=os.environ.get("SLACK_BOT_TOKEN"))
1217

18+
app = App(
19+
token=os.environ.get("SLACK_BOT_TOKEN"),
20+
client=WebClient(
21+
base_url=os.environ.get("SLACK_API_URL", "https://slack.com/api"),
22+
token=os.environ.get("SLACK_BOT_TOKEN"),
23+
),
24+
)
1325
# Register Listeners
1426
register_listeners(app)
1527

app_oauth.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import logging
22
import os
3+
34
from slack_bolt import App, BoltResponse
4-
from slack_bolt.oauth.callback_options import CallbackOptions, SuccessArgs, FailureArgs
5+
from slack_bolt.oauth.callback_options import CallbackOptions, FailureArgs, SuccessArgs
56
from slack_bolt.oauth.oauth_settings import OAuthSettings
6-
77
from slack_sdk.oauth.installation_store import FileInstallationStore
88
from slack_sdk.oauth.state_store import FileOAuthStateStore
99

listeners/__init__.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
from .assistant import assistant
1+
from slack_bolt import App
22

3+
from listeners import actions, assistant, events
34

4-
def register_listeners(app):
5-
# Using assistant middleware is the recommended way.
6-
app.assistant(assistant)
75

8-
# The following event listeners demonstrate how to implement the same on your own.
9-
# from listeners import events
10-
# events.register(app)
6+
def register_listeners(app: App):
7+
actions.register(app)
8+
assistant.register(app)
9+
events.register(app)

listeners/actions/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from slack_bolt import App
2+
3+
from .actions import handle_feedback
4+
5+
6+
def register(app: App):
7+
app.action("feedback")(handle_feedback)

listeners/actions/actions.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from logging import Logger
2+
3+
from slack_bolt import Ack
4+
from slack_sdk import WebClient
5+
6+
7+
def handle_feedback(ack: Ack, body: dict, client: WebClient, logger: Logger):
8+
"""
9+
Handles user feedback on AI-generated responses via thumbs up/down buttons.
10+
11+
Args:
12+
ack: Function to acknowledge the action request
13+
body: Action payload containing feedback details (message, channel, user, action value)
14+
client: Slack WebClient for making API calls
15+
logger: Logger instance for debugging and error tracking
16+
"""
17+
try:
18+
ack()
19+
message_ts = body["message"]["ts"]
20+
channel_id = body["channel"]["id"]
21+
feedback_type = body["actions"][0]["value"]
22+
is_positive = feedback_type == "good-feedback"
23+
24+
if is_positive:
25+
client.chat_postEphemeral(
26+
channel=channel_id,
27+
user=body["user"]["id"],
28+
thread_ts=message_ts,
29+
text="We're glad you found this useful.",
30+
)
31+
else:
32+
client.chat_postEphemeral(
33+
channel=channel_id,
34+
user=body["user"]["id"],
35+
thread_ts=message_ts,
36+
text="Sorry to hear that response wasn't up to par :slightly_frowning_face: Starting a new chat may help with AI mistakes and hallucinations.",
37+
)
38+
39+
logger.debug(f"Handled feedback: type={feedback_type}, message_ts={message_ts}")
40+
except Exception as error:
41+
logger.error(f":warning: Something went wrong! {error}")

listeners/assistant.py

Lines changed: 0 additions & 109 deletions
This file was deleted.

0 commit comments

Comments
 (0)