Skip to content

Commit a2d2311

Browse files
docs: merge conflict
2 parents df42514 + 42af666 commit a2d2311

File tree

7 files changed

+89
-27
lines changed

7 files changed

+89
-27
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ jobs:
1111
runs-on: ubuntu-20.04
1212
timeout-minutes: 10
1313
strategy:
14+
fail-fast: false
1415
matrix:
1516
python-version:
1617
- "3.6"

docs/content/concepts/assistant.md

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,38 @@ lang: en
44
slug: /concepts/assistant
55
---
66

7-
This guide focuses on how to implement Agents & Assistants using Bolt. For general information about the feature, please refer to the [API documentation](https://api.slack.com/docs/apps/ai).
7+
:::info This feature requires a paid plan
8+
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.
9+
:::
810

9-
To get started, enable the **Agents & Assistants** feature on [the app configuration page](https://api.slack.com/apps). Add [`assistant:write`](https://api.slack.com/scopes/assistant:write), [`chat:write`](https://api.slack.com/scopes/chat:write), and [`im:history`](https://api.slack.com/scopes/im:history) to the **bot** scopes on the **OAuth & Permissions** page. Make sure to subscribe to [`assistant_thread_started`](https://api.slack.com/events/assistant_thread_started), [`assistant_thread_context_changed`](https://api.slack.com/events/assistant_thread_context_changed), and [`message.im`](https://api.slack.com/events/message.im) events on the **Event Subscriptions** page.
11+
Agents and assistants comprise a new messaging experience for Slack. If you're unfamiliar with using agents and assistants within Slack, you'll want to read the [API documentation on the subject](https://api.slack.com/docs/apps/ai). Then come back here to implement them with Bolt!
1012

11-
Please note that this feature requires a paid plan. 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.
13+
## Configuring your app to support assistants {#configuring-your-app}
14+
15+
1. Within [App Settings](https://api.slack.com/apps), enable the **Agents & Assistants** feature.
16+
17+
2. Within the App Settings **OAuth & Permissions** page, add the following scopes:
18+
* [`assistant:write`](https://api.slack.com/scopes/assistant:write)
19+
* [`chat:write`](https://api.slack.com/scopes/chat:write)
20+
* [`im:history`](https://api.slack.com/scopes/im:history)
21+
22+
3. Within the App Settings **Event Subscriptions** page, subscribe to the following events:
23+
* [`assistant_thread_started`](https://api.slack.com/events/assistant_thread_started)
24+
* [`assistant_thread_context_changed`](https://api.slack.com/events/assistant_thread_context_changed)
25+
* [`message.im`](https://api.slack.com/events/message.im)
26+
27+
:::info
28+
You _could_ implement your own assistants by [listening](event-listening) for the `assistant_thread_started`, `assistant_thread_context_changed`, and `message.im` events (see implementation details below). That being said, using the `Assistant` class will streamline the process. And we already wrote this nice guide for you!
29+
:::
30+
31+
## The `Assistant` class instance {#assistant-class}
32+
33+
The `Assistant` class can be used to handle the incoming events expected from a user interacting with an assistant in Slack. A typical flow would look like:
34+
35+
1. [The user starts a thread](#handling-a-new-thread). The `Assistant` class handles the incoming [`assistant_thread_started`](https://api.slack.com/events/assistant_thread_started) event.
36+
2. [The thread context may change at any point](#handling-thread-context-changes). The Assistant class can handle any incoming [`assistant_thread_context_changed`](https://api.slack.com/events/assistant_thread_context_changed) events. The class also provides a default context store to keep track of thread context changes as the user moves through Slack.
37+
3. [The user responds](#handling-the-user-response). The Assistant class handles the incoming [`message.im`](https://api.slack.com/events/message.im) event.
1238

13-
To handle assistant thread interactions with humans, although you can implement your agents [using `app.event(...)` listeners](event-listening) for `assistant_thread_started`, `assistant_thread_context_changed`, and `message` events, Bolt offers a simpler approach. You just need to create an `Assistant` instance, attach the needed event handlers to it, and then add the assistant to your `App` instance.
1439

1540
```python
1641
assistant = Assistant()
@@ -72,31 +97,27 @@ def respond_in_assistant_thread(
7297
app.use(assistant)
7398
```
7499

75-
Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn the available listener arguments.
100+
While the `assistant_thread_started` and `assistant_thread_context_changed` events do provide Slack-client thread context information, the `message.im` event does not. Any subsequent user message events won't contain thread context data. For that reason, Bolt not only provides a way to store thread context — the `threadContextStore` property — but it also provides an instance that is utilized by default. This implementation relies on storing and retrieving [message metadata](https://api.slack.com/metadata/using) as the user interacts with the assistant.
76101

77-
When a user opens an Assistant thread while in a channel, the channel information is stored as the thread's `AssistantThreadContext` data. You can access this information by using the `get_thread_context` utility. The reason Bolt provides this utility is that the most recent thread context information is not included in the subsequent user message event payload data. Therefore, an app must store the context data when it is changed so that the app can refer to the data in message event listeners.
102+
If you do provide your own `threadContextStore` property, it must feature `get` and `save` methods.
78103

79-
When the user switches channels, the `assistant_thread_context_changed` event will be sent to your app. If you use the built-in `Assistant` middleware without any custom configuration (like the above code snippet does), the updated context data is automatically saved as message metadata of the first reply from the assistant bot.
104+
:::tip
105+
Refer to the [module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn the available listener arguments.
106+
:::
80107

81-
As long as you use the built-in approach, you don't need to store the context data within a datastore. The downside of this default behavior is the overhead of additional calls to the Slack API. These calls include those to `conversations.history` which are used to look up the stored message metadata that contains the thread context (via `get_thread_context`).
108+
## Handling a new thread {#handling-a-new-thread}
82109

83-
To store context elsewhere, pass a custom `AssistantThreadContextStore` implementation to the `Assistant` constructor. We provide `FileAssistantThreadContextStore`, which is a reference implementation that uses the local file system:
110+
When the user opens a new thread with your assistant, the [`assistant_thread_started`](https://api.slack.com/events/assistant_thread_started) event will be sent to your app.
84111

85-
```python
86-
# You can use your own thread_context_store if you want
87-
from slack_bolt import FileAssistantThreadContextStore
88-
assistant = Assistant(thread_context_store=FileAssistantThreadContextStore())
89-
```
90-
91-
Since this reference implementation relies on local files, it's not advised for use in production. For production apps, we recommend creating a class that inherits `AssistantThreadContextStore`.
92-
93-
If you want to check full working example app, you can check [our sample repository](https://github.com/slack-samples/bolt-python-assistant-template) on GitHub.
112+
:::tip
113+
When a user opens an assistant thread while in a channel, the channel info is stored as the thread's `AssistantThreadContext` data. You can grab that info by using the `get_thread_context` utility, as subsequent user message event payloads won't include the channel info.
114+
:::
94115

95-
## Block Kit interactions in the assistant thread
116+
### Block Kit interactions in the assistant thread {#block-kit-interactions}
96117

97118
For advanced use cases, Block Kit buttons may be used instead of suggested prompts, as well as the sending of messages with structured [metadata](https://api.slack.com/metadata) to trigger subsequent interactions with the user.
98119

99-
For example, an app can display a button like "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, the purpose of the summary, etc.), the app can handle that information and post a message that describes the request with structured metadata.
120+
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.
100121

101122
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:
102123

@@ -109,8 +130,6 @@ app = App(
109130

110131
assistant = Assistant()
111132

112-
# Refer to https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html to learn available listener arguments
113-
114133
@assistant.thread_started
115134
def start_assistant_thread(say: Say):
116135
say(
@@ -217,7 +236,37 @@ def respond_to_bot_messages(logger: logging.Logger, set_status: SetStatus, say:
217236

218237
except Exception as e:
219238
logger.exception(f"Failed to respond to an inquiry: {e}")
239+
...
240+
```
241+
242+
## Handling thread context changes {#handling-thread-context-changes}
243+
244+
When the user switches channels, the [`assistant_thread_context_changed`](https://api.slack.com/events/assistant_thread_context_changed) event will be sent to your app.
220245

246+
If you use the built-in `Assistant` middleware without any custom configuration, the updated context data is automatically saved as [message metadata](https://api.slack.com/metadata/using) of the first reply from the assistant bot.
247+
248+
As long as you use the built-in approach, you don't need to store the context data within a datastore. The downside of this default behavior is the overhead of additional calls to the Slack API. These calls include those to `conversations.history`, which are used to look up the stored message metadata that contains the thread context (via `get_thread_context`).
249+
250+
To store context elsewhere, pass a custom `AssistantThreadContextStore` implementation to the `Assistant` constructor. We provide `FileAssistantThreadContextStore`, which is a reference implementation that uses the local file system. Since this reference implementation relies on local files, it's not advised for use in production. For production apps, we recommend creating a class that inherits `AssistantThreadContextStore`.
251+
252+
```python
253+
from slack_bolt import FileAssistantThreadContextStore
254+
assistant = Assistant(thread_context_store=FileAssistantThreadContextStore())
255+
```
256+
257+
## Handling the user response {#handling-the-user-response}
258+
259+
When the user messages your assistant, the [`message.im`](https://api.slack.com/events/message.im) event will be sent to your app.
260+
261+
Messages sent to the assistant do not contain a [subtype](https://api.slack.com/events/message#subtypes) and must be deduced based on their shape and any provided [message metadata](https://api.slack.com/metadata/using).
262+
263+
There are three utilities that are particularly useful in curating the user experience:
264+
* [`say`](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/#slack_bolt.Say)
265+
* [`setTitle`](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/#slack_bolt.SetTitle)
266+
* [`setStatus`](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/#slack_bolt.SetStatus)
267+
268+
```python
269+
...
221270
# This listener is invoked when the human user posts a reply
222271
@assistant.user_message
223272
def respond_to_user_messages(logger: logging.Logger, set_status: SetStatus, say: Say):
@@ -230,4 +279,12 @@ def respond_to_user_messages(logger: logging.Logger, set_status: SetStatus, say:
230279

231280
# Enable this assistant middleware in your Bolt app
232281
app.use(assistant)
282+
```
283+
284+
## Full example: Assistant Template {#full-example}
285+
286+
Below is the `assistant.py` listener file of the [Assistant Template repo](https://github.com/slack-samples/bolt-python-assistant-template) we've created for you to build off of.
287+
288+
```py reference title="assistant.py"
289+
https://github.com/slack-samples/bolt-python-assistant-template/blob/main/listeners/assistant.py
233290
```

requirements/adapter.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pyramid>=1,<3
1616
sanic>=20,<21; python_version=="3.6"
1717
sanic>=21,<24; python_version>"3.6" and python_version<="3.8"
1818
sanic>=21,<25; python_version>"3.8"
19-
starlette>=0.14,<1
19+
starlette>=0.19.1,<1
2020
tornado>=6,<7
2121
uvicorn<1 # The oldest version can vary among Python runtime versions
2222
gunicorn>=20,<24

tests/adapter_tests/starlette/test_fastapi.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,8 @@ async def endpoint(req: Request):
206206
return await app_handler.handle(req)
207207

208208
client = TestClient(api)
209-
response = client.get("/slack/install", allow_redirects=False)
209+
client.follow_redirects = False
210+
response = client.get("/slack/install")
210211
assert response.status_code == 200
211212
assert response.headers.get("content-type") == "text/html; charset=utf-8"
212213
assert "https://slack.com/oauth/v2/authorize?state=" in response.text

tests/adapter_tests/starlette/test_starlette.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,8 @@ async def endpoint(req: Request):
215215
routes=[Route("/slack/install", endpoint=endpoint, methods=["GET"])],
216216
)
217217
client = TestClient(api)
218-
response = client.get("/slack/install", allow_redirects=False)
218+
client.follow_redirects = False
219+
response = client.get("/slack/install")
219220
assert response.status_code == 200
220221
assert response.headers.get("content-type") == "text/html; charset=utf-8"
221222
assert "https://slack.com/oauth/v2/authorize?state=" in response.text

tests/adapter_tests_async/test_async_fastapi.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,8 @@ async def endpoint(req: Request):
206206
return await app_handler.handle(req)
207207

208208
client = TestClient(api)
209-
response = client.get("/slack/install", allow_redirects=False)
209+
client.follow_redirects = False
210+
response = client.get("/slack/install")
210211
assert response.status_code == 200
211212
assert response.headers.get("content-type") == "text/html; charset=utf-8"
212213
assert response.headers.get("content-length") == "607"

tests/adapter_tests_async/test_async_starlette.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,8 @@ async def endpoint(req: Request):
216216
)
217217

218218
client = TestClient(api)
219-
response = client.get("/slack/install", allow_redirects=False)
219+
client.follow_redirects = False
220+
response = client.get("/slack/install")
220221
assert response.status_code == 200
221222
assert response.headers.get("content-type") == "text/html; charset=utf-8"
222223
assert response.headers.get("content-length") == "607"

0 commit comments

Comments
 (0)