Skip to content

Commit de2b9e2

Browse files
authored
Add Agents & Assistants document page (#1383)
1 parent 0e17588 commit de2b9e2

File tree

3 files changed

+317
-0
lines changed

3 files changed

+317
-0
lines changed

docs/content/guides/assistants.md

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
---
2+
lang: en
3+
---
4+
5+
# Agents & Assistants
6+
7+
This guide focuses on how to implement Agents & Assistants using Bolt. For general information about the feature, please refer to the [API documentation](https://api.slack.com/docs/apps/ai).
8+
9+
## Slack App Configuration
10+
11+
To get started, you'll need to enable the **Agents & Assistants** feature on [the app configuration page](https://api.slack.com/apps). Then, add [`assistant:write`](https://api.slack.com/scopes/assistant:write), [`chat:write`](https://api.slack.com/scopes/chat:write), and [`im:history`](https://api.slack.com/scopes/im:history) to the **bot** scopes on the **OAuth & Permissions** page. Also, make sure to subscribe to [`assistant_thread_started`](https://api.slack.com/events/assistant_thread_started), [`assistant_thread_context_changed`](https://api.slack.com/events/assistant_thread_context_changed), and [`message.im`](https://api.slack.com/events/message.im) events on the **Event Subscriptions** page.
12+
13+
Please note that 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.
14+
15+
## Examples
16+
17+
To handle assistant thread interactions with humans, although you can implement your agents using `app.event(...)` listeners for `assistant_thread_started`, `assistant_thread_context_changed`, and `message` events, Bolt offers a simpler approach. You just need to create an `Assistant` instance, attach the needed event handlers to it, and then add the assistant to your `App` instance.
18+
19+
```java
20+
App app = new App();
21+
Assistant assistant = new Assistant(app.executorService());
22+
23+
assistant.threadStarted((req, ctx) -> {
24+
try {
25+
ctx.say(r -> r.text("Hi, how can I help you today?"));
26+
ctx.setSuggestedPrompts(Collections.singletonList(
27+
SuggestedPrompt.create("What does SLACK stand for?")
28+
));
29+
} catch (Exception e) {
30+
ctx.logger.error("Failed to handle assistant thread started event: {e}", e);
31+
}
32+
});
33+
34+
assistant.userMessage((req, ctx) -> {
35+
try {
36+
ctx.setStatus("is typing...");
37+
Thread.sleep(500L);
38+
if (ctx.getThreadContext() != null) {
39+
String contextChannel = ctx.getThreadContext().getChannelId();
40+
ctx.say(r -> r.text("I am ware of the channel context: <#" + contextChannel + ">"));
41+
} else {
42+
ctx.say(r -> r.text("Here you are!"));
43+
}
44+
} catch (Exception e) {
45+
ctx.logger.error("Failed to handle assistant thread started event: {e}", e);
46+
try {
47+
ctx.say(r -> r.text(":warning: Sorry, something went wrong during processing your request!"));
48+
} catch (Exception ee) {
49+
ctx.logger.error("Failed to inform the error to the end-user: {ee}", ee);
50+
}
51+
}
52+
});
53+
54+
app.assistant(assistant);
55+
```
56+
57+
When a user opens an Assistant thread while in a channel, the channel information is stored as the thread's `AssistantThreadContext` data. You can access this information by using the `get_thread_context` utility. The reason Bolt provides this utility is that the most recent thread context information is not included in the subsequent user message event payload data. Therefore, an app must store the context data when it is changed so that the app can refer to the data in message event listeners.
58+
59+
When the user switches channels, the `assistant_thread_context_changed` event will be sent to your app. If you use the built-in `Assistant` middleware without any custom configuration (like the above code snippet does), the updated context data is automatically saved as message metadata of the first reply from the assistant bot.
60+
61+
As long as you use the built-in approach, you don't need to store the context data within a datastore. The downside of this default behavior is the overhead of additional calls to the Slack API. These calls include those to `conversations.history` which are used to look up the stored message metadata that contains the thread context (via `context.getThreadContextService().findCurrentContext(channelId, threadTs)`).
62+
63+
If you prefer storing this data elsewhere, you can pass your own `AssistantThreadContextService` implementation to the `Assistant` instance:
64+
65+
```java
66+
Assistant assistant = new Assistant(new YourOwnAssistantThreadContextService());
67+
```
68+
69+
<details>
70+
71+
<summary>
72+
Block Kit interactions in the assistant thread
73+
</summary>
74+
75+
For advanced use cases, Block Kit buttons may be used instead of suggested prompts, as well as the sending of messages with structured [metadata](https://api.slack.com/metadata) to trigger subsequent interactions with the user.
76+
77+
For example, an app can display a button like "Summarize the referring channel" in the initial reply. When the user clicks the button and submits detailed information (such as the number of messages, days to check, the purpose of the summary, etc.), the app can handle that information and post a message that describes the request with structured metadata.
78+
79+
By default, your app can't respond to its own bot messages (Bolt prevents infinite loops by default). However, if you set `ignoringSelfAssistantMessageEventsEnabled` to false and add a `botMessage` listener to your `Assistant` middleware, your app can continue processing the request as shown below:
80+
81+
```java
82+
App app = new App(AppConfig.builder()
83+
.singleTeamBotToken(System.getenv("SLACK_BOT_TOKEN"))
84+
.ignoringSelfAssistantMessageEventsEnabled(false)
85+
.build());
86+
87+
Assistant assistant = new Assistant(app.executorService());
88+
89+
assistant.threadStarted((req, ctx) -> {
90+
try {
91+
ctx.say(r -> r
92+
.text("Hi, how can I help you today?")
93+
.blocks(Arrays.asList(
94+
section(s -> s.text(plainText("Hi, how I can I help you today?"))),
95+
actions(a -> a.elements(Collections.singletonList(
96+
button(b -> b.actionId("assistant-generate-numbers").text(plainText("Generate numbers")))
97+
)))
98+
))
99+
);
100+
} catch (Exception e) {
101+
ctx.logger.error("Failed to handle assistant thread started event: {e}", e);
102+
}
103+
});
104+
105+
app.blockAction("assistant-generate-numbers", (req, ctx) -> {
106+
app.executorService().submit(() -> {
107+
Map<String, Object> eventPayload = new HashMap<>();
108+
eventPayload.put("num", 20);
109+
try {
110+
ctx.client().chatPostMessage(r -> r
111+
.channel(req.getPayload().getChannel().getId())
112+
.threadTs(req.getPayload().getMessage().getThreadTs())
113+
.text("OK, I will generate numbers for you!")
114+
.metadata(new Message.Metadata("assistant-generate-numbers", eventPayload))
115+
);
116+
} catch (Exception e) {
117+
ctx.logger.error("Failed to post a bot message: {e}", e);
118+
}
119+
});
120+
return ctx.ack();
121+
});
122+
123+
assistant.botMessage((req, ctx) -> {
124+
if (req.getEvent().getMetadata() != null
125+
&& req.getEvent().getMetadata().getEventType().equals("assistant-generate-numbers")) {
126+
try {
127+
ctx.setStatus("is typing...");
128+
Double num = (Double) req.getEvent().getMetadata().getEventPayload().get("num");
129+
Set<String> numbers = new HashSet<>();
130+
SecureRandom random = new SecureRandom();
131+
while (numbers.size() < num) {
132+
numbers.add(String.valueOf(random.nextInt(100)));
133+
}
134+
Thread.sleep(1000L);
135+
ctx.say(r -> r.text("Her you are: " + String.join(", ", numbers)));
136+
} catch (Exception e) {
137+
ctx.logger.error("Failed to handle assistant bot message event: {e}", e);
138+
}
139+
}
140+
});
141+
142+
assistant.userMessage((req, ctx) -> {
143+
try {
144+
ctx.setStatus("is typing...");
145+
ctx.say(r -> r.text("Sorry, I couldn't understand your comment."));
146+
} catch (Exception e) {
147+
ctx.logger.error("Failed to handle assistant user message event: {e}", e);
148+
try {
149+
ctx.say(r -> r.text(":warning: Sorry, something went wrong during processing your request!"));
150+
} catch (Exception ee) {
151+
ctx.logger.error("Failed to inform the error to the end-user: {ee}", ee);
152+
}
153+
}
154+
});
155+
156+
app.assistant(assistant);
157+
```
158+
159+
</details>
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
---
2+
lang: en
3+
---
4+
5+
# エージェント・アシスタント
6+
7+
このページは、Bolt を使ってエージェント・アシスタントを実装するための方法を紹介します。この機能に関する一般的な情報については、[こちらのドキュメントページ(英語)](https://api.slack.com/docs/apps/ai)を参照してください。
8+
9+
## Slack App Configuration
10+
11+
この機能を実装するためには、まず[アプリの設定画面](https://api.slack.com/apps)**Agents & Assistants** 機能を有効にし、**OAuth & Permissions** のページで [`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)**ボットの**スコープに追加し、**Event Subscriptions** のページで [`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) イベントを有効にしてください。
12+
13+
また、この機能は Slack の有料プランでのみ利用可能です。もし開発用の有料プランのワークスペースをお持ちでない場合は、[Developer Program](https://api.slack.com/developer-program) に参加し、全ての有料プラン向け機能を利用可能なサンドボックス環境をつくることができます。
14+
15+
## Examples
16+
17+
ユーザーとのアシスタントスレッド内でのやりとりを処理するには、`assistant_thread_started``assistant_thread_context_changed``message` イベントの `app.event(...)` リスナーを使うことも可能ですが、Bolt はよりシンプルなアプローチを提供しています。`Assistant` インスタンスを作り、それに必要なイベントリスナーを追加し、最後にこのアシスタント設定を `App` インスタンスに渡すだけで良いのです。
18+
19+
```java
20+
App app = new App();
21+
Assistant assistant = new Assistant(app.executorService());
22+
23+
assistant.threadStarted((req, ctx) -> {
24+
try {
25+
ctx.say(r -> r.text("Hi, how can I help you today?"));
26+
ctx.setSuggestedPrompts(Collections.singletonList(
27+
SuggestedPrompt.create("What does SLACK stand for?")
28+
));
29+
} catch (Exception e) {
30+
ctx.logger.error("Failed to handle assistant thread started event: {e}", e);
31+
}
32+
});
33+
34+
assistant.userMessage((req, ctx) -> {
35+
try {
36+
ctx.setStatus("is typing...");
37+
Thread.sleep(500L);
38+
if (ctx.getThreadContext() != null) {
39+
String contextChannel = ctx.getThreadContext().getChannelId();
40+
ctx.say(r -> r.text("I am ware of the channel context: <#" + contextChannel + ">"));
41+
} else {
42+
ctx.say(r -> r.text("Here you are!"));
43+
}
44+
} catch (Exception e) {
45+
ctx.logger.error("Failed to handle assistant thread started event: {e}", e);
46+
try {
47+
ctx.say(r -> r.text(":warning: Sorry, something went wrong during processing your request!"));
48+
} catch (Exception ee) {
49+
ctx.logger.error("Failed to inform the error to the end-user: {ee}", ee);
50+
}
51+
}
52+
});
53+
54+
app.assistant(assistant);
55+
```
56+
57+
ユーザーがチャンネルの横でアシスタンスレッドを開いた場合、そのチャンネルの情報はそのスレッドの `AssistantThreadContext` データとして保持され、 `context.getThreadContextService().findCurrentContext(channelId, threadTs)` ユーティリティを使ってアクセスすることができます。
58+
59+
そのユーザーがチャンネルを切り替えた場合、`assistant_thread_context_changed` イベントがあなたのアプリに送信されます。(上記のコード例のように)組み込みの `Assistant` ミドルウェアをカスタム設定なしで利用している場合、この更新されたチャンネル情報は、自動的にこのアシスタントボットからの最初の返信のメッセージメタデータとして保存されます。これは、組み込みの仕組みを使う場合は、このコンテキスト情報を自前で用意したデータストアに保存する必要はないということです。この組み込みの仕組みの唯一の短所は、追加の Slack API 呼び出しによる処理時間のオーバーヘッドです。具体的には `context.getThreadContextService().findCurrentContext(channelId, threadTs)` を実行したときに、この保存されたメッセージメタデータにアクセスするために `conversations.history` API が呼び出されます。
60+
61+
このデータを別の場所に保存したい場合、自前の `AssistantThreadContextService` 実装を `Assistant` のコンストラクターに渡すことができます。
62+
63+
```java
64+
Assistant assistant = new Assistant(new YourOwnAssistantThreadContextService());
65+
```
66+
67+
<details>
68+
69+
<summary>
70+
アシスタントスレッドでの Block Kit インタラクション
71+
</summary>
72+
73+
より高度なユースケースでは、上のようなプロンプト例の提案ではなく Block Kit のボタンなどを使いたいという場合があるかもしれません。そして、後続の処理のために構造化されたメッセージメタデータを含むメッセージを送信したいという場合もあるでしょう。
74+
75+
例えば、アプリが最初の返信で「参照しているチャンネルを要約」のようなボタンを表示し、ユーザーがそれをクリックして、より詳細な情報(例:要約するメッセージ数・日数、要約の目的など)を送信、アプリがそれを構造化されたメータデータに整理した上でリクエスト内容をボットのメッセージとして送信するようなシナリオです。
76+
77+
デフォルトでは、アプリはそのアプリ自身から送信したボットメッセージに応答することはできません(Bolt にはあらかじめ無限ループを防止する制御が入っているため)。`ignoringSelfAssistantMessageEventsEnabled` を false に設定し、`botMessage` リスナーを `Assistant` ミドルウェアに追加すると、上記の例のようなリクエストを伝えるボットメッセージを使って処理を継続することができるようになります。
78+
79+
```java
80+
App app = new App(AppConfig.builder()
81+
.singleTeamBotToken(System.getenv("SLACK_BOT_TOKEN"))
82+
.ignoringSelfAssistantMessageEventsEnabled(false)
83+
.build());
84+
85+
Assistant assistant = new Assistant(app.executorService());
86+
87+
assistant.threadStarted((req, ctx) -> {
88+
try {
89+
ctx.say(r -> r
90+
.text("Hi, how can I help you today?")
91+
.blocks(Arrays.asList(
92+
section(s -> s.text(plainText("Hi, how I can I help you today?"))),
93+
actions(a -> a.elements(Collections.singletonList(
94+
button(b -> b.actionId("assistant-generate-numbers").text(plainText("Generate numbers")))
95+
)))
96+
))
97+
);
98+
} catch (Exception e) {
99+
ctx.logger.error("Failed to handle assistant thread started event: {e}", e);
100+
}
101+
});
102+
103+
app.blockAction("assistant-generate-numbers", (req, ctx) -> {
104+
app.executorService().submit(() -> {
105+
Map<String, Object> eventPayload = new HashMap<>();
106+
eventPayload.put("num", 20);
107+
try {
108+
ctx.client().chatPostMessage(r -> r
109+
.channel(req.getPayload().getChannel().getId())
110+
.threadTs(req.getPayload().getMessage().getThreadTs())
111+
.text("OK, I will generate numbers for you!")
112+
.metadata(new Message.Metadata("assistant-generate-numbers", eventPayload))
113+
);
114+
} catch (Exception e) {
115+
ctx.logger.error("Failed to post a bot message: {e}", e);
116+
}
117+
});
118+
return ctx.ack();
119+
});
120+
121+
assistant.botMessage((req, ctx) -> {
122+
if (req.getEvent().getMetadata() != null
123+
&& req.getEvent().getMetadata().getEventType().equals("assistant-generate-numbers")) {
124+
try {
125+
ctx.setStatus("is typing...");
126+
Double num = (Double) req.getEvent().getMetadata().getEventPayload().get("num");
127+
Set<String> numbers = new HashSet<>();
128+
SecureRandom random = new SecureRandom();
129+
while (numbers.size() < num) {
130+
numbers.add(String.valueOf(random.nextInt(100)));
131+
}
132+
Thread.sleep(1000L);
133+
ctx.say(r -> r.text("Her you are: " + String.join(", ", numbers)));
134+
} catch (Exception e) {
135+
ctx.logger.error("Failed to handle assistant bot message event: {e}", e);
136+
}
137+
}
138+
});
139+
140+
assistant.userMessage((req, ctx) -> {
141+
try {
142+
ctx.setStatus("is typing...");
143+
ctx.say(r -> r.text("Sorry, I couldn't understand your comment."));
144+
} catch (Exception e) {
145+
ctx.logger.error("Failed to handle assistant user message event: {e}", e);
146+
try {
147+
ctx.say(r -> r.text(":warning: Sorry, something went wrong during processing your request!"));
148+
} catch (Exception ee) {
149+
ctx.logger.error("Failed to inform the error to the end-user: {ee}", ee);
150+
}
151+
}
152+
});
153+
154+
app.assistant(assistant);
155+
```
156+
157+
</details>

docs/sidebars.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const sidebars = {
3131
items: [
3232
'guides/bolt-basics',
3333
'guides/socket-mode',
34+
'guides/assistants',
3435
'guides/shortcuts',
3536
'guides/interactive-components',
3637
'guides/modals',

0 commit comments

Comments
 (0)