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: agents and assistants #2295

Merged
merged 12 commits into from
Nov 1, 2024
156 changes: 156 additions & 0 deletions docs/content/basic/app-assistant.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
---
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!
lukegalbraithrussell marked this conversation as resolved.
Show resolved Hide resolved
:::

## The `Assistant` class instance
lukegalbraithrussell marked this conversation as resolved.
Show resolved Hide resolved

```ts
const assistant = new Assistant({
threadContextStore: {

This comment was marked as resolved.

Copy link
Contributor

Choose a reason for hiding this comment

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

The default one is purely in-memory, right? If so, it will not work well when used with the AwsLambdaReceiver, since each event invocation spins up a fresh Bolt instance, meaning the default thread context store will be empty each time. If so, we might need a callout here to instruct users of the AwsLambdaReceiver to not use the default one.

Copy link
Member

Choose a reason for hiding this comment

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

@filmaj the default one uses the bot's first reply's message metadata to remember the latest context data, so you can safely use it for any situation.

get: async ({ context, client, payload }) => {},
save: async ({ context, client, payload }) => {},
},
threadStarted: async ({ say, saveThreadContext, setStatus, setSuggestedPrompts, setTitle }) => {},
threadContextChanged: async ({ say, setStatus, setSuggestedPrompts, setTitle }) => {},
lukegalbraithrussell marked this conversation as resolved.
Show resolved Hide resolved
userMessage: async ({ say, getThreadContext, setStatus, setSuggestedPrompts, setTitle }) => {},
});
```

You can store context through the `threadContextStore` property but it must feature `get` and `save` methods. 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.
Copy link
Contributor

@filmaj filmaj Oct 18, 2024

Choose a reason for hiding this comment

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

I think we need to define what 'context' means here. It is such a loaded term in programming in general but also just in Bolt: there is a separate advanced Bolt concept "Adding context" that is different - but maybe related? - from the context concept in this doc! I'd probably break out defining context into its own sub-section.

@misscoded how should we reconcile the two ideas of "context" here? Maybe it's the same idea just extended? Not sure, but we probably need to elaborate somewhat.

Another thing: we should describe why developers might need a context store. This was one of the first questions that came up for me when I reviewed this functionality in Bolt earlier this week, and @seratch provided a great answer that I think we should leverage in these docs. The key for me was: while the two assistant_* events do provide the Slack-client context information (that is, where is the end-user situated within the workspace when they interact with the Assistant view, e.g. what channel do they have open side-by-side with the assistant view?), crucially the assistant-thread-message events delivered to the app do not. Therefore, as a developer, if you're trying to build a nice experience that flows across all three assistant events, you likely have to keep track of this context as different combinations of these three assistant events get delivered to your app. The context store, then, is more about filling that context gap when assistant message events get delivered to an app.

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 it's important whenever talking about context as it relates to an Assistant as "thread context" to distinguish it from the event context that is otherwise provided.


## 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).
lukegalbraithrussell marked this conversation as resolved.
Show resolved Hide resolved

```js
...
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 },
misscoded marked this conversation as resolved.
Show resolved Hide resolved
lukegalbraithrussell marked this conversation as resolved.
Show resolved Hide resolved
});

await saveThreadContext();
lukegalbraithrussell marked this conversation as resolved.
Show resolved Hide resolved

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

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 }) => {
lukegalbraithrussell marked this conversation as resolved.
Show resolved Hide resolved
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.
lukegalbraithrussell marked this conversation as resolved.
Show resolved Hide resolved

## 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 }) => {
lukegalbraithrussell marked this conversation as resolved.
Show resolved Hide resolved
const { channel, thread_ts } = message;

// Set the title of the Assistant thread to capture the initial topic/question
await setTitle(message.text);
lukegalbraithrussell marked this conversation as resolved.
Show resolved Hide resolved

// 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>

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"
lukegalbraithrussell marked this conversation as resolved.
Show resolved Hide resolved
https://github.com/slack-samples/bolt-js-assistant-template/blob/main/app.js
```
</details>
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

:::

## Assistants
lukegalbraithrussell marked this conversation as resolved.
Show resolved Hide resolved

### 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.
lukegalbraithrussell marked this conversation as resolved.
Show resolved Hide resolved
| `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.
lukegalbraithrussell marked this conversation as resolved.
Show resolved Hide resolved

## 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 @@ -40,6 +40,7 @@ const sidebars = {
'basic/options',
'basic/authenticating-oauth',
'basic/socket-mode',
'basic/app-assistant'
lukegalbraithrussell marked this conversation as resolved.
Show resolved Hide resolved
],
},
{
Expand Down