Skip to content

Commit

Permalink
Docs: agents and assistants (#2295)
Browse files Browse the repository at this point in the history
Co-authored-by: Alissa Renz <[email protected]>
  • Loading branch information
lukegalbraithrussell and misscoded authored Nov 1, 2024
1 parent 9bcc680 commit 3d7f242
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 0 deletions.
175 changes: 175 additions & 0 deletions docs/content/concepts/assistant.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
---
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

The [`Assistant`](/reference#the-assistantconfig-configuration-object) can be used to handle the incoming events expected from a user interacting with an assistant in Slack. A typical flow would look like:

1. [The user starts a thread](#handling-new-thread). The `Assistant` class handles the incoming [`assistant_thread_started`](https://api.slack.com/events/assistant_thread_started) event.
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.
3. [The user responds](#handling-user-response). The `Assistant` class handles the incoming [`message.im`](https://api.slack.com/events/message.im) event.

```ts
const assistant = new Assistant({
// If you prefer to not use the provided DefaultThreadContextStore,
// you can use your own optional threadContextStore
threadContextStore: {
get: async ({ context, client, payload }) => {},
save: async ({ context, client, payload }) => {},
},
threadStarted: async ({ say, saveThreadContext, setStatus, setSuggestedPrompts, setTitle }) => {},
// threadContextChanged is optional
// If you use your own optional threadContextStore you likely won't use it
threadContextChanged: async ({ say, setStatus, setSuggestedPrompts, setTitle }) => {},
userMessage: async ({ say, getThreadContext, setStatus, setSuggestedPrompts, setTitle }) => {},
});
```

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 a `DefaultThreadContextStore` 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.

If you do provide your own `threadContextStore` property, it must feature `get` and `save` methods.

:::tip
Be sure to give the [assistants reference docs](/reference#assistants) a look!
:::

## Handling a new thread {#handling-new-thread}

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. Capture this with the `threadStarted` handler to allow your app to respond.

In the example below, the app is sending a message — containing thread context [message metadata](https://api.slack.com/metadata/using) behind the scenes — to the user, along with a single [prompt](https://api.slack.com/methods/assistant.threads.setSuggestedPrompts).

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

await say('Hi, how can I help?');

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, title: 'Here are some suggested options:' });
},
...
```

:::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 thread context changes {#handling-thread-context-changes}

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. Capture this with the `threadContextChanged` handler.

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

If you use the built-in `AssistantThreadContextStore` without any custom configuration, you can skip this — the updated thread context data is automatically saved as [message metadata](https://api.slack.com/metadata/using) on the first reply from the assistant bot.

## Handling the user response {#handling-user-response}

When the user messages your assistant, the [`message.im`](https://api.slack.com/events/message.im) event will be sent to your app. Capture this with the `userMessage` handler.

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).

There are three [utilities](/reference#the-assistantconfig-configuration-object) that are particularly useful in curating the user experience:
* `say`
* `setTitle`
* `setStatus`

The following example uses the [OpenAI API client](https://platform.openai.com/docs/api-reference/introduction), but you can substitute it with the AI client of your choice.

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

try {
// 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);

} catch (e) {
console.error(e);

// Send message to advise user and clear processing status if a failure occurs
await say('Sorry, something went wrong!');
}
},
});

app.assistant(assistant);
```

## Full example : App Agent & Assistant Template

Below is the `app.js` file of the [App Agent & Assistant Template repo](https://github.com/slack-samples/bolt-js-assistant-template/) we've created for you to build off of.

```js reference title="app.js"
https://github.com/slack-samples/bolt-js-assistant-template/blob/main/app.js
```
22 changes: 22 additions & 0 deletions docs/content/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,28 @@ Bolt's client is an instance of `WebClient` from the [Node Slack SDK](https://to

:::

## Agents & Assistants

### The `AssistantConfig` configuration object

| Property | Required? | Description |
|---|---|---|
|`threadContextStore` | Optional | 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) is received, 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.

### Assistant 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.

## Framework error types
Bolt includes a set of error types to make errors easier to handle, with more specific contextual information. Below is a non-exhaustive list of error codes you may run into during development:

Expand Down
1 change: 1 addition & 0 deletions docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const sidebars = {
'concepts/publishing-views',
],
},
'concepts/assistant',
'concepts/custom-steps',
{
type: 'category',
Expand Down

0 comments on commit 3d7f242

Please sign in to comment.