|
| 1 | +--- |
| 2 | +lang: en |
| 3 | +--- |
| 4 | + |
| 5 | +# Agents & Assistants |
| 6 | + |
| 7 | +This guide page focuses on how to implement Agents & Assistants using Bolt framework. For general information about the feature, please refer to [this document page](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 an end-user opens an assistant thread next to a channel, the channel information is stored as the thread's `AssistantThreadContext` data, and you can access this information by using `context.getThreadContextService().findCurrentContext(channelId, threadTs)` utility. |
| 58 | + |
| 59 | +When the user switches the channel, 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. This means that, as long as you go with this built-in approach, you don't need to store the context data within any datastore. The only downside of this default module is the runtime overhead of additional Slack API calls. More specifically, it calls `conversations.history` API to look up the stored message metadata when you execute `context.getThreadContextService().findCurrentContext(channelId, threadTs)`. |
| 60 | + |
| 61 | +If you prefer storing this data elsewhere, you can pass your own `AssistantThreadContextService` implementation to the `Assistant` instance: |
| 62 | + |
| 63 | +```java |
| 64 | +Assistant assistant = new Assistant(new YourOwnAssistantThreadContextService()); |
| 65 | +``` |
| 66 | + |
| 67 | +<details> |
| 68 | + |
| 69 | +<summary> |
| 70 | +Block Kit interactions in the assistant thread |
| 71 | +</summary> |
| 72 | + |
| 73 | +For advanced use cases, you might want to use Block Kit buttons instead of the suggested prompts. Additionally, consider sending a message with structured metadata to trigger subsequent interactions with the user. |
| 74 | + |
| 75 | +For example, your app can display a button like "Summarize the referring channel" in the initial reply. When an end-user clicks the button and submits detailed information (such as the number of messages, days to check, the purpose of the summary, etc.), your app can handle the received information and post a bot message describing the request with structured metadata. |
| 76 | + |
| 77 | +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: |
| 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> |
0 commit comments