Skip to content

Commit 221397a

Browse files
moving it around
1 parent e5bf232 commit 221397a

File tree

1 file changed

+134
-134
lines changed

1 file changed

+134
-134
lines changed

docs/english/concepts/ai-apps.md

Lines changed: 134 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
# Using AI in Apps
2+
# Using AI in Apps {#using-ai-in-apps}
33

44
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, or they can utilize features applicable to messages throughout Slack, like [chat streaming](#text-streaming) and [feedback buttons](#adding-and-handling-feedback).
55

@@ -63,7 +63,7 @@ If you do provide your own `threadContextStore` property, it must feature `get`
6363
:::tip[Refer to the [reference docs](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments.]
6464
:::
6565

66-
### Configuring your app to support the `Assistant` class
66+
### Configuring your app to support the `Assistant` class {#configuring-assistant-class}
6767

6868
1. Within [App Settings](https://api.slack.com/apps), enable the **Agents & AI Apps** feature.
6969

@@ -122,7 +122,7 @@ def start_assistant_thread(
122122

123123
You can send more complex messages to the user — see [Sending Block Kit alongside messages](#block-kit-interactions) for more info.
124124

125-
## Handling thread context changes {#handling-thread-context-changes}
125+
### Handling thread context changes {#handling-thread-context-changes}
126126

127127
When the user switches channels, the [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed) event will be sent to your app.
128128

@@ -137,7 +137,7 @@ from slack_bolt import FileAssistantThreadContextStore
137137
assistant = Assistant(thread_context_store=FileAssistantThreadContextStore())
138138
```
139139

140-
## Handling the user response {#handling-user-response}
140+
### Handling the user response {#handling-user-response}
141141

142142
When the user messages your app, the [`message.im`](/reference/events/message.im) event will be sent to your app.
143143

@@ -205,6 +205,134 @@ def respond_in_assistant_thread(
205205
app.use(assistant)
206206
```
207207

208+
### Sending Block Kit alongside messages {#block-kit-interactions}
209+
210+
For advanced use cases, Block Kit buttons may be used instead of suggested prompts, as well as the sending of messages with structured [metadata](/messaging/message-metadata/) to trigger subsequent interactions with the user.
211+
212+
For example, an app can display a button such as "Summarize the referring channel" in the initial reply. When the user clicks the button and submits detailed information (such as the number of messages, days to check, purpose of the summary, etc.), the app can handle that information and post a message that describes the request with structured metadata.
213+
214+
By default, apps can't respond to their own bot messages (Bolt prevents infinite loops by default). However, if you pass `ignoring_self_assistant_message_events_enabled=False` to the `App` constructor and add a `bot_message` listener to your `Assistant` middleware, your app can continue processing the request as shown below:
215+
216+
```python
217+
app = App(
218+
token=os.environ["SLACK_BOT_TOKEN"],
219+
# This must be set to handle bot message events
220+
ignoring_self_assistant_message_events_enabled=False,
221+
)
222+
223+
assistant = Assistant()
224+
225+
@assistant.thread_started
226+
def start_assistant_thread(say: Say):
227+
say(
228+
text=":wave: Hi, how can I help you today?",
229+
blocks=[
230+
{
231+
"type": "section",
232+
"text": {"type": "mrkdwn", "text": ":wave: Hi, how can I help you today?"},
233+
},
234+
{
235+
"type": "actions",
236+
"elements": [
237+
# You can have multiple buttons here
238+
{
239+
"type": "button",
240+
"action_id": "assistant-generate-random-numbers",
241+
"text": {"type": "plain_text", "text": "Generate random numbers"},
242+
"value": "clicked",
243+
},
244+
],
245+
},
246+
],
247+
)
248+
249+
# This listener is invoked when the above button is clicked
250+
@app.action("assistant-generate-random-numbers")
251+
def configure_random_number_generation(ack: Ack, client: WebClient, body: dict):
252+
ack()
253+
client.views_open(
254+
trigger_id=body["trigger_id"],
255+
view={
256+
"type": "modal",
257+
"callback_id": "configure_assistant_summarize_channel",
258+
"title": {"type": "plain_text", "text": "My Assistant"},
259+
"submit": {"type": "plain_text", "text": "Submit"},
260+
"close": {"type": "plain_text", "text": "Cancel"},
261+
# Relay the assistant thread information to app.view listener
262+
"private_metadata": json.dumps(
263+
{
264+
"channel_id": body["channel"]["id"],
265+
"thread_ts": body["message"]["thread_ts"],
266+
}
267+
),
268+
"blocks": [
269+
{
270+
"type": "input",
271+
"block_id": "num",
272+
"label": {"type": "plain_text", "text": "# of outputs"},
273+
# You can have this kind of predefined input from a user instead of parsing human text
274+
"element": {
275+
"type": "static_select",
276+
"action_id": "input",
277+
"placeholder": {"type": "plain_text", "text": "How many numbers do you need?"},
278+
"options": [
279+
{"text": {"type": "plain_text", "text": "5"}, "value": "5"},
280+
{"text": {"type": "plain_text", "text": "10"}, "value": "10"},
281+
{"text": {"type": "plain_text", "text": "20"}, "value": "20"},
282+
],
283+
"initial_option": {"text": {"type": "plain_text", "text": "5"}, "value": "5"},
284+
},
285+
}
286+
],
287+
},
288+
)
289+
290+
# This listener is invoked when the above modal is submitted
291+
@app.view("configure_assistant_summarize_channel")
292+
def receive_random_number_generation_details(ack: Ack, client: WebClient, payload: dict):
293+
ack()
294+
num = payload["state"]["values"]["num"]["input"]["selected_option"]["value"]
295+
thread = json.loads(payload["private_metadata"])
296+
297+
# Post a bot message with structured input data
298+
# The following assistant.bot_message will continue processing
299+
# If you prefer processing this request within this listener, it also works!
300+
# If you don't need bot_message listener, no need to set ignoring_self_assistant_message_events_enabled=False
301+
client.chat_postMessage(
302+
channel=thread["channel_id"],
303+
thread_ts=thread["thread_ts"],
304+
text=f"OK, you need {num} numbers. I will generate it shortly!",
305+
metadata={
306+
"event_type": "assistant-generate-random-numbers",
307+
"event_payload": {"num": int(num)},
308+
},
309+
)
310+
311+
# This listener is invoked whenever your app's bot user posts a message
312+
@assistant.bot_message
313+
def respond_to_bot_messages(logger: logging.Logger, set_status: SetStatus, say: Say, payload: dict):
314+
try:
315+
if payload.get("metadata", {}).get("event_type") == "assistant-generate-random-numbers":
316+
# Handle the above random-number-generation request
317+
set_status("is generating an array of random numbers...")
318+
time.sleep(1)
319+
nums: Set[str] = set()
320+
num = payload["metadata"]["event_payload"]["num"]
321+
while len(nums) < num:
322+
nums.add(str(random.randint(1, 100)))
323+
say(f"Here you are: {', '.join(nums)}")
324+
else:
325+
# nothing to do for this bot message
326+
# If you want to add more patterns here, be careful not to cause infinite loop messaging
327+
pass
328+
329+
except Exception as e:
330+
logger.exception(f"Failed to respond to an inquiry: {e}")
331+
...
332+
```
333+
334+
See the [_Adding and handling feedback_](#adding-and-handling-feedback) section for adding feedback buttons with Block Kit.
335+
208336
## Text streaming in messages {#text-streaming}
209337

210338
Three Web API methods work together to provide users a text streaming experience:
@@ -285,7 +413,7 @@ def respond_in_assistant_thread(
285413
say(f":warning: Something went wrong! ({e})")
286414
```
287415

288-
## Adding and handling feedback
416+
## Adding and handling feedback {#adding-and-handling-feedback}
289417

290418
Use the [feedback buttons block element](/reference/block-kit/block-elements/feedback-buttons-element/) to allow users to immediately provide feedback regarding your app's responses. Here's a quick example:
291419

@@ -379,134 +507,6 @@ def handle_feedback(ack, body, client, logger: logging.Logger):
379507
logger.error(f":warning: Something went wrong! {error}")
380508
```
381509

382-
Keep reading for more Block Kit possibilities for your AI-enabled app.
383-
384-
## Sending Block Kit alongside messages {#block-kit-interactions}
385-
386-
For advanced use cases, Block Kit buttons may be used instead of suggested prompts, as well as the sending of messages with structured [metadata](/messaging/message-metadata/) to trigger subsequent interactions with the user.
387-
388-
For example, an app can display a button such as "Summarize the referring channel" in the initial reply. When the user clicks the button and submits detailed information (such as the number of messages, days to check, purpose of the summary, etc.), the app can handle that information and post a message that describes the request with structured metadata.
389-
390-
By default, apps can't respond to their own bot messages (Bolt prevents infinite loops by default). However, if you pass `ignoring_self_assistant_message_events_enabled=False` to the `App` constructor and add a `bot_message` listener to your `Assistant` middleware, your app can continue processing the request as shown below:
391-
392-
```python
393-
app = App(
394-
token=os.environ["SLACK_BOT_TOKEN"],
395-
# This must be set to handle bot message events
396-
ignoring_self_assistant_message_events_enabled=False,
397-
)
398-
399-
assistant = Assistant()
400-
401-
@assistant.thread_started
402-
def start_assistant_thread(say: Say):
403-
say(
404-
text=":wave: Hi, how can I help you today?",
405-
blocks=[
406-
{
407-
"type": "section",
408-
"text": {"type": "mrkdwn", "text": ":wave: Hi, how can I help you today?"},
409-
},
410-
{
411-
"type": "actions",
412-
"elements": [
413-
# You can have multiple buttons here
414-
{
415-
"type": "button",
416-
"action_id": "assistant-generate-random-numbers",
417-
"text": {"type": "plain_text", "text": "Generate random numbers"},
418-
"value": "clicked",
419-
},
420-
],
421-
},
422-
],
423-
)
424-
425-
# This listener is invoked when the above button is clicked
426-
@app.action("assistant-generate-random-numbers")
427-
def configure_random_number_generation(ack: Ack, client: WebClient, body: dict):
428-
ack()
429-
client.views_open(
430-
trigger_id=body["trigger_id"],
431-
view={
432-
"type": "modal",
433-
"callback_id": "configure_assistant_summarize_channel",
434-
"title": {"type": "plain_text", "text": "My Assistant"},
435-
"submit": {"type": "plain_text", "text": "Submit"},
436-
"close": {"type": "plain_text", "text": "Cancel"},
437-
# Relay the assistant thread information to app.view listener
438-
"private_metadata": json.dumps(
439-
{
440-
"channel_id": body["channel"]["id"],
441-
"thread_ts": body["message"]["thread_ts"],
442-
}
443-
),
444-
"blocks": [
445-
{
446-
"type": "input",
447-
"block_id": "num",
448-
"label": {"type": "plain_text", "text": "# of outputs"},
449-
# You can have this kind of predefined input from a user instead of parsing human text
450-
"element": {
451-
"type": "static_select",
452-
"action_id": "input",
453-
"placeholder": {"type": "plain_text", "text": "How many numbers do you need?"},
454-
"options": [
455-
{"text": {"type": "plain_text", "text": "5"}, "value": "5"},
456-
{"text": {"type": "plain_text", "text": "10"}, "value": "10"},
457-
{"text": {"type": "plain_text", "text": "20"}, "value": "20"},
458-
],
459-
"initial_option": {"text": {"type": "plain_text", "text": "5"}, "value": "5"},
460-
},
461-
}
462-
],
463-
},
464-
)
465-
466-
# This listener is invoked when the above modal is submitted
467-
@app.view("configure_assistant_summarize_channel")
468-
def receive_random_number_generation_details(ack: Ack, client: WebClient, payload: dict):
469-
ack()
470-
num = payload["state"]["values"]["num"]["input"]["selected_option"]["value"]
471-
thread = json.loads(payload["private_metadata"])
472-
473-
# Post a bot message with structured input data
474-
# The following assistant.bot_message will continue processing
475-
# If you prefer processing this request within this listener, it also works!
476-
# If you don't need bot_message listener, no need to set ignoring_self_assistant_message_events_enabled=False
477-
client.chat_postMessage(
478-
channel=thread["channel_id"],
479-
thread_ts=thread["thread_ts"],
480-
text=f"OK, you need {num} numbers. I will generate it shortly!",
481-
metadata={
482-
"event_type": "assistant-generate-random-numbers",
483-
"event_payload": {"num": int(num)},
484-
},
485-
)
486-
487-
# This listener is invoked whenever your app's bot user posts a message
488-
@assistant.bot_message
489-
def respond_to_bot_messages(logger: logging.Logger, set_status: SetStatus, say: Say, payload: dict):
490-
try:
491-
if payload.get("metadata", {}).get("event_type") == "assistant-generate-random-numbers":
492-
# Handle the above random-number-generation request
493-
set_status("is generating an array of random numbers...")
494-
time.sleep(1)
495-
nums: Set[str] = set()
496-
num = payload["metadata"]["event_payload"]["num"]
497-
while len(nums) < num:
498-
nums.add(str(random.randint(1, 100)))
499-
say(f"Here you are: {', '.join(nums)}")
500-
else:
501-
# nothing to do for this bot message
502-
# If you want to add more patterns here, be careful not to cause infinite loop messaging
503-
pass
504-
505-
except Exception as e:
506-
logger.exception(f"Failed to respond to an inquiry: {e}")
507-
...
508-
```
509-
510-
## Full example: App Agent Template
510+
## Full example: App Agent Template {#app-agent-template}
511511

512512
Want to see the functionality described throughout this guide in action? We've created a [App Agent Template](https://github.com/slack-samples/bolt-python-assistant-template) repo for you to build off of.

0 commit comments

Comments
 (0)