feat: add support for say_stream utility#1462
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1462 +/- ##
==========================================
+ Coverage 91.35% 91.43% +0.08%
==========================================
Files 229 232 +3
Lines 7262 7334 +72
==========================================
+ Hits 6634 6706 +72
Misses 628 628 ☔ View full report in Codecov by Sentry. |
mwbrooks
left a comment
There was a problem hiding this comment.
@WilliamBergamin Thanks a bunch for this PR! It's super exciting.
❓ Can you please provide some code for us to manually test? I'm having trouble importing SayStream and I'm unsure if I'm importing the incorrect path or have the sample app configured incorrectly. What is the import path?
zimeg
left a comment
There was a problem hiding this comment.
🎙️ Leaving a few comments too but an example listener in description might be helpful for later CHANGELOG I agree!
| # TODO: in the future we might want to introduce a "proper" extract_ts utility | ||
| thread_ts = req.context.thread_ts or event.get("ts") | ||
| if req.context.channel_id and thread_ts: | ||
| req.context["say_stream"] = SayStream( | ||
| client=req.context.client, | ||
| channel_id=req.context.channel_id, | ||
| thread_ts=thread_ts, | ||
| team_id=req.context.team_id, | ||
| user_id=req.context.user_id, | ||
| ) |
There was a problem hiding this comment.
👾 thought: Defaulting to different threading behavior for say and say_stream concerns me somewhat.
🔮 ramble: I understand thread_ts is required to stream chat at this time, but if "parent" messages can be streamed in the future we might want to revisit also say behavior?
There was a problem hiding this comment.
Yess I think we can revisite the say behavior in the future, but changing it I think would result a breaking change 🤔
I can add a TODO or maybe create an issue for this?
There was a problem hiding this comment.
@WilliamBergamin Thanks for keeping note of this too! 📫
I'm partial to waiting for related feedback since this might be an expected experience for most! I fear that knowing some of the implementation details biases me...
| assert say_stream is None | ||
| assert context.say_stream is None |
There was a problem hiding this comment.
🌟 praise: This is a pleasant assertion to have for application code guards too I think!
Co-authored-by: Eden Zimbelman <eden.zimbelman@salesforce.com>
|
@mwbrooks check out this app I've been using for testing app.pyimport os
import logging
from time import sleep
from slack_bolt import App, BoltContext, SayStream
from slack_bolt.adapter.socket_mode import SocketModeHandler
from slack_sdk import WebClient
logging.basicConfig(level=logging.DEBUG)
app = App(token=os.environ.get("SLACK_BOT_TOKEN"))
@app.event("app_mention")
def handle_app_mention(client: WebClient, say_stream: SayStream, context: BoltContext):
stream = say_stream(buffer_size=100)
client.assistant_threads_setStatus(
channel_id=context.channel_id,
thread_ts=say_stream.thread_ts,
status="Thinking...",
loading_messages=[
"Waking up from my mass-production nap...",
"Putting on my thinking cap...",
"Consulting the ancient Stack Overflow...",
"Bribing the servers with virtual cookies...",
"Loading witty response... please hold...",
],
)
stream.append(markdown_text="*Someone rang the bat signal!* :bat:\n\n")
sleep(5)
stream.append(markdown_text="Oh wait, it's just an @mention. Even better!\n\n")
stream.append(markdown_text="> Fun fact: I was mass-produced in a mass-production factory, but I like to think I'm *one of a kind*.\n\n")
sleep(1)
stream.append(markdown_text="Anyway, here's my *totally professional* take:\n\n")
stream.append(markdown_text="1. `say_stream` is basically magic — words appear like I'm *actually typing* :sparkles:\n")
sleep(1)
stream.append(markdown_text="2. Streaming means you don't have to stare at a blank screen wondering if I ghosted you\n")
sleep(1)
stream.append(markdown_text="3. Bolt for Python makes building Slack apps easier than microwaving leftovers\n\n")
sleep(0.5)
stream.append(markdown_text="_*mic drop*_ :microphone:")
stream.stop()
@app.message("")
def handle_ask_bot(client: WebClient, say_stream: SayStream, context: BoltContext):
stream = say_stream(buffer_size=100)
client.assistant_threads_setStatus(
channel_id=context.channel_id,
thread_ts=say_stream.thread_ts,
status="Thinking...",
loading_messages=[
"Rummaging through my one brain cell...",
"Asking my rubber duck for advice...",
"Warming up the nonsense generator...",
"Downloading more RAM (just kidding)...",
"Practicing my typing... clack clack clack...",
],
)
stream.append(markdown_text="*Psst...* you just DMed a bot. Bold move. I respect that. :sunglasses:\n\n")
sleep(5)
stream.append(markdown_text="Let me consult my *vast knowledge database*...\n\n")
stream.append(markdown_text="```\n[ scanning... ]\n[ found: 1 brain cell ]\n[ deploying it now ]\n```\n\n")
sleep(1)
stream.append(markdown_text="Okay here's the deal:\n\n")
sleep(1)
stream.append(markdown_text=":rocket: *Streaming responses* means you get to watch me think in real time — terrifying, I know\n")
sleep(1)
stream.append(markdown_text=":hammer_and_wrench: *Bolt for Python* is the secret sauce behind my dazzling personality\n")
sleep(1)
stream.append(markdown_text=":zap: *Socket Mode* keeps our conversation nice and private — no nosy webhooks here\n\n")
sleep(0.5)
stream.append(markdown_text="That's all I've got. Don't forget to tip your bot! :robot_face:")
stream.stop()
if __name__ == "__main__":
SocketModeHandler(app, os.environ.get("SLACK_APP_TOKEN")).start()manifest.json{
"_metadata": {
"major_version": 1,
"minor_version": 1
},
"display_information": {
"name": "say_stream_experiment"
},
"features": {
"app_home": {
"home_tab_enabled": false,
"messages_tab_enabled": true,
"messages_tab_read_only_enabled": false
},
"bot_user": {
"display_name": "say_stream_experiment",
"always_online": false
}
},
"oauth_config": {
"scopes": {
"bot": [
"app_mentions:read",
"chat:write",
"im:read",
"im:write",
"channels:history",
"im:history"
]
}
},
"settings": {
"event_subscriptions": {
"bot_events": [
"app_mention",
"message.im"
]
},
"interactivity": {
"is_enabled": true
},
"org_deploy_enabled": true,
"socket_mode_enabled": true,
"token_rotation_enabled": false
}
} |
zimeg
left a comment
There was a problem hiding this comment.
@WilliamBergamin Super awesome changes recent! I'm finding this streams to channel as expected 🐢 💨
I'm leaving a note on experimental documentation but nothing to block this from merging I think!
| "say_stream is experimental and may change in future versions.", | ||
| category=ExperimentalWarning, | ||
| stacklevel=2, | ||
| ) |
There was a problem hiding this comment.
🔍 note: I might not find this in outputs at the moment - would this be something to include in docstring for say_stream too?
Summary
This PR aims to introduce a new kwarg
say_stream, it allows developers the ability to easily use a WebClient.chat_stream object initialized with logical default values.say_streamisWebClient.chat_streaminitialized withchannel_id: from the event payloadthread_ts:thread_tsfrom the event payload or falls back to thetsvalue if it is availablerecipient_team_id: theteam_idfrom the event received or the enterprise_id if the app is installed on the orgrecipient_user_id: theuser_idfrom the event receivedsay_streamis available onapp.eventandapp.messagelisteners, if Bolt fails to extractchannel_idorthread_tsthensay_streamwill beNoneTesting
scripts/build_pypi_package.shsay_streamSample app.py
manifest.json
{ "_metadata": { "major_version": 1, "minor_version": 1 }, "display_information": { "name": "say_stream_experiment" }, "features": { "app_home": { "home_tab_enabled": false, "messages_tab_enabled": true, "messages_tab_read_only_enabled": false }, "bot_user": { "display_name": "say_stream_experiment", "always_online": false } }, "oauth_config": { "scopes": { "bot": [ "app_mentions:read", "chat:write", "im:read", "im:write", "channels:history", "im:history" ] } }, "settings": { "event_subscriptions": { "bot_events": [ "app_mention", "message.im" ] }, "interactivity": { "is_enabled": true }, "org_deploy_enabled": true, "socket_mode_enabled": true, "token_rotation_enabled": false } }Category
slack_bolt.Appand/or its core componentsslack_bolt.async_app.AsyncAppand/or its core componentsslack_bolt.adapter/docsRequirements
Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.
./scripts/install_all_and_run_tests.shafter making the changes.