Skip to content

Commit fc5bbc1

Browse files
zimeglukegalbraithrussellsrtaalejmwbrooks
authored
feat: add ai-enabled features text streaming methods, feedback blocks, and loading state (#1387)
Co-authored-by: Luke Russell <[email protected]> Co-authored-by: Maria Alejandra <[email protected]> Co-authored-by: Michael Brooks <[email protected]>
1 parent 95150b8 commit fc5bbc1

29 files changed

+693
-146
lines changed

docs/english/concepts/ai-apps.md

Lines changed: 317 additions & 91 deletions
Large diffs are not rendered by default.

docs/english/concepts/message-sending.md

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Within your listener function, `say()` is available whenever there is an associa
55
In the case that you'd like to send a message outside of a listener or you want to do something more advanced (like handle specific errors), you can call `client.chat_postMessage` [using the client attached to your Bolt instance](/tools/bolt-python/concepts/web-api).
66

77
Refer to [the module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments.
8+
89
```python
910
# Listens for messages containing "knock knock" and responds with an italicized "who's there?"
1011
@app.message("knock knock")
@@ -38,4 +39,62 @@ def show_datepicker(event, say):
3839
blocks=blocks,
3940
text="Pick a date for me to remind you"
4041
)
41-
```
42+
```
43+
44+
## Streaming messages {#streaming-messages}
45+
46+
You can have your app's messages stream in to replicate conventional AI chatbot behavior. This is done through three Web API methods:
47+
48+
* [`chat_startStream`](/reference/methods/chat.startstream)
49+
* [`chat_appendStream`](/reference/methods/chat.appendstream)
50+
* [`chat_stopStream`](/reference/methods/chat.stopstream)
51+
52+
The Python Slack SDK provides a [`chat_stream()`](https://docs.slack.dev/tools/python-slack-sdk/reference/web/client.html#slack_sdk.web.client.WebClient.chat_stream) helper utility to streamline calling these methods. Here's an excerpt from our [Assistant template app](https://github.com/slack-samples/bolt-python-assistant-template):
53+
54+
```python
55+
streamer = client.chat_stream(
56+
channel=channel_id,
57+
recipient_team_id=team_id,
58+
recipient_user_id=user_id,
59+
thread_ts=thread_ts,
60+
)
61+
62+
# Loop over OpenAI response stream
63+
# https://platform.openai.com/docs/api-reference/responses/create
64+
for event in returned_message:
65+
if event.type == "response.output_text.delta":
66+
streamer.append(markdown_text=f"{event.delta}")
67+
else:
68+
continue
69+
70+
feedback_block = create_feedback_block()
71+
streamer.stop(blocks=feedback_block)
72+
```
73+
74+
In that example, a [feedback buttons](/reference/block-kit/block-elements/feedback-buttons-element) block element is passed to `streamer.stop` to provide feedback buttons to the user at the bottom of the message. Interaction with these buttons will send a block action event to your app to receive the feedback.
75+
76+
```python
77+
def create_feedback_block() -> List[Block]:
78+
blocks: List[Block] = [
79+
ContextActionsBlock(
80+
elements=[
81+
FeedbackButtonsElement(
82+
action_id="feedback",
83+
positive_button=FeedbackButtonObject(
84+
text="Good Response",
85+
accessibility_label="Submit positive feedback on this response",
86+
value="good-feedback",
87+
),
88+
negative_button=FeedbackButtonObject(
89+
text="Bad Response",
90+
accessibility_label="Submit negative feedback on this response",
91+
value="bad-feedback",
92+
),
93+
)
94+
]
95+
)
96+
]
97+
return blocks
98+
```
99+
100+
For information on calling the `chat_*Stream` API methods without the helper utility, see the [_Sending streaming messages_](/tools/python-slack-sdk/web#sending-streaming-messages) section of the Python Slack SDK docs.

docs/reference/app/app.html

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,7 +1195,9 @@ <h2 class="section-title" id="header-classes">Classes</h2>
11951195
middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
11961196
) -&gt; Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]:
11971197
&#34;&#34;&#34;Registers a new `view_submission` listener.
1198-
Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details.&#34;&#34;&#34;
1198+
Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
1199+
details.
1200+
&#34;&#34;&#34;
11991201

12001202
def __call__(*args, **kwargs):
12011203
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
@@ -3018,7 +3020,9 @@ <h2 id="args">Args</h2>
30183020
middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
30193021
) -&gt; Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]:
30203022
&#34;&#34;&#34;Registers a new `view_submission` listener.
3021-
Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details.&#34;&#34;&#34;
3023+
Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
3024+
details.
3025+
&#34;&#34;&#34;
30223026

30233027
def __call__(*args, **kwargs):
30243028
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
@@ -3028,7 +3032,8 @@ <h2 id="args">Args</h2>
30283032
return __call__</code></pre>
30293033
</details>
30303034
<div class="desc"><p>Registers a new <code>view_submission</code> listener.
3031-
Refer to <a href="https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission">https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission</a> for details.</p></div>
3035+
Refer to <a href="https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission">https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission</a> for
3036+
details.</p></div>
30323037
</dd>
30333038
</dl>
30343039
</dd>

docs/reference/app/async_app.html

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,7 +1215,9 @@ <h2 class="section-title" id="header-classes">Classes</h2>
12151215
middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
12161216
) -&gt; Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
12171217
&#34;&#34;&#34;Registers a new `view_submission` listener.
1218-
Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details.&#34;&#34;&#34;
1218+
Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
1219+
details.
1220+
&#34;&#34;&#34;
12191221

12201222
def __call__(*args, **kwargs):
12211223
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
@@ -3075,7 +3077,9 @@ <h2 id="args">Args</h2>
30753077
middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
30763078
) -&gt; Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
30773079
&#34;&#34;&#34;Registers a new `view_submission` listener.
3078-
Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details.&#34;&#34;&#34;
3080+
Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
3081+
details.
3082+
&#34;&#34;&#34;
30793083

30803084
def __call__(*args, **kwargs):
30813085
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
@@ -3085,7 +3089,8 @@ <h2 id="args">Args</h2>
30853089
return __call__</code></pre>
30863090
</details>
30873091
<div class="desc"><p>Registers a new <code>view_submission</code> listener.
3088-
Refer to <a href="https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission">https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission</a> for details.</p></div>
3092+
Refer to <a href="https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission">https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission</a> for
3093+
details.</p></div>
30893094
</dd>
30903095
<dt id="slack_bolt.app.async_app.AsyncApp.web_app"><code class="name flex">
30913096
<span>def <span class="ident">web_app</span></span>(<span>self, path: str = '/slack/events', port: int = 3000) ‑> aiohttp.web_app.Application</span>

docs/reference/app/index.html

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,7 +1214,9 @@ <h2 class="section-title" id="header-classes">Classes</h2>
12141214
middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
12151215
) -&gt; Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]:
12161216
&#34;&#34;&#34;Registers a new `view_submission` listener.
1217-
Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details.&#34;&#34;&#34;
1217+
Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
1218+
details.
1219+
&#34;&#34;&#34;
12181220

12191221
def __call__(*args, **kwargs):
12201222
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
@@ -3037,7 +3039,9 @@ <h2 id="args">Args</h2>
30373039
middleware: Optional[Sequence[Union[Callable, Middleware]]] = None,
30383040
) -&gt; Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]:
30393041
&#34;&#34;&#34;Registers a new `view_submission` listener.
3040-
Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details.&#34;&#34;&#34;
3042+
Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
3043+
details.
3044+
&#34;&#34;&#34;
30413045

30423046
def __call__(*args, **kwargs):
30433047
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
@@ -3047,7 +3051,8 @@ <h2 id="args">Args</h2>
30473051
return __call__</code></pre>
30483052
</details>
30493053
<div class="desc"><p>Registers a new <code>view_submission</code> listener.
3050-
Refer to <a href="https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission">https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission</a> for details.</p></div>
3054+
Refer to <a href="https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission">https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission</a> for
3055+
details.</p></div>
30513056
</dd>
30523057
</dl>
30533058
</dd>

docs/reference/async_app.html

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,7 +1306,9 @@ <h3>Class variables</h3>
13061306
middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
13071307
) -&gt; Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
13081308
&#34;&#34;&#34;Registers a new `view_submission` listener.
1309-
Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details.&#34;&#34;&#34;
1309+
Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
1310+
details.
1311+
&#34;&#34;&#34;
13101312

13111313
def __call__(*args, **kwargs):
13121314
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
@@ -3166,7 +3168,9 @@ <h2 id="args">Args</h2>
31663168
middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None,
31673169
) -&gt; Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]:
31683170
&#34;&#34;&#34;Registers a new `view_submission` listener.
3169-
Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for details.&#34;&#34;&#34;
3171+
Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for
3172+
details.
3173+
&#34;&#34;&#34;
31703174

31713175
def __call__(*args, **kwargs):
31723176
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
@@ -3176,7 +3180,8 @@ <h2 id="args">Args</h2>
31763180
return __call__</code></pre>
31773181
</details>
31783182
<div class="desc"><p>Registers a new <code>view_submission</code> listener.
3179-
Refer to <a href="https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission">https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission</a> for details.</p></div>
3183+
Refer to <a href="https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission">https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission</a> for
3184+
details.</p></div>
31803185
</dd>
31813186
<dt id="slack_bolt.async_app.AsyncApp.web_app"><code class="name flex">
31823187
<span>def <span class="ident">web_app</span></span>(<span>self, path: str = '/slack/events', port: int = 3000) ‑> aiohttp.web_app.Application</span>
@@ -5158,6 +5163,7 @@ <h3>Class variables</h3>
51585163
icon_emoji: Optional[str] = None,
51595164
icon_url: Optional[str] = None,
51605165
username: Optional[str] = None,
5166+
markdown_text: Optional[str] = None,
51615167
mrkdwn: Optional[bool] = None,
51625168
link_names: Optional[bool] = None,
51635169
parse: Optional[str] = None, # none, full
@@ -5183,6 +5189,7 @@ <h3>Class variables</h3>
51835189
icon_emoji=icon_emoji,
51845190
icon_url=icon_url,
51855191
username=username,
5192+
markdown_text=markdown_text,
51865193
mrkdwn=mrkdwn,
51875194
link_names=link_names,
51885195
parse=parse,
@@ -5248,11 +5255,18 @@ <h3>Class variables</h3>
52485255
self.channel_id = channel_id
52495256
self.thread_ts = thread_ts
52505257

5251-
async def __call__(self, status: str) -&gt; AsyncSlackResponse:
5258+
async def __call__(
5259+
self,
5260+
status: str,
5261+
loading_messages: Optional[List[str]] = None,
5262+
**kwargs,
5263+
) -&gt; AsyncSlackResponse:
52525264
return await self.client.assistant_threads_setStatus(
5253-
status=status,
52545265
channel_id=self.channel_id,
52555266
thread_ts=self.thread_ts,
5267+
status=status,
5268+
loading_messages=loading_messages,
5269+
**kwargs,
52565270
)</code></pre>
52575271
</details>
52585272
<div class="desc"></div>
@@ -5298,7 +5312,7 @@ <h3>Class variables</h3>
52985312

52995313
async def __call__(
53005314
self,
5301-
prompts: List[Union[str, Dict[str, str]]],
5315+
prompts: Sequence[Union[str, Dict[str, str]]],
53025316
title: Optional[str] = None,
53035317
) -&gt; AsyncSlackResponse:
53045318
prompts_arg: List[Dict[str, str]] = []

docs/reference/context/say/async_say.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ <h2 class="section-title" id="header-classes">Classes</h2>
8787
icon_emoji: Optional[str] = None,
8888
icon_url: Optional[str] = None,
8989
username: Optional[str] = None,
90+
markdown_text: Optional[str] = None,
9091
mrkdwn: Optional[bool] = None,
9192
link_names: Optional[bool] = None,
9293
parse: Optional[str] = None, # none, full
@@ -112,6 +113,7 @@ <h2 class="section-title" id="header-classes">Classes</h2>
112113
icon_emoji=icon_emoji,
113114
icon_url=icon_url,
114115
username=username,
116+
markdown_text=markdown_text,
115117
mrkdwn=mrkdwn,
116118
link_names=link_names,
117119
parse=parse,

docs/reference/context/say/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ <h2 class="section-title" id="header-classes">Classes</h2>
105105
icon_emoji: Optional[str] = None,
106106
icon_url: Optional[str] = None,
107107
username: Optional[str] = None,
108+
markdown_text: Optional[str] = None,
108109
mrkdwn: Optional[bool] = None,
109110
link_names: Optional[bool] = None,
110111
parse: Optional[str] = None, # none, full
@@ -130,6 +131,7 @@ <h2 class="section-title" id="header-classes">Classes</h2>
130131
icon_emoji=icon_emoji,
131132
icon_url=icon_url,
132133
username=username,
134+
markdown_text=markdown_text,
133135
mrkdwn=mrkdwn,
134136
link_names=link_names,
135137
parse=parse,

docs/reference/context/say/say.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ <h2 class="section-title" id="header-classes">Classes</h2>
9090
icon_emoji: Optional[str] = None,
9191
icon_url: Optional[str] = None,
9292
username: Optional[str] = None,
93+
markdown_text: Optional[str] = None,
9394
mrkdwn: Optional[bool] = None,
9495
link_names: Optional[bool] = None,
9596
parse: Optional[str] = None, # none, full
@@ -115,6 +116,7 @@ <h2 class="section-title" id="header-classes">Classes</h2>
115116
icon_emoji=icon_emoji,
116117
icon_url=icon_url,
117118
username=username,
119+
markdown_text=markdown_text,
118120
mrkdwn=mrkdwn,
119121
link_names=link_names,
120122
parse=parse,

docs/reference/context/set_status/async_set_status.html

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,18 @@ <h2 class="section-title" id="header-classes">Classes</h2>
7070
self.channel_id = channel_id
7171
self.thread_ts = thread_ts
7272

73-
async def __call__(self, status: str) -&gt; AsyncSlackResponse:
73+
async def __call__(
74+
self,
75+
status: str,
76+
loading_messages: Optional[List[str]] = None,
77+
**kwargs,
78+
) -&gt; AsyncSlackResponse:
7479
return await self.client.assistant_threads_setStatus(
75-
status=status,
7680
channel_id=self.channel_id,
7781
thread_ts=self.thread_ts,
82+
status=status,
83+
loading_messages=loading_messages,
84+
**kwargs,
7885
)</code></pre>
7986
</details>
8087
<div class="desc"></div>

0 commit comments

Comments
 (0)