Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: concept focused nav #2302

Merged
merged 9 commits into from
Oct 29, 2024
Merged
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think one thing we should call out in this document is that, while these interactivity and shortcut-related app features do require acknowledgement of Slack events, the Events API handlers do not because Bolt automatically acknowledges them. Perhaps this particular convenience/magic needs reinforcement on this page and possibly also on the 'listening to messages' and 'listening to events' pages?

File renamed without changes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the shortcuts and commands pages, we combine both listening and responding into one doc. Maybe we could do the same for actions? I.e. combine action-listening.md and action-respond.md?

Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ You’ll notice in all `action()` examples, `ack()` is used. It is required to c

:::info

Since v2, message shortcuts (previously message actions) now use the `shortcut()` method instead of the `action()` method. View the [migration guide for V2](/tutorial/migration-v2) to learn about the changes.
Since v2, message shortcuts (previously message actions) now use the `shortcut()` method instead of the `action()` method. View the [migration guide for V2](/migration/migration-v2) to learn about the changes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's just get rid of this callout; we're on v4 now. No need to link or call out things specific about a version that was last released 4 years ago.


:::

Expand Down
186 changes: 186 additions & 0 deletions docs/content/concepts/assistant.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
---
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grabbing this from other PR so I can display Assistant in Nav

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we can merge this change ealier than the assistant page PR, removing this from this PR, merging this PR without assistant page, and then adjusting the assistant page PR to be compatible with this new structure may be smoother

title: Agents & Assistants
lang: en
slug: /concepts/assistant
---

:::info[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.
:::

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!

## Configuring your app to support assistants

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

2. Within the App Settings **OAuth & Permissions** page, add the following scopes:
* [`assistant:write`](https://api.slack.com/scopes/assistant:write)
* [`chat:write`](https://api.slack.com/scopes/chat:write)
* [`im:history`](https://api.slack.com/scopes/im:history)

3 Within the App Settings **Event Subscriptions** page, subscribe to the following events:
* [`assistant_thread_started`](https://api.slack.com/events/assistant_thread_started)
* [`assistant_thread_context_changed`](https://api.slack.com/events/assistant_thread_context_changed)
* [`message.im`](https://api.slack.com/events/message.im)

:::info
You _could_ implement your own assistants by [listening](/concepts/event-listening) for the `assistant_thread_started`, `assistant_thread_context_changed`, and `message.im` events. That being said, using the `Assistant` class will streamline the process. And we already wrote this nice guide for you!
:::

## The `Assistant` class

### Instance template

```ts
const assistant = new Assistant({
threadContextStore: {
get: async ({ context, client, payload }) => {},
save: async ({ context, client, payload }) => {},
},
threadStarted: async ({ say, saveThreadContext, setStatus, setSuggestedPrompts, setTitle }) => {},
threadContextChanged: async ({ say, setStatus, setSuggestedPrompts, setTitle }) => {},
userMessage: async ({ say, getThreadContext, setStatus, setSuggestedPrompts, setTitle }) => {},
});
```

### The `AssistantConfig` configuration object

| Property | Required? | Description |
|---|---|---|
|`threadContextStore` | Optional, but recommended | When provided, must have the required methods to get and save thread context, which will override the `getThreadContext` and `saveThreadContext` utilities. <br/> <br/> If not provided, a `DefaultAssistantContextStore` instance is used.
| `threadStarted` | Required | Executes when the user opens the assistant container or otherwise begins a new chat, thus sending the [`assistant_thread_started`](https://api.slack.com/events/assistant_thread_started) event.
| `threadContextChanged` | Optional | Executes when a user switches channels while the assistant container is open, thus sending the [`assistant_thread_context_changed`](https://api.slack.com/events/assistant_thread_context_changed) event. <br/> <br/> If not provided, context will be saved using the AssistantContextStore's `save` method (either the `DefaultAssistantContextStore` instance or provided `threadContextStore`).
| `userMessage` | Required | Executes when a [message](https://api.slack.com/events/message), thus sending the [`message.im`](https://api.slack.com/events/message.im) event. These messages do not contain a subtype and must be deduced based on their shape and metadata (if provided). Bolt handles this deduction out of the box for those using the `Assistant` class.

### Utilities

Utility | Description
|---|---|
| `getThreadContext` | Alias for `AssistantContextStore.get()` method. Executed if custom `AssistantContextStore` value is provided. <br/><br/> If not provided, the `DefaultAssistantContextStore` instance will retrieve the most recent context saved to the instance.
| `saveThreadContext` | Alias for `AssistantContextStore.save()`. Executed if `AssistantContextStore` value is provided. <br/> <br/> If not provided, the `DefaultAssistantContextStore` instance will save the `assistant_thread.context` to the instance and attach it to the initial assistant message that was sent to the thread.
| `say(message: string)` | Alias for the `postMessage` method.<br/><br/> Sends a message to the current assistant thread.
| `setTitle(title: string)` | [Sets the title](https://api.slack.com/methods/assistant.threads.setTitle) of the assistant thread to capture the initial topic/question.
| `setStatus(status: string)` | Sets the [status](https://api.slack.com/methods/assistant.threads.setStatus) of the assistant to give the appearance of active processing.
| `setSuggestedPrompts({ prompts: [{ title: string; message: string; }]` | Provides the user up to 4 optional, preset [prompts](https://api.slack.com/methods/assistant.threads.setSuggestedPrompts) to choose from.

## Handling a new thread

The `threadStarted` event handler allows your app to respond to new threads opened by users. In the example below, the app is sending a message — containing context message metadata — to the user, along with a single [prompt](https://api.slack.com/methods/assistant.threads.setSuggestedPrompts).

```js
const assistant = new Assistant({
threadStarted: async ({ event, say, setSuggestedPrompts, saveThreadContext }) => {
const { context } = event.assistant_thread;

await say({
text: 'Hi, how can I help?',
metadata: { event_type: 'assistant_thread_context', event_payload: context },
});

await saveThreadContext();

const prompts = [{
title: 'Fun Slack fact',
message: 'Give me a fun fact about Slack, please!',
}];

// Provide the user up to 4 optional, preset prompts to choose from.
await setSuggestedPrompts({ prompts });
},
...
```

:::tip
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 using the `getThreadContext()` utility, as subsequent user message event payloads won't include the channel info.
:::


## Handling context changes

You can store context through the `threadContextStore` property but it must feature `get` and `save` methods.

```js
threadContextStore: {
get: async ({ context, client, payload }) => {},
save: async ({ context, client, payload }) => {},
},
```

If not provided, a `DefaultThreadContextStore` instance is utilized instead, which is a reference implementation that relies on storing and retrieving message metadata as the context changes.

When the user switches channels, the `assistant_thread_context_changed` event will be sent to your app. Capture this with the `threadContextChanged` handler.

```js
...
threadContextChanged: async ({ saveThreadContext }) => {
await saveThreadContext();
},
...
```

If you use the built-in `AssistantThreadContextStore` without any custom configuration the updated context data is automatically saved as message metadata on the first reply from the assistant bot.

## Handling the user response

User messages are handled with the `userMessage` event handler. The `setTitle` and `setStatus` utilities are useful in curating the user experience.

:::warning
Messages sent to the assistant do not contain a subtype and must be deduced based on their shape and any provided metadata.
:::

```js
...
userMessage: async ({ client, message, say, setTitle, setStatus }) => {
const { channel, thread_ts } = message;

// Set the title of the Assistant thread to capture the initial topic/question
await setTitle(message.text);

// Set the status of the Assistant to give the appearance of active processing.
await setStatus('is typing..');

// Retrieve the Assistant thread history for context of question being asked
const thread = await client.conversations.replies({
channel,
ts: thread_ts,
oldest: thread_ts,
});

// Prepare and tag each message for LLM processing
const userMessage = { role: 'user', content: message.text };
const threadHistory = thread.messages.map((m) => {
const role = m.bot_id ? 'assistant' : 'user';
return { role, content: m.text };
});

const messages = [
{ role: 'system', content: DEFAULT_SYSTEM_CONTENT },
...threadHistory,
userMessage,
];

// Send message history and newest question to LLM
const llmResponse = await openai.chat.completions.create({
model: 'gpt-4o-mini',
n: 1,
messages,
});

// Provide a response to the user
await say(llmResponse.choices[0].message.content);
},
});

app.assistant(assistant);
```

## Full example

<details>
<summary>App Agent & Assistant Template</summary>

```js reference title="app.js"
https://github.com/slack-samples/bolt-js-assistant-template/blob/main/app.js
```
</details>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there some end-user or api.slack.com document we can link to for Slash Commands?

File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Listening and responding to custom steps
title: Custom Steps
lang: en
slug: /concepts/custom-steps
---
Expand Down Expand Up @@ -64,7 +64,7 @@ Example app manifest definition

---

### Listening to custom step interactivity events
## Listening to custom step interactivity events

Your app's custom steps may create interactivity points for users, for example: Post a message with a button

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ slug: /concepts/error-handling

:::info

Since v2, error handling has improved! View the [migration guide for V2](/tutorial/migration-v2) to learn about the changes.
Since v2, error handling has improved! View the [migration guide for V2](/migration/migration-v2) to learn about the changes.

:::

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ lang: en
slug: /concepts/event-listening
---

You can listen to [any Events API event](https://api.slack.com/events) using the `event()` method after subscribing to it in your app configuration. This allows your app to take action when something happens in Slack, like a user reacting to a message or joining a channel.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this isn't exactly part of your PR here but figured I'd mention it: the <detail> at the bottom of this page calls out a specific thing about the message() event listening. Perhaps we should move that to the message-listening.md page instead? I also think we can start 'unfurling' these <detail> blocks, since the pages now are much shorter and more focussed. Often, when interacting with devs about various topics, I find I have to explicitly point them to docs content nested inside these folded-up <detail> sections for content relevant to their issue or question.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me!

You can listen to any [Events API event](https://api.slack.com/events) using the `event()` method after subscribing to it in your app configuration. This allows your app to take action when something happens in Slack, like a user reacting to a message or joining a channel.

The `event()` method requires an `eventType` of type string.

Expand Down
File renamed without changes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to change the title of this document so it's a bit more obvious what it's about. "Options" on its own lacks context. It's about dynamic rendering of dropdown select menu options - I'd like to make that more clear in this document as well as in the sidebar.

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ lang: en
slug: /concepts/web-api
---

You can call [any Web API method](https://api.slack.com/methods) using the [`WebClient`](https://tools.slack.dev/node-slack-sdk/web-api) provided to your app's listeners as `client`. This uses either the token that initialized your app **or** the token that is returned from the [`authorize`](/concepts/authorization) function for the incoming event. The built-in [OAuth support](/concepts/authenticating-oauth) handles the second case by default.
You can call any [Web API method](https://api.slack.com/methods) using the [`WebClient`](https://tools.slack.dev/node-slack-sdk/web-api) provided to your app's listeners as `client`. This uses either the token that initialized your app **or** the token that is returned from the [`authorize`](/concepts/authorization) function for the incoming event. The built-in [OAuth support](/concepts/authenticating-oauth) handles the second case by default.

Your Bolt app also has a top-level `app.client` which you can manually pass the `token` parameter. If the incoming request is not authorized or you're calling a method from outside of a listener, use the top-level `app.client`.

Expand Down
Loading