diff --git a/docs/english/_sidebar.json b/docs/english/_sidebar.json
index eab9d94f8..79721bdcd 100644
--- a/docs/english/_sidebar.json
+++ b/docs/english/_sidebar.json
@@ -7,7 +7,19 @@
},
"tools/bolt-python/getting-started",
{ "type": "html", "value": "
" },
- "tools/bolt-python/building-an-app",
+ "tools/bolt-python/creating-an-app",
+ {
+ "type": "category",
+ "label": "AI & Agents",
+ "link": {
+ "type": "doc",
+ "id": "tools/bolt-python/concepts/adding-agent-features"
+ },
+ "items": [
+ "tools/bolt-python/concepts/adding-agent-features",
+ "tools/bolt-python/concepts/using-the-assistant-class"
+ ]
+ },
{
"type": "category",
"label": "Slack API calls",
@@ -39,7 +51,6 @@
"tools/bolt-python/concepts/app-home"
]
},
- "tools/bolt-python/concepts/ai-apps",
{
"type": "category",
"label": "Custom Steps",
@@ -85,11 +96,7 @@
"tools/bolt-python/concepts/token-rotation"
]
},
- {
- "type": "category",
- "label": "Experiments",
- "items": ["tools/bolt-python/experiments"]
- },
+ "tools/bolt-python/experiments",
{
"type": "category",
"label": "Legacy",
diff --git a/docs/english/concepts/adding-agent-features.md b/docs/english/concepts/adding-agent-features.md
new file mode 100644
index 000000000..dae8bc449
--- /dev/null
+++ b/docs/english/concepts/adding-agent-features.md
@@ -0,0 +1,674 @@
+---
+sidebar_label: Adding agent features
+---
+
+# Adding agent features with Bolt for Python
+
+:::tip[Check out the Support Agent sample app]
+The code snippets throughout this guide are from our [Support Agent sample app](https://github.com/slack-samples/bolt-python-support-agent), Casey, which supports integration with Pydantic, Anthropic, and OpenAI.
+
+View our [agent quickstart](/ai/agent-quickstart) to get up and running with Casey. Otherwise, read on for exploration and explanation of agent-focused Bolt features found within Casey.
+:::
+
+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](/tools/bolt-python/concepts/assistant-class) for a side-panel view designed with AI in mind.
+
+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!
+
+---
+
+## Listening for user invocation
+
+Agents can be invoked throughout Slack, such as via @mentions in channels, messaging the agent, and using the assistant side panel.
+
+
+
+
+```python
+import re
+from logging import Logger
+
+from agents import Runner
+from slack_bolt import BoltContext, Say, SayStream, SetStatus
+from slack_sdk import WebClient
+
+from agent import CaseyDeps, casey_agent
+from thread_context import conversation_store
+from listeners.views.feedback_builder import build_feedback_blocks
+
+
+def handle_app_mentioned(
+ client: WebClient,
+ context: BoltContext,
+ event: dict,
+ logger: Logger,
+ say: Say,
+ say_stream: SayStream,
+ set_status: SetStatus,
+):
+ """Handle @Casey mentions in channels."""
+ try:
+ channel_id = context.channel_id
+ text = event.get("text", "")
+ thread_ts = event.get("thread_ts") or event["ts"]
+ user_id = context.user_id
+
+ # Strip the bot mention from the text
+ cleaned_text = re.sub(r"<@[A-Z0-9]+>", "", text).strip()
+
+ if not cleaned_text:
+ say(
+ text="Hey there! How can I help you? Describe your IT issue and I'll do my best to assist.",
+ thread_ts=thread_ts,
+ )
+ return
+
+ # Add eyes reaction only to the first message (not threaded replies)
+ if not event.get("thread_ts"):
+ client.reactions_add(
+ channel=channel_id,
+ timestamp=event["ts"],
+ name="eyes",
+ )
+ ...
+```
+
+
+
+
+```python
+from logging import Logger
+
+from slack_bolt.context.async_context import AsyncBoltContext
+from slack_bolt.context.say.async_say import AsyncSay
+from slack_bolt.context.say_stream.async_say_stream import AsyncSayStream
+from slack_bolt.context.set_status.async_set_status import AsyncSetStatus
+from slack_sdk.web.async_client import AsyncWebClient
+
+from agent import CaseyDeps, run_casey_agent
+from thread_context import session_store
+from listeners.views.feedback_builder import build_feedback_blocks
+
+
+async def handle_message(
+ client: AsyncWebClient,
+ context: AsyncBoltContext,
+ event: dict,
+ logger: Logger,
+ say: AsyncSay,
+ say_stream: AsyncSayStream,
+ set_status: AsyncSetStatus,
+):
+ """Handle messages sent to Casey via DM or in threads the bot is part of."""
+ # Issue submissions are posted by the bot with metadata so the message
+ # handler can run the agent on behalf of the original user.
+ is_issue_submission = (
+ event.get("metadata", {}).get("event_type") == "issue_submission"
+ )
+
+ # Skip message subtypes (edits, deletes, etc.) and bot messages that
+ # are not issue submissions.
+ if event.get("subtype"):
+ return
+ if event.get("bot_id") and not is_issue_submission:
+ return
+
+ is_dm = event.get("channel_type") == "im"
+ is_thread_reply = event.get("thread_ts") is not None
+
+ if is_dm:
+ pass
+ elif is_thread_reply:
+ # Channel thread replies are handled only if the bot is already engaged
+ session = session_store.get_session(context.channel_id, event["thread_ts"])
+ if session is None:
+ return
+ else:
+ # Top-level channel messages are handled by app_mentioned
+ return
+
+ try:
+ channel_id = context.channel_id
+ text = event.get("text", "")
+ thread_ts = event.get("thread_ts") or event["ts"]
+
+ # Get session ID for conversation context
+ existing_session_id = session_store.get_session(channel_id, thread_ts)
+
+ # Add eyes reaction only to the first message (DMs only — channel
+ # threads already have the reaction from the initial app_mention)
+ if is_dm and not existing_session_id:
+ await client.reactions_add(
+ channel=channel_id,
+ timestamp=event["ts"],
+ name="eyes",
+ )
+
+ # Set assistant thread status with loading messages
+ await set_status(
+ status="Thinking...",
+ loading_messages=[
+ "Teaching the hamsters to type faster…",
+ "Untangling the internet cables…",
+ "Consulting the office goldfish…",
+ "Polishing up the response just for you…",
+ "Convincing the AI to stop overthinking…",
+ ],
+ )
+
+ # For issue submissions the bot posted the message, so the real
+ # user_id comes from the metadata rather than the event context.
+ if is_issue_submission:
+ user_id = event["metadata"]["event_payload"]["user_id"]
+ else:
+ user_id = context.user_id
+
+ # Run the agent with deps for tool access
+ deps = CaseyDeps(
+ client=client,
+ user_id=user_id,
+ channel_id=channel_id,
+ thread_ts=thread_ts,
+ message_ts=event["ts"],
+ )
+ response_text, new_session_id = await run_casey_agent(
+ text, session_id=existing_session_id, deps=deps
+ )
+
+ # Stream response in thread with feedback buttons
+ streamer = await say_stream()
+ await streamer.append(markdown_text=response_text)
+ feedback_blocks = build_feedback_blocks()
+ await streamer.stop(blocks=feedback_blocks)
+
+ # Store session ID for future context
+ if new_session_id:
+ session_store.set_session(channel_id, thread_ts, new_session_id)
+
+ except Exception as e:
+ logger.exception(f"Failed to handle message: {e}")
+ await say(
+ text=f":warning: Something went wrong! ({e})",
+ thread_ts=event.get("thread_ts") or event.get("ts"),
+ )
+```
+
+
+
+
+
+:::tip[Using the Assistant side panel]
+The Assistant side panel requires additional setup. See the [Assistant class guide](/tools/bolt-python/concepts/assistant-class).
+:::
+
+
+```py
+from logging import Logger
+
+from slack_bolt.context.set_suggested_prompts.async_set_suggested_prompts import (
+ AsyncSetSuggestedPrompts,
+)
+
+SUGGESTED_PROMPTS = [
+ {"title": "Reset Password", "message": "I need to reset my password"},
+ {"title": "Request Access", "message": "I need access to a system or tool"},
+ {"title": "Network Issues", "message": "I'm having network connectivity issues"},
+]
+
+
+async def handle_assistant_thread_started(
+ set_suggested_prompts: AsyncSetSuggestedPrompts, logger: Logger
+):
+ """Handle assistant thread started events by setting suggested prompts."""
+ try:
+ await set_suggested_prompts(
+ prompts=SUGGESTED_PROMPTS,
+ title="How can I help you today?",
+ )
+ except Exception as e:
+ logger.exception(f"Failed to handle assistant thread started: {e}")
+```
+
+
+
+
+---
+
+## Setting status {#setting-assistant-status}
+
+Your app can show its users action is happening behind the scenes by setting its thread status.
+
+```python
+def handle_app_mentioned(
+ set_status: SetStatus,
+ ...
+):
+ set_status(
+ status="Thinking...",
+ loading_messages=[
+ "Teaching the hamsters to type faster…",
+ "Untangling the internet cables…",
+ "Consulting the office goldfish…",
+ "Polishing up the response just for you…",
+ "Convincing the AI to stop overthinking…",
+ ],
+ )
+```
+
+---
+
+## Streaming messages {#text-streaming}
+
+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.
+
+The `say_stream` utility streamlines calling the Python Slack SDK's [`WebClient.chat_stream`](https://docs.slack.dev/tools/python-slack-sdk/reference/web/client.html#slack_sdk.web.client.WebClient.chat_stream) helper utility by sourcing parameter values from the relevant event payload.
+
+| Parameter | Value |
+|---|---|
+| `channel_id` | Sourced from the event payload.
+| `thread_ts` | Sourced from the event payload. Falls back to the `ts` value if available.
+| `recipient_team_id` | Sourced from the event `team_id` (`enterprise_id` if the app is installed on an org).
+| `recipient_user_id` | Sourced from the `user_id` of the event.
+
+If neither a `channel_id` or `thread_ts` can be sourced, then the utility will be `None`.
+
+```python
+from slack_bolt import SayStream
+
+def handle_message(say_stream: SayStream):
+ """Stream a response to a message."""
+ streamer = say_stream()
+ streamer.append(markdown_text="Here's my response...")
+ streamer.append(markdown_text="And here's more...")
+ streamer.stop()
+```
+
+---
+
+## Adding and handling feedback {#adding-and-handling-feedback}
+
+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:
+
+```py title=".../listeners/views/feedback_builder.py"
+from slack_sdk.models.blocks import (
+ Block,
+ ContextActionsBlock,
+ FeedbackButtonObject,
+ FeedbackButtonsElement,
+)
+
+
+def build_feedback_blocks() -> list[Block]:
+ """Build feedback blocks with thumbs up/down buttons."""
+ return [
+ ContextActionsBlock(
+ elements=[
+ FeedbackButtonsElement(
+ action_id="feedback",
+ positive_button=FeedbackButtonObject(
+ text="Good Response",
+ accessibility_label="Submit positive feedback on this response",
+ value="good-feedback",
+ ),
+ negative_button=FeedbackButtonObject(
+ text="Bad Response",
+ accessibility_label="Submit negative feedback on this response",
+ value="bad-feedback",
+ ),
+ )
+ ]
+ )
+ ]
+```
+
+That feedback block is then rendered at the bottom of your app's message via the `say_stream` utility.
+
+```py
+...
+ # Stream response in thread with feedback buttons
+ streamer = say_stream()
+ streamer.append(markdown_text=result.output)
+ feedback_blocks = build_feedback_blocks()
+ streamer.stop(blocks=feedback_blocks)
+...
+```
+
+You can also add a response for when the user provides feedback.
+
+```python title="...listeners/actions/feedback_button.py"
+from logging import Logger
+
+from slack_bolt import Ack, BoltContext
+from slack_sdk import WebClient
+
+
+def handle_feedback_button(
+ ack: Ack, body: dict, client: WebClient, context: BoltContext, logger: Logger
+):
+ """Handle thumbs up/down feedback on Casey's responses."""
+ ack()
+
+ try:
+ channel_id = context.channel_id
+ user_id = context.user_id
+ message_ts = body["message"]["ts"]
+ feedback_value = body["actions"][0]["value"]
+
+ if feedback_value == "good-feedback":
+ client.chat_postEphemeral(
+ channel=channel_id,
+ user=user_id,
+ thread_ts=message_ts,
+ text="Glad that was helpful! :tada:",
+ )
+ else:
+ client.chat_postEphemeral(
+ channel=channel_id,
+ user=user_id,
+ thread_ts=message_ts,
+ text="Sorry that wasn't helpful. :slightly_frowning_face: Try rephrasing your question or I can create a support ticket for you.",
+ )
+
+ logger.debug(
+ f"Feedback received: value={feedback_value}, message_ts={message_ts}"
+ )
+ except Exception as e:
+ logger.exception(f"Failed to handle feedback: {e}")
+```
+
+---
+
+## Full example
+
+Putting all those concepts together results in a dynamic agent ready to helpfully respond.
+
+
+
+
+```python title="app_mentioned.py"
+import re
+from logging import Logger
+
+from slack_bolt import BoltContext, Say, SayStream, SetStatus
+from slack_sdk import WebClient
+
+from agent import CaseyDeps, casey_agent, get_model
+from thread_context import conversation_store
+from listeners.views.feedback_builder import build_feedback_blocks
+
+
+def handle_app_mentioned(
+ client: WebClient,
+ context: BoltContext,
+ event: dict,
+ logger: Logger,
+ say: Say,
+ say_stream: SayStream,
+ set_status: SetStatus,
+):
+ """Handle @Casey mentions in channels."""
+ try:
+ channel_id = context.channel_id
+ text = event.get("text", "")
+ thread_ts = event.get("thread_ts") or event["ts"]
+ user_id = context.user_id
+
+ # Strip the bot mention from the text
+ cleaned_text = re.sub(r"<@[A-Z0-9]+>", "", text).strip()
+
+ if not cleaned_text:
+ say(
+ text="Hey there! How can I help you? Describe your IT issue and I'll do my best to assist.",
+ thread_ts=thread_ts,
+ )
+ return
+
+ # Add eyes reaction only to the first message (not threaded replies)
+ if not event.get("thread_ts"):
+ client.reactions_add(
+ channel=channel_id,
+ timestamp=event["ts"],
+ name="eyes",
+ )
+
+ # Set assistant thread status with loading messages
+ set_status(
+ status="Thinking...",
+ loading_messages=[
+ "Teaching the hamsters to type faster…",
+ "Untangling the internet cables…",
+ "Consulting the office goldfish…",
+ "Polishing up the response just for you…",
+ "Convincing the AI to stop overthinking…",
+ ],
+ )
+
+ # Get conversation history
+ history = conversation_store.get_history(channel_id, thread_ts)
+
+ # Run the agent
+ deps = CaseyDeps(
+ client=client,
+ user_id=user_id,
+ channel_id=channel_id,
+ thread_ts=thread_ts,
+ message_ts=event["ts"],
+ )
+ result = casey_agent.run_sync(
+ cleaned_text,
+ model=get_model(),
+ deps=deps,
+ message_history=history,
+ )
+
+ # Stream response in thread with feedback buttons
+ streamer = say_stream()
+ streamer.append(markdown_text=result.output)
+ feedback_blocks = build_feedback_blocks()
+ streamer.stop(blocks=feedback_blocks)
+
+ # Store conversation history
+ conversation_store.set_history(channel_id, thread_ts, result.all_messages())
+
+ except Exception as e:
+ logger.exception(f"Failed to handle app mention: {e}")
+ say(
+ text=f":warning: Something went wrong! ({e})",
+ thread_ts=event.get("thread_ts") or event["ts"],
+ )
+```
+
+
+
+
+```python title="app_mentioned.py"
+import re
+from logging import Logger
+
+from slack_bolt.context.async_context import AsyncBoltContext
+from slack_bolt.context.say.async_say import AsyncSay
+from slack_bolt.context.say_stream.async_say_stream import AsyncSayStream
+from slack_bolt.context.set_status.async_set_status import AsyncSetStatus
+from slack_sdk.web.async_client import AsyncWebClient
+
+from agent import CaseyDeps, run_casey_agent
+from thread_context import session_store
+from listeners.views.feedback_builder import build_feedback_blocks
+
+
+async def handle_app_mentioned(
+ client: AsyncWebClient,
+ context: AsyncBoltContext,
+ event: dict,
+ logger: Logger,
+ say: AsyncSay,
+ say_stream: AsyncSayStream,
+ set_status: AsyncSetStatus,
+):
+ """Handle @Casey mentions in channels."""
+ try:
+ channel_id = context.channel_id
+ text = event.get("text", "")
+ thread_ts = event.get("thread_ts") or event["ts"]
+
+ # Strip the bot mention from the text
+ cleaned_text = re.sub(r"<@[A-Z0-9]+>", "", text).strip()
+
+ if not cleaned_text:
+ await say(
+ text="Hey there! How can I help you? Describe your IT issue and I'll do my best to assist.",
+ thread_ts=thread_ts,
+ )
+ return
+
+ # Add eyes reaction only to the first message (not threaded replies)
+ if not event.get("thread_ts"):
+ await client.reactions_add(
+ channel=channel_id,
+ timestamp=event["ts"],
+ name="eyes",
+ )
+
+ # Set assistant thread status with loading messages
+ await set_status(
+ status="Thinking...",
+ loading_messages=[
+ "Teaching the hamsters to type faster…",
+ "Untangling the internet cables…",
+ "Consulting the office goldfish…",
+ "Polishing up the response just for you…",
+ "Convincing the AI to stop overthinking…",
+ ],
+ )
+
+ # Get session ID for conversation context
+ existing_session_id = session_store.get_session(channel_id, thread_ts)
+
+ # Run the agent with deps for tool access
+ deps = CaseyDeps(
+ client=client,
+ user_id=context.user_id,
+ channel_id=channel_id,
+ thread_ts=thread_ts,
+ message_ts=event["ts"],
+ )
+ response_text, new_session_id = await run_casey_agent(
+ cleaned_text, session_id=existing_session_id, deps=deps
+ )
+
+ # Stream response in thread with feedback buttons
+ streamer = await say_stream()
+ await streamer.append(markdown_text=response_text)
+ feedback_blocks = build_feedback_blocks()
+ await streamer.stop(blocks=feedback_blocks)
+
+ # Store session ID for future context
+ if new_session_id:
+ session_store.set_session(channel_id, thread_ts, new_session_id)
+
+ except Exception as e:
+ logger.exception(f"Failed to handle app mention: {e}")
+ await say(
+ text=f":warning: Something went wrong! ({e})",
+ thread_ts=event.get("thread_ts") or event["ts"],
+ )
+```
+
+
+
+```python title="app_mentioned.py"
+import re
+from logging import Logger
+
+from agents import Runner
+from slack_bolt import BoltContext, Say, SayStream, SetStatus
+from slack_sdk import WebClient
+
+from agent import CaseyDeps, casey_agent
+from thread_context import conversation_store
+from listeners.views.feedback_builder import build_feedback_blocks
+
+
+def handle_app_mentioned(
+ client: WebClient,
+ context: BoltContext,
+ event: dict,
+ logger: Logger,
+ say: Say,
+ say_stream: SayStream,
+ set_status: SetStatus,
+):
+ """Handle @Casey mentions in channels."""
+ try:
+ channel_id = context.channel_id
+ text = event.get("text", "")
+ thread_ts = event.get("thread_ts") or event["ts"]
+ user_id = context.user_id
+
+ # Strip the bot mention from the text
+ cleaned_text = re.sub(r"<@[A-Z0-9]+>", "", text).strip()
+
+ if not cleaned_text:
+ say(
+ text="Hey there! How can I help you? Describe your IT issue and I'll do my best to assist.",
+ thread_ts=thread_ts,
+ )
+ return
+
+ # Add eyes reaction only to the first message (not threaded replies)
+ if not event.get("thread_ts"):
+ client.reactions_add(
+ channel=channel_id,
+ timestamp=event["ts"],
+ name="eyes",
+ )
+
+ # Set assistant thread status with loading messages
+ set_status(
+ status="Thinking...",
+ loading_messages=[
+ "Teaching the hamsters to type faster…",
+ "Untangling the internet cables…",
+ "Consulting the office goldfish…",
+ "Polishing up the response just for you…",
+ "Convincing the AI to stop overthinking…",
+ ],
+ )
+
+ # Get conversation history
+ history = conversation_store.get_history(channel_id, thread_ts)
+
+ # Build input for the agent
+ if history:
+ input_items = history + [{"role": "user", "content": cleaned_text}]
+ else:
+ input_items = cleaned_text
+
+ # Run the agent
+ deps = CaseyDeps(
+ client=client,
+ user_id=user_id,
+ channel_id=channel_id,
+ thread_ts=thread_ts,
+ message_ts=event["ts"],
+ )
+ result = Runner.run_sync(casey_agent, input=input_items, context=deps)
+
+ # Stream response in thread with feedback buttons
+ streamer = say_stream()
+ streamer.append(markdown_text=result.final_output)
+ feedback_blocks = build_feedback_blocks()
+ streamer.stop(blocks=feedback_blocks)
+
+ # Store conversation history
+ conversation_store.set_history(channel_id, thread_ts, result.to_input_list())
+
+ except Exception as e:
+ logger.exception(f"Failed to handle app mention: {e}")
+ say(
+ text=f":warning: Something went wrong! ({e})",
+ thread_ts=event.get("thread_ts") or event["ts"],
+ )
+```
+
+
+
diff --git a/docs/english/concepts/message-sending.md b/docs/english/concepts/message-sending.md
index 87c433129..090503ff2 100644
--- a/docs/english/concepts/message-sending.md
+++ b/docs/english/concepts/message-sending.md
@@ -43,37 +43,58 @@ def show_datepicker(event, say):
## Streaming messages {#streaming-messages}
-You can have your app's messages stream in to replicate conventional AI chatbot behavior. This is done through three Web API methods:
+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.
-* [`chat_startStream`](/reference/methods/chat.startStream)
-* [`chat_appendStream`](/reference/methods/chat.appendStream)
-* [`chat_stopStream`](/reference/methods/chat.stopStream)
+The `say_stream` utility streamlines calling the Python Slack SDK's [`WebClient.chat_stream`](https://docs.slack.dev/tools/python-slack-sdk/reference/web/client.html#slack_sdk.web.client.WebClient.chat_stream) helper utility by sourcing parameter values from the relevant event payload.
-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):
+| Parameter | Value |
+|---|---|
+| `channel_id` | Sourced from the event payload.
+| `thread_ts` | Sourced from the event payload. Falls back to the `ts` value if available.
+| `recipient_team_id` | Sourced from the event `team_id` (`enterprise_id` if the app is installed on an org).
+| `recipient_user_id` | Sourced from the `user_id` of the event.
-```python
-streamer = client.chat_stream(
- channel=channel_id,
- recipient_team_id=team_id,
- recipient_user_id=user_id,
- thread_ts=thread_ts,
-)
-
-# Loop over OpenAI response stream
-# https://platform.openai.com/docs/api-reference/responses/create
-for event in returned_message:
- if event.type == "response.output_text.delta":
- streamer.append(markdown_text=f"{event.delta}")
- else:
- continue
-
-feedback_block = create_feedback_block()
-streamer.stop(blocks=feedback_block)
+If neither a `channel_id` or `thread_ts` can be sourced, then the utility will be `None`.
+
+For information on calling the `chat_*Stream` API methods directly, see the [_Sending streaming messages_](/tools/python-slack-sdk/web#sending-streaming-messages) section of the Python Slack SDK docs.
+
+### Example {#example}
+
+```py
+import os
+
+from slack_bolt import App, SayStream
+from slack_bolt.adapter.socket_mode import SocketModeHandler
+from slack_sdk import WebClient
+
+app = App(token=os.environ.get("SLACK_BOT_TOKEN"))
+
+@app.event("app_mention")
+def handle_app_mention(client: WebClient, say_stream: SayStream):
+ stream = say_stream()
+ stream.append(markdown_text="Someone rang the bat signal!")
+ stream.stop()
+
+@app.message("")
+def handle_message(client: WebClient, say_stream: SayStream):
+ stream = say_stream()
+
+ stream.append(markdown_text="Let me consult my *vast knowledge database*...)
+ stream.stop()
+
+if __name__ == "__main__":
+ SocketModeHandler(app, os.environ.get("SLACK_APP_TOKEN")).start()
```
-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.
+#### Adding feedback buttons after a stream
-```python
+You can pass a [feedback buttons](/reference/block-kit/block-elements/feedback-buttons-element) block element to `stream.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.
+
+```py
+stream.stop(blocks=feedback_block)
+```
+
+```py
def create_feedback_block() -> List[Block]:
blocks: List[Block] = [
ContextActionsBlock(
@@ -95,6 +116,4 @@ def create_feedback_block() -> List[Block]:
)
]
return blocks
-```
-
-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.
\ No newline at end of file
+```
\ No newline at end of file
diff --git a/docs/english/concepts/ai-apps.md b/docs/english/concepts/using-the-assistant-class.md
similarity index 66%
rename from docs/english/concepts/ai-apps.md
rename to docs/english/concepts/using-the-assistant-class.md
index 3b057bc7e..992e7a3c3 100644
--- a/docs/english/concepts/ai-apps.md
+++ b/docs/english/concepts/using-the-assistant-class.md
@@ -1,17 +1,10 @@
-
-# Using AI in Apps {#using-ai-in-apps}
-
-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).
-
-If you're unfamiliar with using these feature within Slack, you may want to read the [API documentation on the subject](/ai/). Then come back here to implement them with Bolt!
-
-## The `Assistant` class instance {#assistant}
+# Using the Assistant class
:::info[Some features within this guide require 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.
:::
-The [`Assistant`](/tools/bolt-js/reference#the-assistantconfig-configuration-object) class can be used to handle the incoming events expected from a user interacting with an app in Slack that has the Agents & AI Apps feature enabled.
+The `Assistant` class can be used to handle the incoming events expected from a user interacting with an app in Slack that has the Agents & AI Apps feature enabled.
A typical flow would look like:
@@ -63,7 +56,7 @@ If you do provide your own `threadContextStore` property, it must feature `get`
:::tip[Refer to the [reference docs](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments.]
:::
-### Configuring your app to support the `Assistant` class {#configuring-assistant-class}
+## Configuring your app to support the `Assistant` class {#configuring-assistant-class}
1. Within [App Settings](https://api.slack.com/apps), enable the **Agents & AI Apps** feature.
@@ -77,7 +70,7 @@ If you do provide your own `threadContextStore` property, it must feature `get`
* [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed)
* [`message.im`](/reference/events/message.im)
-### Handling a new thread {#handling-new-thread}
+## Handling a new thread {#handling-new-thread}
When the user opens a new thread with your AI-enabled app, the [`assistant_thread_started`](/reference/events/assistant_thread_started) event will be sent to your app.
@@ -122,7 +115,7 @@ def start_assistant_thread(
You can send more complex messages to the user — see [Sending Block Kit alongside messages](#block-kit-interactions) for more info.
-### Handling thread context changes {#handling-thread-context-changes}
+## Handling thread context changes {#handling-thread-context-changes}
When the user switches channels, the [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed) event will be sent to your app.
@@ -137,7 +130,7 @@ from slack_bolt import FileAssistantThreadContextStore
assistant = Assistant(thread_context_store=FileAssistantThreadContextStore())
```
-### Handling the user response {#handling-user-response}
+## Handling the user response {#handling-user-response}
When the user messages your app, the [`message.im`](/reference/events/message.im) event will be sent to your app.
@@ -205,7 +198,7 @@ def respond_in_assistant_thread(
app.use(assistant)
```
-### Sending Block Kit alongside messages {#block-kit-interactions}
+## Sending Block Kit alongside messages {#block-kit-interactions}
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.
@@ -331,182 +324,6 @@ def respond_to_bot_messages(logger: logging.Logger, set_status: SetStatus, say:
...
```
-See the [_Adding and handling feedback_](#adding-and-handling-feedback) section for adding feedback buttons with Block Kit.
-
-## Text streaming in messages {#text-streaming}
-
-Three Web API methods work together to provide users a text streaming experience:
-
-* the [`chat.startStream`](/reference/methods/chat.startStream) method starts the text stream,
-* the [`chat.appendStream`](/reference/methods/chat.appendStream) method appends text to the stream, and
-* the [`chat.stopStream`](/reference/methods/chat.stopStream) method stops it.
-
-Since you're using Bolt for Python, built upon the Python Slack SDK, you can use the [`chat_stream()`](https://docs.slack.dev/tools/python-slack-sdk/reference/web/client.html#slack_sdk.web.client.WebClient.chat_stream) utility to streamline all three aspects of streaming in your app's messages.
-
-The following example uses OpenAI's streaming API with the new `chat_stream()` functionality, but you can substitute it with the AI client of your choice.
-
-
-```python
-import os
-from typing import List, Dict
-
-import openai
-from openai import Stream
-from openai.types.responses import ResponseStreamEvent
-
-DEFAULT_SYSTEM_CONTENT = """
-You're an assistant in a Slack workspace.
-Users in the workspace will ask you to help them write something or to think better about a specific topic.
-You'll respond to those questions in a professional way.
-When you include markdown text, convert them to Slack compatible ones.
-When a prompt has Slack's special syntax like <@USER_ID> or <#CHANNEL_ID>, you must keep them as-is in your response.
-"""
-
-def call_llm(
- messages_in_thread: List[Dict[str, str]],
- system_content: str = DEFAULT_SYSTEM_CONTENT,
-) -> Stream[ResponseStreamEvent]:
- openai_client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
- messages = [{"role": "system", "content": system_content}]
- messages.extend(messages_in_thread)
- response = openai_client.responses.create(model="gpt-4o-mini", input=messages, stream=True)
- return response
-
-@assistant.user_message
-def respond_in_assistant_thread(
- ...
-):
- try:
- ...
- replies = client.conversations_replies(
- channel=context.channel_id,
- ts=context.thread_ts,
- oldest=context.thread_ts,
- limit=10,
- )
- messages_in_thread: List[Dict[str, str]] = []
- for message in replies["messages"]:
- role = "user" if message.get("bot_id") is None else "assistant"
- messages_in_thread.append({"role": role, "content": message["text"]})
-
- returned_message = call_llm(messages_in_thread)
-
- streamer = client.chat_stream(
- channel=channel_id,
- recipient_team_id=team_id,
- recipient_user_id=user_id,
- thread_ts=thread_ts,
- )
-
- # Loop over OpenAI response stream
- # https://platform.openai.com/docs/api-reference/responses/create
- for event in returned_message:
- if event.type == "response.output_text.delta":
- streamer.append(markdown_text=f"{event.delta}")
- else:
- continue
-
- streamer.stop()
-
- except Exception as e:
- logger.exception(f"Failed to handle a user message event: {e}")
- say(f":warning: Something went wrong! ({e})")
-```
-
-## Adding and handling feedback {#adding-and-handling-feedback}
-
-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:
-
-```py
-from typing import List
-from slack_sdk.models.blocks import Block, ContextActionsBlock, FeedbackButtonsElement, FeedbackButtonObject
-
-
-def create_feedback_block() -> List[Block]:
- """
- Create feedback block with thumbs up/down buttons
-
- Returns:
- Block Kit context_actions block
- """
- blocks: List[Block] = [
- ContextActionsBlock(
- elements=[
- FeedbackButtonsElement(
- action_id="feedback",
- positive_button=FeedbackButtonObject(
- text="Good Response",
- accessibility_label="Submit positive feedback on this response",
- value="good-feedback",
- ),
- negative_button=FeedbackButtonObject(
- text="Bad Response",
- accessibility_label="Submit negative feedback on this response",
- value="bad-feedback",
- ),
- )
- ]
- )
- ]
- return blocks
-```
-
-Use the `chat_stream` utility to render the feedback block at the bottom of your app's message.
-
-```js
-...
- streamer = client.chat_stream(
- channel=channel_id,
- recipient_team_id=team_id,
- recipient_user_id=user_id,
- thread_ts=thread_ts,
- )
-
- # Loop over OpenAI response stream
- # https://platform.openai.com/docs/api-reference/responses/create
- for event in returned_message:
- if event.type == "response.output_text.delta":
- streamer.append(markdown_text=f"{event.delta}")
- else:
- continue
-
- feedback_block = create_feedback_block()
- streamer.stop(blocks=feedback_block)
-...
-```
-
-Then add a response for when the user provides feedback.
-
-```python
-# Handle feedback buttons (thumbs up/down)
-def handle_feedback(ack, body, client, logger: logging.Logger):
- try:
- ack()
- message_ts = body["message"]["ts"]
- channel_id = body["channel"]["id"]
- feedback_type = body["actions"][0]["value"]
- is_positive = feedback_type == "good-feedback"
-
- if is_positive:
- client.chat_postEphemeral(
- channel=channel_id,
- user=body["user"]["id"],
- thread_ts=message_ts,
- text="We're glad you found this useful.",
- )
- else:
- client.chat_postEphemeral(
- channel=channel_id,
- user=body["user"]["id"],
- thread_ts=message_ts,
- text="Sorry to hear that response wasn't up to par :slightly_frowning_face: Starting a new chat may help with AI mistakes and hallucinations.",
- )
-
- logger.debug(f"Handled feedback: type={feedback_type}, message_ts={message_ts}")
- except Exception as error:
- logger.error(f":warning: Something went wrong! {error}")
-```
-
-## Full example: App Agent Template {#app-agent-template}
+See the [_Creating agents: adding and handling feedback_](/tools/bolt-python/concepts/ai-apps#adding-and-handling-feedback) section for adding feedback buttons with Block Kit.
-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.
+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.
\ No newline at end of file
diff --git a/docs/english/building-an-app.md b/docs/english/creating-an-app.md
similarity index 99%
rename from docs/english/building-an-app.md
rename to docs/english/creating-an-app.md
index bde340961..7f06e9d42 100644
--- a/docs/english/building-an-app.md
+++ b/docs/english/creating-an-app.md
@@ -1,8 +1,8 @@
---
-sidebar_label: Building an App
+sidebar_label: Creating an app
---
-# Building an App with Bolt for Python
+# Creating an app with Bolt for Python
This guide is meant to walk you through getting up and running with a Slack app using Bolt for Python. Along the way, we’ll create a new Slack app, set up your local environment, and develop an app that listens and responds to messages from a Slack workspace.
@@ -10,7 +10,7 @@ When you're finished, you'll have created the [Getting Started app](https://gith
---
-### Create an app {#create-an-app}
+### Create a new app {#create-an-app}
First thing's first: before you start developing with Bolt, you'll want to [create a Slack app](https://api.slack.com/apps/new).
:::tip[A place to test and learn]
diff --git a/docs/english/experiments.md b/docs/english/experiments.md
index 681c8cbc6..13adf0a32 100644
--- a/docs/english/experiments.md
+++ b/docs/english/experiments.md
@@ -28,7 +28,3 @@ def handle_mention(agent: BoltAgent):
stream.append(markdown_text="Hello!")
stream.stop()
```
-
-### Limitations
-
-The `chat_stream()` method currently only works when the `thread_ts` field is available in the event context (DMs and threaded replies). Top-level channel messages do not have a `thread_ts` field, and the `ts` field is not yet provided to `BoltAgent`.
\ No newline at end of file