Skip to content

Commit b2badd1

Browse files
go
1 parent 51ac181 commit b2badd1

2 files changed

Lines changed: 181 additions & 10 deletions

File tree

docs/english/concepts/ai-apps.md

Lines changed: 180 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1+
---
2+
sidebar_label: Overview
3+
---
14

2-
# Using AI in Apps {#using-ai-in-apps}
5+
# Creating agents with Bolt
36

4-
The Slack platform offers features tailored for AI agents and assistants. Your apps can [utilize the `Assistant` class](#assistant) for a side-panel view designed with AI in mind, and they can utilize features applicable to messages throughout Slack, like [chat streaming](#text-streaming) and [feedback buttons](#adding-and-handling-feedback).
7+
The Slack platform offers features tailored for AI agents. Your agent can utilize features applicable to messages throughout Slack, like [chat streaming](#text-streaming) and [feedback buttons](#adding-and-handling-feedback). They can also [utilize the `Assistant` class](/bolt-python/concepts/assistant-class) for a side-panel view designed with AI in mind,
58

69
If you're unfamiliar with using these feature within Slack, you may want to read the [API docs on the subject](/ai/). Then come back here to implement them with Bolt!
710

8-
## Listening for events
11+
---
912

10-
Agents can be invoked throughout Slack, such as @mentions in channels.
13+
## Listening for user invocation
1114

12-
:::tip[Using the Assistant side panel]
15+
Agents can be invoked throughout Slack, such as @mentions in channels, messages to the app, and using the assistant side panel.
1316

14-
The Assistant side panel requires additional setup. See the [Assistant class guide](/concepts/assistant-class).
15-
:::
17+
<Tabs>
18+
<TabItem value="appmention" label = "app_mention">
1619

1720
```python
1821
import re
@@ -63,9 +66,172 @@ def handle_app_mentioned(
6366
...
6467
```
6568

66-
## Setting assistant status {#setting-assistant-status}
69+
<TabItem>
70+
<TabItem value="message" label = "Message">
71+
72+
```python
73+
from logging import Logger
74+
75+
from slack_bolt.context.async_context import AsyncBoltContext
76+
from slack_bolt.context.say.async_say import AsyncSay
77+
from slack_bolt.context.say_stream.async_say_stream import AsyncSayStream
78+
from slack_bolt.context.set_status.async_set_status import AsyncSetStatus
79+
from slack_sdk.web.async_client import AsyncWebClient
80+
81+
from agent import CaseyDeps, run_casey_agent
82+
from thread_context import session_store
83+
from listeners.views.feedback_builder import build_feedback_blocks
84+
85+
86+
async def handle_message(
87+
client: AsyncWebClient,
88+
context: AsyncBoltContext,
89+
event: dict,
90+
logger: Logger,
91+
say: AsyncSay,
92+
say_stream: AsyncSayStream,
93+
set_status: AsyncSetStatus,
94+
):
95+
"""Handle messages sent to Casey via DM or in threads the bot is part of."""
96+
# Issue submissions are posted by the bot with metadata so the message
97+
# handler can run the agent on behalf of the original user.
98+
is_issue_submission = (
99+
event.get("metadata", {}).get("event_type") == "issue_submission"
100+
)
101+
102+
# Skip message subtypes (edits, deletes, etc.) and bot messages that
103+
# are not issue submissions.
104+
if event.get("subtype"):
105+
return
106+
if event.get("bot_id") and not is_issue_submission:
107+
return
108+
109+
is_dm = event.get("channel_type") == "im"
110+
is_thread_reply = event.get("thread_ts") is not None
111+
112+
if is_dm:
113+
pass
114+
elif is_thread_reply:
115+
# Channel thread replies are handled only if the bot is already engaged
116+
session = session_store.get_session(context.channel_id, event["thread_ts"])
117+
if session is None:
118+
return
119+
else:
120+
# Top-level channel messages are handled by app_mentioned
121+
return
122+
123+
try:
124+
channel_id = context.channel_id
125+
text = event.get("text", "")
126+
thread_ts = event.get("thread_ts") or event["ts"]
127+
128+
# Get session ID for conversation context
129+
existing_session_id = session_store.get_session(channel_id, thread_ts)
130+
131+
# Add eyes reaction only to the first message (DMs only — channel
132+
# threads already have the reaction from the initial app_mention)
133+
if is_dm and not existing_session_id:
134+
await client.reactions_add(
135+
channel=channel_id,
136+
timestamp=event["ts"],
137+
name="eyes",
138+
)
139+
140+
# Set assistant thread status with loading messages
141+
await set_status(
142+
status="Thinking...",
143+
loading_messages=[
144+
"Teaching the hamsters to type faster…",
145+
"Untangling the internet cables…",
146+
"Consulting the office goldfish…",
147+
"Polishing up the response just for you…",
148+
"Convincing the AI to stop overthinking…",
149+
],
150+
)
151+
152+
# For issue submissions the bot posted the message, so the real
153+
# user_id comes from the metadata rather than the event context.
154+
if is_issue_submission:
155+
user_id = event["metadata"]["event_payload"]["user_id"]
156+
else:
157+
user_id = context.user_id
158+
159+
# Run the agent with deps for tool access
160+
deps = CaseyDeps(
161+
client=client,
162+
user_id=user_id,
163+
channel_id=channel_id,
164+
thread_ts=thread_ts,
165+
message_ts=event["ts"],
166+
)
167+
response_text, new_session_id = await run_casey_agent(
168+
text, session_id=existing_session_id, deps=deps
169+
)
170+
171+
# Stream response in thread with feedback buttons
172+
streamer = await say_stream()
173+
await streamer.append(markdown_text=response_text)
174+
feedback_blocks = build_feedback_blocks()
175+
await streamer.stop(blocks=feedback_blocks)
176+
177+
# Store session ID for future context
178+
if new_session_id:
179+
session_store.set_session(channel_id, thread_ts, new_session_id)
180+
181+
except Exception as e:
182+
logger.exception(f"Failed to handle message: {e}")
183+
await say(
184+
text=f":warning: Something went wrong! ({e})",
185+
thread_ts=event.get("thread_ts") or event.get("ts"),
186+
)
187+
```
188+
189+
</TabItem>
190+
191+
<TabItem value="assistant" label = "Assistant thread">
192+
193+
194+
:::tip[Using the Assistant side panel]
195+
196+
The Assistant side panel requires additional setup. See the [Assistant class guide](/bolt-python/concepts/assistant-class).
197+
:::
198+
199+
200+
```py
201+
from logging import Logger
202+
203+
from slack_bolt.context.set_suggested_prompts.async_set_suggested_prompts import (
204+
AsyncSetSuggestedPrompts,
205+
)
206+
207+
SUGGESTED_PROMPTS = [
208+
{"title": "Reset Password", "message": "I need to reset my password"},
209+
{"title": "Request Access", "message": "I need access to a system or tool"},
210+
{"title": "Network Issues", "message": "I'm having network connectivity issues"},
211+
]
212+
213+
214+
async def handle_assistant_thread_started(
215+
set_suggested_prompts: AsyncSetSuggestedPrompts, logger: Logger
216+
):
217+
"""Handle assistant thread started events by setting suggested prompts."""
218+
try:
219+
await set_suggested_prompts(
220+
prompts=SUGGESTED_PROMPTS,
221+
title="How can I help you today?",
222+
)
223+
except Exception as e:
224+
logger.exception(f"Failed to handle assistant thread started: {e}")
225+
```
226+
227+
</TabItem>
228+
</Tabs>
67229

68-
Your app can show users action is happening behind the scenes by setting the thread status.
230+
---
231+
232+
## Setting status {#setting-assistant-status}
233+
234+
Your app can show its users action is happening behind the scenes by setting its thread status.
69235

70236
```python
71237
def handle_app_mentioned(
@@ -84,6 +250,8 @@ def handle_app_mentioned(
84250
)
85251
```
86252

253+
---
254+
87255
## Streaming messages {#text-streaming}
88256

89257
You can have your app's messages stream in to replicate conventional agent behavior. Bolt for Python provides a `say_stream` utility as a listener argument available for `app.event` and `app.message` listeners.
@@ -112,6 +280,8 @@ def handle_message(say_stream: SayStream):
112280
streamer.stop()
113281
```
114282

283+
---
284+
115285
## Adding and handling feedback {#adding-and-handling-feedback}
116286

117287
You can use [feedback buttons block element](/reference/block-kit/block-elements/feedback-buttons-element/) to allow users to immediately provide feedback regarding the app's responses. Here's what the feedback buttons look like from the Support Agent sample app:
@@ -203,6 +373,7 @@ def handle_feedback_button(
203373
logger.exception(f"Failed to handle feedback: {e}")
204374
```
205375

376+
---
206377

207378
## Full example
208379

docs/english/concepts/assistant-class.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
## The `Assistant` class instance {#assistant}
1+
# The Assistant class
22

33
:::info[Some features within this guide require a paid plan]
44
If you don't have a paid workspace for development, you can join the [Developer Program](https://api.slack.com/developer-program) and provision a sandbox with access to all Slack features for free.

0 commit comments

Comments
 (0)